/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Bespin.
*
* The Initial Developer of the Original Code is
* Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */

var ENV = {"platform":"classic","mode":"production"};
var SC=SC||{BUNDLE_INFO:{},LAZY_INSTANTIATION:{}};SC.json=JSON;SC.browser=(function(){var c=navigator.userAgent.toLowerCase();
var a=(c.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1];var b={version:a,safari:(/webkit/).test(c)?a:0,opera:(/opera/).test(c)?a:0,msie:(/msie/).test(c)&&!(/opera/).test(c)?a:0,mozilla:(/mozilla/).test(c)&&!(/(compatible|webkit)/).test(c)?a:0,mobileSafari:(/apple.*mobile.*safari/).test(c)?a:0,windows:!!(/(windows)/).test(c),mac:!!((/(macintosh)/).test(c)||(/(mac os x)/).test(c)),language:(navigator.language||navigator.browserLanguage).split("-",1)[0]};
b.current=b.msie?"msie":b.mozilla?"mozilla":b.safari?"safari":b.opera?"opera":"unknown";
return b})();SC.bundleDidLoad=function(a){var b=this.BUNDLE_INFO[a];if(!b){b=this.BUNDLE_INFO[a]={}
}b.loaded=true};SC.bundleIsLoaded=function(a){var b=this.BUNDLE_INFO[a];return b?!!b.loaded:false
};SC.loadBundle=function(){throw"SC.loadBundle(): SproutCore is not loaded."};SC.setupBodyClassNames=function(){var e=document.body;
if(!e){return}var c,a,f,b,g,d;c=SC.browser.current;a=SC.browser.windows?"windows":SC.browser.mac?"mac":"other-platform";
d=document.documentElement.style;f=(d.MozBoxShadow!==undefined)||(d.webkitBoxShadow!==undefined)||(d.oBoxShadow!==undefined)||(d.boxShadow!==undefined);
b=(d.MozBorderRadius!==undefined)||(d.webkitBorderRadius!==undefined)||(d.oBorderRadius!==undefined)||(d.borderRadius!==undefined);
g=e.className?e.className.split(" "):[];if(f){g.push("box-shadow")}if(b){g.push("border-rad")
}g.push(c);g.push(a);if(SC.browser.mobileSafari){g.push("mobile-safari")}e.className=g.join(" ")
};
/* >>>>>>>>>> BEGIN source/__preamble__.js */
// ==========================================================================
// Project:   Tiki
// Copyright: ©2009 Apple Inc.
// ==========================================================================
/*globals tiki */

"use modules false";
"use loader false";

/**
  Implements a very simple handler for the loader registration API so that
  additional scripts can load without throwing exceptions.  This loader can
  also return module instances for modules registered with an actual factory
  function.
  
  Note that this stub loader cannot be used on its own.  You must load the 
  regular tiki package as well, which will replace this loader as soon as it
  is fetched.
*/
if ("undefined" === typeof tiki) { var tiki = function() {
  
  var queue = [];
  var factories = {} ; // temporary store of modules
  var modules = {};
  
  // save a registration method in a queue to be replayed later once the 
  // real loader is available.
  function _record(method, args) {
    queue.push({ m: method, a: args });
  }
  
  var tiki = {
    
    _modules: modules,
    _factories: factories,
    
    // used to detect when real loader should replace this one
    isBootstrap: true,
    
    // log of actions to be replayed later
    queue: queue, 
    
    // helpers just record into queue
    register: function() { 
      _record('register', arguments);
       return this;  
    },
    
    script:   function() { 
      _record('script', arguments); 
      return this; 
    },
    
    stylesheet: function() { 
      _record('stylesheet', arguments); 
      return this; 
    },

    // modules actually get saved as well a recorded so you can use them.
    module: function(moduleId, factory) {
      factories[moduleId] = factory;
      _record('module', arguments);
      return this ;
    },

    // require just instantiates the module if needed.  This allows registered
    // modules to be used during bootstrap.  Note that you can only 
    // instantiate modules that register with a real factory function.  
    //
    // use the directive "use factory_format function" in your file to turn 
    // this on.
    //
    require: function(moduleId) {
      var ret, factory, info, idx, packagePart, modulePart ;

      ret = modules[moduleId];
      
      if (!ret) {
        ret = modules[moduleId] = {} ;
        info = { id: moduleId };
        factory = factories[moduleId];
        if (typeof factory !== 'function') throw(moduleId+" is not function");
        factory.call(ret, tiki.require, ret, info);
      }
       
      return ret ;
    },
    
    // cleanup the bootstrap loader when finished
    destroy: function() {
      if (this.isDestroyed) return this;  // nothing to do
      this.isDestroyed = true;
      modules = factories = queue = this.queue = null ;
      return this ;
    }
    
  };
  
  return tiki;
  
}(); };


/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('tiki', {"scripts":[{"url":"\/static\/tiki\/en\/1fd621c214115da84f497cbcf3691b42074a4261\/javascript.js","id":"tiki:en\/1fd621c214115da84f497cbcf3691b42074a4261\/javascript.js"}]});

/* >>>>>>>>>> BEGIN source/core.js */
tiki.module('tiki:core',function(require,exports,module){var setupDisplayNames;// ==========================================================================
// Project:   Tiki
// Copyright: ©2009 Apple Inc.
// ==========================================================================
/*globals setupDisplayNames */

"export package setupDisplayNames";
"use factory_format function";

var TMP_ARY = [];

/**
  Iterate over a property, setting display names on functions as needed. 
*/
setupDisplayNames = function setupDisplayNames(obj, root) {
  var a = TMP_ARY;
  a[0] = root;
  
  var k,v;
  for(k in obj) {
    if (!obj.hasOwnProperty(k)) continue ;
    v = obj[k];
    if ('function' === typeof v) {
      a[1] = k;
      v.displayName = a.join('.');
    }
  }
  
  a.length = 0;
  return obj;
};;exports.setupDisplayNames = setupDisplayNames;
});
/* >>>>>>>>>> BEGIN source/lib/loader.js */
tiki.module('tiki:lib/loader',function(require,exports,module){var core = require('tiki:core');var promise = require('tiki:lib/promise');var sandbox = require('tiki:lib/sandbox');var Loader,create,setup;// ==========================================================================
// Project:   Tiki
// Copyright: ©2009 Apple Inc.
// ==========================================================================
/*globals Loader sandbox promise setup create core */

"import core as core";
"import lib/promise as promise";
"import lib/sandbox as sandbox";

"export package Loader";
"export create setup";

"use factory_format function";

/**
  @file
  
  The Loader module implement a package-based module loader that can fetch 
  packages from any server provided that you register the module first.

  When you load tiki, a global loader is automatically created that will 
  instantiate from this module.
*/

// types of promises
var SCRIPTS     = 'scripts', 
    CATALOG     = 'catalog', 
    MODULES     = 'modules',
    STYLESHEETS = 'stylesheets', 
    LOADS       = 'loads';


// standard wrapper around a module.  replace item[1] with a string and join.
var MODULE_WRAPPER = [
  '(function(require, exports, module) {',
  null,
  '\n});\n//@ sourceURL=',
  null,
  '\n'];

var TIKI_ARY = ['tiki/', null];
var STRING = 'string';

var globals = {};

var PROMISE_NAME = [];
function promiseName(type, key) {
  PROMISE_NAME[0] = type;
  PROMISE_NAME[1] = key;
  return PROMISE_NAME.join('::');
}

var object_keys = Object.keys;
if (!object_keys) {
  object_keys = function(obj) {
    var k, ret = [];
    for(k in obj) {
      if (obj.hasOwnProperty(k)) ret.push(k);
    }
    return ret ;
  };
}

var inTikiCache = {} ;
function inTiki(key) {
  var ret = inTikiCache[key];
  if (ret) return ret ;
  return (inTikiCache[key] = 'tiki/'+key);
}

/**
  @class
  
  The Loader class instantiated whenever you create a new loader.

  You can optionally pass a pending queue of actions which will be replayed 
  on the loader immediate.
*/
Loader = function Loader(id, queue, env) {
  this.id = id ;
  this.scripts = [];
  this.packages = []; 
  this.stylesheets = [];
  this.modules = [] ;
  this.sandbox = sandbox.create(id, this, env);
  this.register('default', {}); // always have a default package
  this.register('tiki', {}); // always local also
  
  // replay queue if provided
  var len = queue ? queue.length : 0, idx, action;
  for(idx=0;idx<len;idx++) {
    action = queue[idx];
    this[action.m].apply(this, action.a);
  }
  
  this._queue = queue ;
  return this ;
};

Loader.prototype = {

  /**
    List of all loaded script urls.
    
    @property {Array}
  */
  scripts: null,
  
  /**
    List of all loaded stylesheet urls.
  
    @property {Array}
  */
  stylesheets: null,
  
  /**
    List of all registered package names.
    
    @property {Array}
  */
  packages: null,
  
  /**
    The default Sandbox for the loader.
    
    @property {Sandbox}
  */
  sandbox: null,
  
  /**
    Registered modules, organized by package name.
  */
  modules: null,

  logLoadFailures: false,
  
  // ..........................................................
  // STANDARD LOADER API
  // 
  
  /**
    Destroys the loader.  Currently this does nothing.
    
    @returns {void} null
  */
  destroy: function() {},
  
  /**
    Take a relative or fully qualified module name as well as an optional
    base module Id name and returns a fully qualified module name.  If you 
    pass a relative module name and no baseId, throws an exception.
    
    Any embedded package name will remain in-tact.
    
    @param {String} moduleId relative or fully qualified module id
    @param {String} baseId fully qualified base id
    @returns {String} fully qualified name
  */
  resolve: function resolve(moduleId, baseId) {
    var path, len, idx, packageId, part, parts;

    // must have some relative path components
    if (moduleId.match(/(^\.\.?\/)|(\/\.\.?\/)|(\/\.\.?\/?$)/)) {

      // if we have a packageId embedded, get that first
      if ((idx=moduleId.indexOf(':'))>=0) {
        packageId = moduleId.slice(0,idx);
        moduleId  = moduleId.slice(idx+1);
        path      = []; // path must always be absolute.
        
      // if no package ID, then use baseId if first component is . or ..
      } else if (moduleId.match(/^\.\.?\//)) {
        if (!baseId) throw ("base required to resolve relative: " + moduleId);
        
        idx = baseId.indexOf(':');
        packageId = baseId.slice(0,idx);
        baseId = baseId.slice(idx+1);
        path = baseId.split('/');
        path.pop(); 
        
      } else path = [];

      // iterate through path components and update path
      parts = moduleId.split('/');
      len   = parts.length;
      for(idx=0;idx<len;idx++) {
        part = parts[idx];
        if (part === '..') {
          if (path.length<1) throw "invalid path: " + moduleId;
          path.pop();
        
        } else if (part !== '.') path.push(part);
      }

      moduleId = path.join('/');
      if (packageId) moduleId = packageId + ':' + moduleId;
      
    // not a relative path, make sure we have a packageId at least
    } if (moduleId.indexOf(':')<0) {
      if (baseId && (idx=baseId.indexOf(':'))>0) {
        packageId = baseId.slice(0,idx);
        moduleId = packageId + ':' + moduleId;
      }
    }
    return moduleId ;
  },

  /**
    Accepts a moduleId and optional baseId.  Returns a canonical
    reference to a module that can be used to actually load the module.

    This works similarly to resolve() except that passing an id with no 
    explicit package that matches package will actually return the package.
    
    @param {String} moduleId the module id
    @param {String} baseId optional base id
    @returns {String} canonical reference
  */
  canonical: function canonical(moduleId, baseId) {
    var ret = this.resolve(moduleId, baseId),
        factories = this._factories,
        catalog   = this._catalog,
        packageId, idx;

    // if moduleId does not contain a packageId, then it MIGHT be a package
    if ((idx=moduleId.indexOf(':'))<0) {
      
      // named module exists...fast path
      if (!catalog || (factories && factories[ret])) return ret;

      // try mapping to a package
      if (catalog[moduleId]) return moduleId + ':index';
        
      // next, try to map to tiki package
      else if (catalog[inTiki(moduleId)]) return inTiki(moduleId)+':index';

    // otherwise, just resolve and try tiki/foo if regular does not exist
    } else {
      packageId = moduleId.slice(0,idx);
      if (catalog && !catalog[packageId] && catalog[inTiki(packageId)]) {
        ret = inTiki(moduleId) ;
      }
    }
    
    return ret ;
  },
  
  /**
    Takes module text, wraps it in a factory function and evaluates it to 
    return a module factory.  This method does not cache the results so call
    it sparingly.  
    
    @param {String} moduleText the raw module text to wrap
    @param {String} moduleId the module id - used for debugging purposes
    @returns {Function} factory function
  */
  evaluate: function evaluate(moduleText, moduleId) {
    var ret;
    
    MODULE_WRAPPER[1] = moduleText;
    MODULE_WRAPPER[3] = moduleId || '(unknown module)';
    
    ret = MODULE_WRAPPER.join('');
    ret = eval(ret);
    
    MODULE_WRAPPER[1] = MODULE_WRAPPER[3] = null;
    return ret;
  },
  
  /**
    Discover and return the factory function for the named module and package.
    You can also optionally pass a base module ID used to resolve the module.
    If you do not explicitly name a package and the package id is not embedded
    in the moduleId, then this method assumes you want the 'default' package.
    
    @param {String} moduleId the relative or absolute moduleId
    @param {String} baseId optional base moduleId to resolve relative paths
    @returns {Function} the factory function for the module
  */
  load: function load(moduleId, baseId) {

    var factories = this._factories, 
        factory, packagePart, idx, info, tmp;
    
    // normalize - attempts to match to an existing module
    moduleId = this.canonical(moduleId, baseId);

    // Verify that this packageId/moduleId is ready.  Then get the factory.
    // evaling if needed.
    if (!this.ready(moduleId)) throw(moduleId + " is not ready");

    // lookup the actual factory.  assume it's there since it passed ready()
    factory = factories[moduleId];
    
    // if factory was registered as a string, then convert it to a function
    // the first time.
    if (factory && (STRING === typeof factory)) {
      factory = this.evaluate(factory, moduleId);
      factories[moduleId] = factory;
    }

    return factory;
  },  
  
  /**
    Loads the named package or module and then copies any exports onto the 
    global object.  Use this method to make module-based code visible to non-
    module based code.
    
    @param {String} moduleId the package or module to make global
    @returns {Loader} receiver
  */
  global: function(moduleId) {
    var depends, info, len, idx, factories, moduleId, exports, key, packageId, ARY;
    
    moduleId = this.canonical(moduleId);
    if ((idx = moduleId.indexOf(':'))<0) throw "package not found: "+moduleId;
    packageId = moduleId.slice(0,idx);
    ARY = moduleId.split(":");
    
    info = this._catalog[packageId];
    if (!info) throw(packageId + " is not registered");  
    
    depends = info.depends;
    len = depends ? depends.length : 0 ;
    if (len<=0) return this; // nothing to do
    
    factories = this._factories;
    if (!factories) return this;  // nothing to do
    
    for(idx=0;idx<len;idx++) {
      packageId = depends[idx];
      if (globals[packageId]) continue ;
      globals[packageId] = true; // don't do this again
      
      if (!this.ready(packageId)) {
        throw("cannot make " + packageId + " global because it is not ready");
      }

      moduleId = this.canonical(packageId);
      if (!factories[moduleId]) continue; //no module to instantiate
      
      // get module and export it globally
      exports = this.require(moduleId);
      for(key in exports) {
        if (!exports.hasOwnProperty(key)) continue;
        window[key] = exports[key];
      }
      
    }
  },
  
  // ..........................................................
  // REGISTRATION CALLBACKS
  // 
  // These methods should be called on the loader by the scripts as they 
  // load from the server.
  
  /**
    Registers a package or module with the loader.  If the name you pass 
    contains a package only, then we expect the second property to contain a
    package descriptor.  If the name contains a package and module, then the
    second parameter should be a factory string or function.
    
    If you call this method more than once with the same package name,
    the new package descriptor will replace the old one, so beware!
    
    @param {String} name the package name
    @param {Hash} desc a package descriptor
    @returns {Loader} receiver
  */
  register: function(name, desc) {
    
    if (name.indexOf(':')>0) return this.module(name, desc);
    
    var catalog = this._catalog, key;
    if (!catalog) catalog = this._catalog = {};
    catalog[name] = desc;
    
    // if the descriptor names other packages, add them those packages
    // to the catalog if they are not already there so that we can load
    // them.  However, the packages should not actually be 'registered'
    // since they haven't really loaded yet.
    var info = desc ? desc.packages : null;
    if (info) {
      for(key in info) {
        if (!info.hasOwnProperty(key) || catalog[key]) continue;
        catalog[key] = info[key];
      }
    }

    // for the package being registered, also add the package to the list
    // of known packages and resolve a CATALOG promise for it.  This may
    // allow other packages to immediately load their contents as well.
    if (!this._resolved(CATALOG, name)) this.packages.push(name);
    this._promiseFor(CATALOG, name).resolve(name);        
  },
  
  /**
    Registers a script with the loader.  If this is the first time this
    has been called, resolves a promise and adds the URL to the scripts
    array.  You should pass a symbolic name that represents the specific 
    script being loaded.  This may or may not match the actual load URL
    
    @param {String} id the script that was loaded
    @returns {Loader} reciever
  */
  script: function script(id) {
    if (!this._resolved(SCRIPTS, id)) this.scripts.push(id);
    this._promiseFor(SCRIPTS, id).resolve(id);        
    return this;
  },
  
  /**
    Registers that a given stylesheet has been loaded.  If this is the 
    first time this has been called, resolves a promise and adds the URL
    to the stylesheets array.
    
    @param {String} url the URL of the stylesheet that was loaded
  */
  stylesheet: function stylesheet(id) {
    if (!this._resolved(STYLESHEETS, id)) this.stylesheets.push(id);
    this._promiseFor(STYLESHEETS, id).resolve(id);        
    return this;
  },
  
  /**
    Registers a module with the loader.  Normally you should call register()
    instead.
    
    @param {String} moduleId name of module itself with package
    @param {Function} factory factory function for module
    @returns {Loader} receiver
  */
  module: function module(moduleId, factory) {
    var factories = this._factories;
    if (!factories) factories = this._factories = {};
    factories[moduleId] = factory ;
    
    if (!this._resolved(MODULES, moduleId)) this.modules.push(moduleId);
    this._promiseFor(MODULES, moduleId).resolve(moduleId);
  },

  /**
    Loads a package asynchronously.  Returns the package info or a promise
    to return the package info when completed.  Use the promise module to
    handle this promise.
  */
  async: function(packageId) {
    return this._async(packageId, null);
  },
  
  _async: function(packageId, seen) {
    var ret = this._promiseFor(LOADS, packageId);
    
    // if the promise is pending (meaning it hasn't been started yet),
    // then either resolve it now or setup an action to load.
    if (ret.status === promise.PENDING) {
      if (this.ready(packageId)) {
        ret.resolve(); // already loaded - just resolve.

      // not fully loaded yet.  Setup the promise action and run it.
      } else {

        var loader = this ;
        
        // this is the actual load action.  Before we can really "load"
        // the package, the package needs to be registered in the catalog.
        // Hence for this action we actually just want to listen for the
        // catalog registration.  Once registered, then we load the 
        // scripts and stylesheets as needed.
        //
        // Also if some info was already found in the catalog, we'll get
        // scripts, stylesheets, and dependencies listed going there as 
        // well.  These are not going to be formal dependencies however,
        // until the actual package is registered.
        ret.action(function(pr) {
          
          // wait for package to be registered.  Once registered, load 
          // all of its contents and then resolve the load promise.
          loader._promiseFor(CATALOG, packageId).then(pr, function() {
            loader._loadPackage(packageId, pr, seen || {});
            pr.resolve();
            
          // handle cancelling...
          }, function(reason) { pr.cancel(reason); });

          // in case there is info already in the catalog, get it going.
          // any of these items must load before the promise can resolve 
          // as well.
          loader._loadPackage(packageId, pr, seen || {});
          
        }).run();
      }
    }
    
    return ret;
  },

  /**
    Requires a module.  This will instantiate a module in the default 
    sandbox.
  */
  require: function require(moduleId, packageId) {
    return this.sandbox.require(moduleId, packageId);
  },
  
  /**
    Return true if package or module is ready.
    
    @param {String} moduleId package or module id
    @returns {Boolean} true if ready
  */
  ready: function ready(moduleId) {
    return this._ready(moduleId, null);
  },
  
  _ready: function(moduleId, seen) {

    if (seen) {
      if (seen[moduleId]) return true ;
      else seen[moduleId] = true ;
    }
    
    var idx, packageId, info, items, loc, scriptId, styleId;

    idx = moduleId.indexOf(':');
    if (idx>=0) packageId = moduleId.slice(0, idx);
    else packageId = moduleId ;
        
    // is package loaded already? - nothing to do if not
    if (!this._resolved(CATALOG, packageId)) {
      if (this.logLoadFailures) {
        console.log(moduleId + " is not ready: '" +
          packageId + "' is not loaded");
      }
      return false;
    }
    info = this._catalog[packageId];
    
    // check dependencies
    items = info.depends;
    loc = items ? items.length : 0;
    while (--loc>=0) {
      if (!seen) seen = {};
      if (!this._ready(items[loc], seen)) {
        if (this.logLoadFailures) {
          console.log(moduleId + " is not ready: dependency '" +
            items[loc] + "' is not ready");
        }
        return false;
      }
    }
    
    // check stylesheets
    items = info.stylesheets ;
    loc = items ? items.length : 0;
    while (--loc >= 0) {
      styleId = items[loc];
      if (STRING !== typeof styleId) styleId = styleId.id;
      if (!this._resolved(STYLESHEETS, styleId)) {
        if (this.logLoadFailures) {
          console.log(moduleId + " is not ready: stylesheet '" +
            styleId + "' is unresolved");
        }
        return false;
      }
    }
    
    // check scripts
    items = info.scripts;
    loc = items ? items.length : 0;
    while(--loc >= 0) {
      scriptId = items[loc];
      if (STRING !== typeof scriptId) scriptId = scriptId.id;
      if (!this._resolved(SCRIPTS, scriptId)) {
        if (this.logLoadFailures) {
          console.log(moduleId + " is not ready: script '" +
            scriptId + "' is unresolved");
        }
        return false;
      }
    }
    
    // check module if provided...
    if (moduleId !== packageId) {
      if (!this._resolved(MODULES, moduleId)) {
        if (this.logLoadFailures) {
          console.log(moduleId + " is not ready: module is unresolved");
        }
        return false;
      }
    }
    
    return true ;
  },
  
  /**
    Registers a symbol as main.  The default implementation will pass this
    onto the system ready module to invoke when the app becomes ready.
  */
  main: function(moduleId, method) {
    var r = this.require('system').ready;
    if (!r) throw("cannot register main because system.ready does not exist");

    // register a handler to invoke to main.
    r.main(this, function() {
      this.async(moduleId).then(this, function() {
        this.require(moduleId)[method]();
      });
    });

    return this ;  
  },
  
  // ..........................................................
  // PRIVATE METHODS
  // 
  
  /** @private
  
    Gets a promise to load a script.  If the promise is still pending,
    then setup an action on the promise to actually load the script. Once
    the script registers itself, this promise will resolve.
  */
  _loadScript: function(scriptId) {
    var id, url, pr;

    // normalize scriptId.
    if (STRING !== typeof(scriptId)) {
      id = scriptId.id;  
      url = scriptId.url; 
    } else id = url = scriptId;
    
    pr = this._promiseFor(SCRIPTS, id);
    if (pr.status === promise.PENDING) {
      var loader = this ;
      pr.action(function(pr) {
        var body = document.body, el;
        if (!body) promise.cancel('no document to append script');
        
        el = document.createElement('script');
        el.setAttribute('src', url);
        body.appendChild(el);
        
        body = el = null ;
      });
    }
    return pr;
  },

  /** @private
  
    Gets a promise to load a stylesheet.  If the promise is still pending,
    then setup an action on the promise to actually load the script. Once
    the script registers itself, this promise will resolve.
  */
  _loadStylesheet: function(styleId) {
    var id, url, pr;

    // normalize scriptId.
    if (STRING !== typeof(styleId)) {
      id = styleId.id ;
      url = styleId.url;
    } else id = url = styleId;
    
    pr = this._promiseFor(STYLESHEETS, id);
    if (pr.status === promise.PENDING) {
      var loader = this ;
      pr.action(function(pr) {
        var body = document.body, el;
        if (!body) pr.cancel('no document to append stylesheet');
        
        el = document.createElement('link');
        el.setAttribute('rel', 'stylesheet');
        el.setAttribute('href', url);
        body.appendChild(el);
        body = el = null ;
        
        loader.stylesheet(id); // register and resolve promise
      });
    }
    return pr;
  },
  
  /** @private
  
    Loads the package if it is found in the catalog.  If a promise is 
    passed, the promise will depend on any found items loading.  May do 
    nothing.
  */
  _loadPackage: function(packageId, pr, seen) {
    
    // detect cyclical references - just don't load again
    if (seen) {
      if (seen[packageId]) {
        console.warn("detected cyclical reference to " + packageId + " seen=" + object_keys(seen).join(', '));
        return ;
      } else seen[packageId] = true;
    }
    
    var info = this._catalog ? this._catalog[packageId] : null,
        items, loc, item, ordered, next, prDepends;
        
    if (!info) return this; // nothing to do

    // PROCESS DEPENDENCES.  
    // Create a single promise to gate these dependencies.  Load each 
    // dependency then resolve the depend promise.  This will gate scripts
    // and stylesheets.
    items = info.depends;
    loc   = items ? items.length : 0;
    
    prDepends = promise.create(promiseName('load-package', packageId));
    while(--loc>=0) prDepends.depends(this._async(items[loc], seen));
    pr.depends(prDepends); // wait till resolved..
    prDepends.resolve();
    
    // PROCESS SCRIPTS
    items = info.scripts;
    loc   = items ? items.length : 0;
    next  = null;
    ordered = info.ordered !== false;  // assume ordered
    
    while(--loc>=0) {
      item = this._loadScript(items[loc]); // get promise to load script
      pr.depends(item); // package promise must wait...

      // ordered scripts must load in series. make next wait on current
      if (ordered) {
        if (next) item.then(next, next.run, next.cancel);
        
      // unordered scripts can load right away
      } else item.run();

      next = item ;
    }
    
    // start first item when ordered as soon as dependencies resolve
    if (next && ordered) prDepends.then(next, next.run, next.cancel);
    
    
    // PROCESS STYLESHEETS
    // Stylesheets can only load once dependencies have loaded to ensure
    // CSS rules layer properly.  They must also run in order to ensure
    // the links are in the correct order.
    items = info.stylesheets;
    loc   = items ? items.length : 0;
    next  = null;
    while(--loc >= 0) {
      item = this._loadStylesheet(items[loc]);
      pr.depends(item);
      if (next) item.then(next, next.run, next.cancel);
      next = item;
    }
    if (next) prDepends.then(next, next.run, next.cancel);
    
    // all done...
  },
  
  // eventually will provide a promise for the specified type/name. used
  // for dependency tracking
  _promiseFor: function(promiseType, name1, name2) {
    
    var promises = this._promises, sub1, sub2, ret, Q; 
    if (!promises) promises = this._promises = {};
    
    sub1 = promises[promiseType];
    if (!sub1) sub1 = promises[promiseType] = {};

    if (name2 === undefined) {
      sub2 = sub1; 
      name2 = name1;
    } else {
      sub2 = sub1[name1];
      if (!sub2) sub2 = sub1[name1] = {} ;
    } 
    
    ret = sub2[name2];
    if (!ret) {
      ret = sub2[name2] = promise.create(promiseName(promiseType, name1));
    }
    
    return ret ;
  },

  _discoveredStylesheets: false,
  
  // discovers any stylesheets in the document body
  discoverStylesheets: function() {
    this._discoveredStylesheets = true;
    if ('undefined' === typeof document) return this; //nothing to do

    var links = document.getElementsByTagName('link'),
        loc = links ? links.length : 0,
        link;
    while(--loc>=0) {
      link = links[loc];
      if (!link || (link.rel !== 'stylesheet')) continue;
      link = link.getAttribute("loadid") || link.getAttribute('LOADID') ;
      if (link) {
          this.stylesheet(link.toString());
      } else {
          continue;
      }
      
      link = link.href ;
      if (link) this.stylesheet(link.toString());
    }
    
    link = link = loc = null;
    
    return this ;
  },
  
  // returns true if a promise already exists for the type/name.  
  _resolved: function(promiseType, name1, name2) {
    if ((promiseType === STYLESHEETS) && !this._discoveredStylesheets) {
      this.discoverStylesheets();
    }
    
    var ret = this._promises;
    if (ret) ret = ret[promiseType];
    if (ret) ret = ret[name1];
    if (ret && name2) ret = ret[name2];
    return ret ? (ret.status===promise.RESOLVED) : false ;
  },
  
  /** @private */
  _inspectLoader: function() {
    
    // assemble list of known modules
    var lines = [], 
        modules = this.modules,
        key, names, len, idx, emitted = false;
        
    lines.push("Loader<id=" + this.id + ">:");
    
    if (this.packages.length>0) {
      lines.push("  packages: " + this.packages.join(','));
      lines.push('');
    }

    if (this.scripts.length>0) {
      lines.push("  scripts:");
      len = this.scripts.length;
      for(idx=0;idx<len;idx++) lines.push("    " + this.scripts[idx]);
      lines.push('');
    }


    if (this.stylesheets.length>0) {
      lines.push("  stylesheets:");
      len = this.scripts.length;
      for(idx=0;idx<len;idx++) lines.push("    " + this.scripts[idx]);
      lines.push('');
    }
            
    if (this.modules.length>0) {
      lines.push("  modules: ");
      len = this.modules.length;
      for(idx=0;idx<len;idx++) lines.push("    " + this.modules[idx]);
      lines.push('');
    } 
    
    return lines.join("\n");
  },
  
  _inspectModule: function(moduleId) {
    var lines = [],
        packageId = moduleId.slice(0, moduleId.indexOf(':')),
        tmp;
        
    // if the named package does not exist, see if it is in tiki
    if (this._catalog && !this._catalog[packageId]) {
      tmp = 'tiki/' + packageId;
      if (this._catalog[tmp]) {
        packageId = tmp;
        moduleId  = 'tiki/' + moduleId;
      }     
    }
    
    lines.push(moduleId + " (" + (this.ready(moduleId) ? 'READY' : 'NOT READY') + "):");
    
    lines.push(this._inspectPackage(packageId));
    return lines.join("\n");
  },
  
  _inspectPackage: function(packageId) {


    // if the named package does not exist, see if it is in tiki
    if (this._catalog && !this._catalog[packageId]) {
      var tmp = 'tiki/' + packageId;
      if (this._catalog[tmp]) packageId = tmp;
    }

    // emit header...
    var lines = [],
        info  = this._catalog ? this._catalog[packageId] : null,
        idx, len, item, parts;
        
    lines.push(packageId + " (" + (this.ready(packageId) ? 'READY' : 'NOT READY') + "): " + (info ? '' : 'Not in Catalog!') );
    
    if (!info) return lines.join("\n");
    
    len = info.depends ? info.depends.length : 0;
    if (len > 0) {
      parts = [];
      for(idx=0;idx<len;idx++) {
        item = info.depends[idx];
        parts.push(item + ' (' + (this.ready(item) ? 'READY' : 'NOT READY') + ')');            
      }
      lines.push('  depends: ' + parts.join(', '));
    }
    
    len = info.scripts ? info.scripts.length : 0;
    if (len > 0) {
      lines.push("\n  scripts:");
      for(idx=0;idx<len;idx++) {
        item = info.scripts[idx];
        if (STRING !== typeof item) item = item.id
        lines.push('    ' + item + ' (' + (this._resolved(SCRIPTS, item) ? 'READY' : 'NOT READY') + ')');
      }
    }

    len = info.stylesheets ? info.stylesheets.length : 0;
    if (len > 0) {
      lines.push("\n  stylesheets:");
      for(idx=0;idx<len;idx++) {
        item = info.stylesheets[idx];
        if (STRING !== typeof item) item = item.id
        lines.push('    ' + item + '(' + (this._resolved(STYLESHEETS, item) ? 'READY' : 'NOT READY') + ')');
      }
    }
    
    return lines.join("\n");
  },

  /** @private */
  inspect: function(id) {
    if (arguments.length === 0) return this._inspectLoader();
    else if (id.indexOf(':')<0) return this._inspectPackage(id);
    else return this._inspectModule(id);
  },
  
  /** @private - toString for loader */
  toString: function() {
    return "Loader<id=" + this.id + ">";
  }
  
};

// make methods display useful names
core.setupDisplayNames(Loader.prototype, 'Loader');

// setup the default loader if needed.  pass in the current loader
setup = function setup(curLoader, env) {
  if (curLoader && !curLoader.isBootstrap) return curLoader;
  
  // bootstrap env
  if (!env) env = {};
  if (!env.global && ('undefined' !== typeof window)) env.global = window;
  if (!env.document && ('undefined' !== typeof document)) {
    env.document = document;
  }
  
  var queue = curLoader ? curLoader.queue : null,
      id    = curLoader ? curLoader.id : 'default',
      ret   = new Loader(id, queue, env);
      
  // once we are done with the old loader; tear it down to release memory
  if (curLoader && curLoader.destroy) curLoader.destroy();
  return ret ;
};

create = function create(id) {
  return new Loader(id);
};

;exports.Loader = Loader;
exports.create = create;
exports.setup = setup;
});
/* >>>>>>>>>> BEGIN source/lib/promise.js */
tiki.module('tiki:lib/promise',function(require,exports,module){var core = require('tiki:core');var Promise,create,RESOLVED,PENDING,CANCELLED,BUSY,BLOCKED;// ==========================================================================
// Project:   Tiki
// Copyright: ©2009 Apple Inc.
// ==========================================================================
/*globals Promise create RESOLVED PENDING CANCELLED BUSY BLOCKED core */

// manually implement loader glue so we can place this into bootstrap code

"import core as core";
"export package Promise";
"export create RESOLVED PENDING CANCELLED BUSY BLOCKED";

"use factory_format function";

var LOG_PROMISES = false;

/**
  @file
  
  The promise module provides a generic way of handling asynchronous actions
  that must be defered until some conditions are met.
  
  h2. Examples
  
  Here is an example of how you might use a promise to return some data once
  it is fetched from a server:
  
  {{{
    "import tiki:system/promise as promise";

    // create a promise and call resolve() when the retrieval is complete 
    function retrieveUserInfo() {
      var pr = promise.create();
      SC.Request.getUrl('/user_info').notify(200, pr, pr.resolve);
      return pr ;
    }
    
    // other code can wait on the result like so:
    retrieveUserInfo().then(MyApp, MyApp.processUserInfo);
  }}}
  
  h2. Promise Dependencies
  
  In addition to this basic promise API, you can also make promises dependent
  on one another.  This allows you to create a simple dependency chain of 
  promises that must also resolve before a gating promise resolves.
  
  For example, if you want to know when a UserInfo promise and AccountData 
  promise is resolved, you can create another promise and make it depend on
  those two:
  
  {{{
    "import tiki:system/promise as promise";
    
    function makeReady() {
      var pr = promise.create();
      pr.depends(retrieveUserInfo());
      pr.depends(retrieveAccountData());
      return pr;
    };
    
    makeReady().then(MyApp, MyApp.start);
  }}}
  
  h2. Promise Actions
  
  Finally, promises can have actions associated with them.  When you call 
  run() on the promise, the action will execute as long as the promise is not
  yet resolved and the aciton has not executed before.
  
  Promise actions make it easy to implement a common design pattern where you
  need to perform some action of a promise has not resolved but you only 
  want it to trigger once.
  
  To setup an action, just call the action() method on the promise, passing 
  the method you want to run.  Later, when you are ready to trigger the action
  just call run() on the promise.  This is most useful when you have the run
  method call when another promise resolves.  
  
  For example, if you need the AccountData to load only after the UserInfo 
  runs, here is how you might do it:
  
  {{{
    "import tiki:system/promise as promise";
    "export retrieveUserInfo retrieveAccountData";
    
    var userPromise, accountPromise;
    
    retrieveUserInfo = function() {
      var pr = userPromise ;
      if (!pr) {
        pr = promise.create();
        pr.action(function() {
          SC.Request.getUrl('/user_info').notify(pr, pr.reoslve);
        });
        userPromise = pr;
      }
      pr.run();
      return pr ;
    };
    
    retrieveAccountData = function() {
      var pr = accountPromise;
      if (!pr) {
        pr = promise.create();
        pr.action(funciton() {
          SC.Request.getUrl('/account_data').notify(pr, pr.resolve);
        });

        // run this promise action only when userInfo is retrieved
        retrieveUserInfo().then(pr, pr.run);
        accountPromise = pr;
      }
      return pr;
    }
    
  }}}
*/

/**
  Status of promise when it has been resolved.
  
  @property {String}
*/
RESOLVED = 'resolved';

/**
  Status of promise when it is first created before any action has run.
  
  @property {String}
*/
PENDING = 'pending';

/**
  Status of promise when it has been cancelled.
  
  @property {String}
*/  
CANCELLED = 'cancelled' ;

/**
  Status of promise when it is busy running the action.
  
  @property {String}
*/
BUSY = 'busy';

/**
  Status of promise when it has been resolved but is still waiting on 
  dependencies.
  
  @property {String}
*/
BLOCKED = 'blocked';
  
  
/**
  @class
  
  The promise object itself.  When you create a new promise, this is the 
  object that is returned.

  When you create a promise you can optionally include an id that will be 
  used to identify the promise in log output.
  
  @since Tiki 1.0
*/
var Promise = function(id) {
  this.id = id;
  return this ;
} ;

Promise.prototype = {
  
  /**
    Promise status.  Must be one of RESOLVE, PENDING, CANCELLED, BUSY
    
    @property {String}
  */
  status: PENDING,
  
  /**
    Count of outstanding dependent promises.
    
    @property {Number}
  */
  outstandingDependencies: 0,

  /**
    Becomes true when a promise has an action it can fire.
    
    @property {Boolean}
  */
  hasAction: false,
  
  // ..........................................................
  // PUBLIC METHODS 
  // 
  
  /**
    Resolves the promise with the specified value.  If there are any 
    pending resolve actions, invokes them immediately.
    
    @param {Object} val resolved value
    @returns {Promise} receiver
  */
  resolve: function(val) {
    
    if (LOG_PROMISES) console.log(this.id + ".resolve() dep=" + this.outstandingDependencies + ' status=' + this.status);
    
    var st = this.status;
    if ((st===RESOLVED) || (st===CANCELLED)) return this; // don't go again
    
    this._value = val;

    if (this.outstandingDependencies>0) {
      this.status = BLOCKED;
    } else {
      this.status = RESOLVED;
      this.value  = val;
      this._notify(RESOLVED, val);
    }
    return this ;
  },

  /**
    Cancels the promise with the specified reason.  If there are any 
    pending cancel actions, invokes them immediately.  This ignores any 
    dependencies.
    
    @param {Object} reason a reason for cancelation.  Will be passed to listeners.
    @returns {Promise} receiver
  */
  cancel: function(reason) {
    var st = this.status;
    if ((st===RESOLVED) || (st===CANCELLED)) return this; // nothing to do
    
    this.status = CANCELLED;
    this.value = reason;
    this._notify(CANCELLED, reason);
    return this;
  },

  /**
    Invokes the named target/method(s) when the promise is resolved or 
    cancelled.  If the promise is already resolved or cancelled, invokes
    the callback immediately.
    
    @param {Object} target target for callbacks
    @param {Function} resolveMethod method to invoke when resolved
    @param {Function} cancelMethod method to invoke if cancelled
    @returns {Promise} receiver
  */
  then: function(target, resolveMethod, cancelMethod) {
    var actions;
    
    // normalize arguments - allow you to pass w/o an up front target
    if (arguments.length<3 && ('function' === typeof target)) {
      cancelMethod = resolveMethod;
      resolveMethod = target;
      target = this;
    }
    
    if (resolveMethod) this._register(RESOLVED, target, resolveMethod);
    if (cancelMethod) this._register(CANCELLED, target, cancelMethod);

    return this ;
  },


  /**
    Looks for an "action" function and invokes it unless the promise is
    already resolved or cancelled
  
    @returns {Promise} receiver
  */
  run: function() {
    if (this.method && this.status===PENDING) {
      this.status = BUSY;
      this.method.call(this.target||this, this);
    }
    return this ;
  },

  /**
    If a promise has been resolved or cancelled, this will put it back to
    ready.
  
    @returns {Promise} receiver
  */
  reset: function() {
    var st = this.status;
    if ((st !== BUSY) || (st !== BLOCKED)) this.status = PENDING;
    this.value = null;
    return this;
  },

  /**
    configures the promise so you can run it.  This will only invoke your
    callback once

    @param {Function} method the method to invoke
    @returns {Promise} receiver
  */
  action: function(target, method) {

    // normalize input methods
    if (arguments.length===1) {
      method = target; target = this;
    }

    this.target    = target;
    this.method    = method;
    this.hasAction = true ;
    return this ;
  },

  /**
    Makes the receiver promise depend on the passed promise.  once you 
    resolve a promise, it will not actually resolve until all dependents 
    resolve as well.
    
    @param {Promise} pr promise this promise is dependent upon
    @returns {Promise} receiver
  */
  depends: function(pr) {
    this.outstandingDependencies++;
    if (LOG_PROMISES) console.log(this.id + '.depends('+pr.id+') dep=' + this.outstandingDependencies + ' status=' + this.status);
    pr.then(this, this._resolveDepends, this._cancelDepends);
    return this ;
  },

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

  /** @private
    Called by a dependent promise when it resolves.  Reduce the dependency
    count and, if needed, resolve this promise.
    
    @returns {Promise} receiver
  */
  _resolveDepends: function(ignore, pr) {
    this.outstandingDependencies--;
    if (LOG_PROMISES) console.log('  ' + this.id + '._resolveDepends('+pr.id+') dep=' + this.outstandingDependencies);
    if (this.outstandingDependencies<=0 && (this.status===BLOCKED)) {
      this.resolve(this.value);
    } 
    
    return this ;
  },

  /**
    Called by a dependent promise when the promise is cancelled.  Implicitly
    cancels this promise as well.
    
    @returns {Promise} receiver
  */
  _cancelDepends: function(reason, pr) {
    if (LOG_PROMISES) console.log('  ' + this.id + '._cancelDepends('+pr.id+') dep=' + this.outstandingDependencies);
    this.cancel(reason);
    return this;
  },

  /** @private
    Notify actions of a state change.  This will delete all registered 
    actions in the queue, even for other states.
    
    @param {String} state the state to notify
    @param {Object} arg an optional argument to pass
    @return {void}
  */
  _notify: function(status, arg) {
    var act = this._actions, len, i;
    act = act ? act[status] : null;
    len = act ? act.length : 0;

    this._actions = null; // clear queue
    for(i=0;i<len;i++) this._invoke(act[i].target, act[i].method, arg);
  },

  /** @private
    Invokes the passed target/method with the named argument.  Will convert
    the method if it is a string.
    
    @param {Object} target target to invoke
    @param {Function|String} method the method to invoke
    @param {Object} arg optional argument
    @returns {void}
  */
  _invoke: function(target, method, arg) {
    if (('string' === typeof method) && target) method = target[method];
    if (!target) target = this;
    method.call(target, arg, this);
  },
  
  /** 
    @private
    
    Registers the passed target/method against the named state.  If the 
    promise is already resolved or cancelled, then invoke immediately.
    
    @param {String} status the state to register
    @param {Object} target a target for the callback
    @param {String|Function} method the function to invoke
    @returns {void}
  */
  _register: function(status, target, method) {
    var cur = this.status, actions, f;
    
    if ((cur===RESOLVED)||(cur===CANCELLED)) {
      if (cur === status) this._invoke(target, method, this.value);
      // else ignore.
      
    // not resolved or cancelled; register in queue
    } else {
      actions = this._actions;
      if (!actions) actions = this._actions = {};
      f = actions[status];
      if (!f) f = actions[status] = [];
      f.push({ target: target, method: method });
    }
  },

  /** @private */
  toString: function() {
    return 'Promise<id=' + this.id + ' status=' + this.status + '>';
  }   
};

core.setupDisplayNames(Promise.prototype, 'Promise');

create = function create(id) { return new Promise(id); };

;exports.Promise = Promise;
exports.create = create;
exports.RESOLVED = RESOLVED;
exports.PENDING = PENDING;
exports.CANCELLED = CANCELLED;
exports.BUSY = BUSY;
exports.BLOCKED = BLOCKED;
});
/* >>>>>>>>>> BEGIN source/lib/sandbox.js */
tiki.module('tiki:lib/sandbox',function(require,exports,module){var Sandbox,create;// ==========================================================================
// Project:   Tiki
// Copyright: ©2009 Apple Inc.
// ==========================================================================
/*globals Sandbox create */

"export package Sandbox";
"export create";

"use factory_format function";

/**
  @file
  
  A Sandbox provides a common space to activate secure modules.  You will 
  often have only one sandbox within an application, though you could create
  as many as you like.  This might be useful, for example, to control access
  for plugins.

  To create a new Sandbox, you must pass in a Loader instance as well.  Most 
  of the time you can obtain a loader from the require.loader property.
  
  A new sandbox will inherit whatever modules are registered on the loader.
  Modules will actually be reinstantiated however, when you require each of 
  them.

  @since Tiki 1.0
*/


/**
  @class 

  A sandbox defines a common space where modules can be instantiated and 
  imported.  Each loader comes with a default sandbox where modules are 
  run though you can create additional sandboxes also if you wish.  

  Eventually this will form the basis of a secure system for giving 
  plugins limited access to your application code.

  To create a new sandbox just call the sandbox() method on a loader.  
  This will create a sandbox attached to the named loader.  Once you have 
  created a sandbox, you start running code by calling require().

*/
Sandbox = function Sandbox(id, loader, env) {

  var allExports = {}, // instantiated modules
      modules  = [], // names of instantiated modules
      HASH     = {},
      sandbox  = this;

  this.id = id ;
  this.modules = modules;
  this.loader = loader ; // expose loader

  // private clear method
  var _clear = this.clear = function() {
    
    // if one or more module ids are passed, remove them
    var loc = arguments.length, moduleId;
    if (loc>0) {
      while(--loc>=0) {
        moduleId = arguments[loc];
        if (moduleId && allExports[moduleId]) {
          delete allExports[moduleId];
          modules.splice(modules.indexOf(moduleId), 1);
        }
      }

    // no arguments passed, clear ALL exports.
    } else {
      allExports = {} ;
      modules.length = 0 ;
    }
  }
  _clear.displayName = 'Sandbox.clear';
  
  // this is the core require method.  requires that you pass either a 
  // bundleId
  function _require(moduleId, packageId, curModuleId) {
    var req, exports, moduleInfo, factory, idx;

    // substitute package if needed
    if (packageId) {
      idx = moduleId.indexOf(':');
      if (idx>=0) moduleId = moduleId.slice(0, idx);
      moduleId = packageId + ':' + moduleId;
    }
    
    // convert to canonical moduleId reference.
    moduleId = loader.canonical(moduleId, curModuleId);

    // see if its already initialized. return if so
    if (exp = allExports[moduleId]) return exp;
    
    // not defined...create it
    modules.push(moduleId);

    // not initialized, init module 
    allExports[moduleId] = exports = {};
    
    // generate custom require with safe info exposed
    req = function(m, p) { return _require(m, p, moduleId); };
    req.displayName = 'Sandbox.require';
    
    req.loader  = loader ;
    req.clear   = _clear;
    req.env     = env || {};
    req.sandbox = this;
    
    // setup module info describing module state
    moduleInfo = { id: moduleId };

    // run module factory in loader
    var func = loader.load(moduleId);
    if (!func) throw "could not load function for " + moduleId;
    func.call(exports, req, exports, moduleInfo);

    return exports;
  }

  // require a module...
  this.require = function(moduleId, packageId) { 
    return _require(moduleId, packageId); 
  };
  
  this.require.displayName = 'Sandbox.require';

};

// safe methods
Sandbox.prototype = {};

create = function create(id, loader, env) {
  return new Sandbox(id, loader, env);
};

;exports.Sandbox = Sandbox;
exports.create = create;
});
/* >>>>>>>>>> BEGIN package_exports.js */
tiki.module('tiki:index', function(require, exports, module) {
var m;
m = require('tiki:core');
exports.setupDisplayNames = m.setupDisplayNames;
m = require('tiki:lib/loader');
exports.Loader = m.Loader;
m = require('tiki:lib/promise');
exports.Promise = m.Promise;
m = require('tiki:lib/sandbox');
exports.Sandbox = m.Sandbox;
});

/* >>>>>>>>>> BEGIN source/__postamble__.js */
// ==========================================================================
// Project:   Tiki
// Copyright: ©2009 Apple Inc.
// ==========================================================================
/*globals tiki */

// This postamble runs when the loader and supporting modules are all 
// registered, allowing the real loader to replace the bootstrap version.
// it is not wrapped as a module so that it can run immediately.
"use modules false";
"use loader false";

// note that the loader.setup method is safe so that calling this more than
// once will only setup the default loader once.
tiki = tiki.require('tiki:lib/loader').setup(tiki, ('undefined' === typeof ENV) ? null : ENV) ;

; tiki.script('tiki:en/1fd621c214115da84f497cbcf3691b42074a4261/javascript.js');tiki.stylesheet("sproutcore/foundation:en/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0/stylesheet.css");tiki.stylesheet("sproutcore/testing:en/1e0d6dc675d3d9adb05a01b524dd7fc8fb2ad475/stylesheet.css");tiki.stylesheet("sproutcore/designer:en/3080eb96d6f0ac8d0e9236af215ac0647848d08b/stylesheet.css");tiki.stylesheet("sproutcore/desktop:en/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83/stylesheet.css");tiki.stylesheet("sproutcore/standard_theme:en/4448cf3c8117a1e50376695dc8ea12138b0f9099/stylesheet.css");/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('tiki/platform/classic', {"packages":{"tiki":{"scripts":[{"url":"\/static\/tiki\/en\/1fd621c214115da84f497cbcf3691b42074a4261\/javascript.js","id":"tiki:en\/1fd621c214115da84f497cbcf3691b42074a4261\/javascript.js"}]}},"depends":["tiki"],"scripts":[{"url":"\/static\/tiki\/platform\/classic\/en\/ded01fe6fcc484028c695eebd4c5274b4f895508\/javascript.js","id":"tiki\/platform\/classic:en\/ded01fe6fcc484028c695eebd4c5274b4f895508\/javascript.js"}]});

/* >>>>>>>>>> BEGIN source/lib/console.js */
tiki.module('tiki/platform/classic:lib/console',function(require,exports,module){var console;// ==========================================================================
// Project:   Tiki
// Copyright: ©2009 Apple Inc.
// ==========================================================================
/*globals console */

"export package console";
"use factory_format function";

/**
  @file

  This module exports a console object, which should support that standard 
  console methods as defined by the browser.  Normally we expect the console
  to be equivalent to a "stdout" in a server environment.  In the browser
  environment, we just export the env.window.console object.
  
  system:logger uses this to emit to the standard console log.
  
*/

var env = require.env;
console = (env && env.global) ? env.global.console : null;



;exports.console = console;
});
/* >>>>>>>>>> BEGIN source/lib/event.js */
tiki.module('tiki/platform/classic:lib/event',"var info = require('tiki\/platform\/classic:lib\/info');var add,remove,event,ready,unload,NATIVE_EVENTS;\/\/ ==========================================================================\n\/\/ Project:   Tiki\n\/\/ Copyright: \u00a92006-2009 Sprout Systems, Inc. and contributors.\n\/\/            Portions \u00a92008-2009 Apple Inc. All rights reserved.\n\/\/ License:   Licened under MIT license (see license.js)\n\/\/ ==========================================================================\n\/*globals event exports add remove ready unload info NATIVE_EVENTS *\/\n\n\"import lib\/info as info\";\n\"export add remove\";\n\"export package event ready unload NATIVE_EVENTS\";\n\n\/**\n  @file\n  \n  Implements a low-level interface for registering interest in native events\n  delivered by the web browser.  If you are implementing this API for a non-\n  browser environment, add() and remove() may do nothing.\n  \n  ready() and unload() should still invoke the passed callback when the \n  JS environment is ready for the app to run and just before the app exits,\n  respectively.\n\n  ready() and unload() should both expect to be called exactly once (by the\n  system event module).  They may throw an error if called more than once.\n*\/\n\n\nvar readyCalled  = false, \n    unloadCalled = false,\n    browser      = info.browser,\n    isReady      = false,\n    isUnloaded   = false,\n    ecache       = {};\n    \n\n\/\/ returns a handler function for the onready event depending on the browser.    \nfunction readyHandler(callback) {\n  var ret ;\n  \n  \/\/ opera - wait until all the stylesheets are made visible\n  if (browser.opera) {\n    ret = function() {\n      if (isReady) return;\n      \n      for (var i = 0; i < document.styleSheets.length; i++) {\n        if (document.styleSheets[i].disabled) {\n          setTimeout(ret, 0);\n          return;\n        }\n      }\n      \n      \/\/ and execute any waiting functions\n      isReady = true;\n      callback();\n    };\n    \n  \/\/ msie - wait until the doScroll event stops complaining..\n  } else if (browser.msie) {\n    ret = function() {\n      if (isReady) return;\n      try {\n        \/\/ If IE is used, use the trick by Diego Perini\n        \/\/ http:\/\/javascript.nwbox.com\/IEContentLoaded\/\n        document.documentElement.doScroll(\"left\");\n      } catch( error ) {\n        setTimeout(ret, 0);\n        return;\n      }\n\n      \/\/ and execute any waiting functions\n      isReady = true;\n      callback();\n    };\n    \n  \/\/ everyone else - just call\n  } else {\n    ret = function() {\n      console.log('ready!');\n      if (isReady) return ;\n      isReady = true;\n      callback();\n    };\n  }\n  \n  return ret ;\n  \n}\n\n\/** \n  Invoke the  callback when the browser is ready to handle app code. \n  Throws an exception if it is called more than once.  Uses a built-in \n  listener.\n  \n  @param {Function} callback\n  @returns {void}\n*\/\nready = function(callback) {\n  if (readyCalled) throw(\"Cannot call platform.ready() more than once\");\n  readyCalled = true;\n  \n  var handler = readyHandler(callback);\n  \n  \/\/ Mozilla, Opera (see further below for it) and webkit nightlies \n  \/\/ currently support this event.  Use the handy event callback\n  if (document.addEventListener) {\n    document.addEventListener( \"DOMContentLoaded\", handler, NO );\n  \n  \/\/ If IE is used and is not in a frame\n  \/\/ Continually check to see if the document is ready\n  } else if (browser.msie && (window === top)) handler();\n\n  \/\/ A fallback to window.onload, that will always work\n  add(window, 'load', handler);\n};\n\n\nfunction unloadHandler(callback) {\n  return function() {\n    if (isUnloaded) return ;\n    isUnloaded = true;\n    callback();\n  }; \n}\n\n\/**\n  Invoke the callback just before the browser unloads the page.  Throws an \n  error if called more than once.\n  \n  @param {Function} callback\n  @returns {void}\n*\/\nunload = function(callback) {\n  if (unloadCalled) throw(\"Cannot call platform.unload() more than once\");\n  unloadCalled = true;\n  add(window, 'unload', unloadHandler(callback));\n};\n\n\/\/ ..........................................................\n\/\/ EVENT LISTENERS\n\/\/ \n\n\/**\n  Names of event types natively supported by this library.  Platforms without\n  native event support can return an empty hash.\n*\/\nNATIVE_EVENTS = {};\n\n\/\/ TODO: make this correct per-browser\n\/\/ TODO: add touch events\nvar names = 'mousedown mouseup click dblclick mouseover mouseout selectstart keypress keydown keyup blur focus deactivate change select submit contextmenu dragstart error hashchange help load losecapture readystatechange resize scroll unload'.split(' '), loc = names.length;\nwhile(--loc>=0) NATIVE_EVENTS[names[loc]] = names[loc];\n\n\n\/\/ convert 'foo' => 'onfoo' using cache to avoid malloc\nfunction onStr(str) {\n    var ret = ecache[str];\n    if (!ret) ret = ecache[str] = ('on' + str); \/\/ avoid mallocs\n    return ret ;\n}\n\n\/**\n  Adds a listener for the event.  No need to do any special buffering; just\n  add the listener in a platform-specific way.\n  \n  @param {Object} elem the target element if any.  otherwise use document\n  @param {String} eventType click, mousedown, etc\n  @param {Function} callback function to invoke\n  @returns {void}\n*\/\nadd = function(elem, eventType, callback) {\n  if (!NATIVE_EVENTS[eventType]) return ; \/\/ ignore for native events\n  if (!elem) elem = require.env.document;\n  if (elem.addEventListener) elem.addEventListener(eventType, callback, NO);\n  else if (elem.attachEvent) elem.attachEvent(onStr(eventType), callback);      \n  else throw(\"cannot add listener to element: \" + elem);\n};\n\n\/**\n  Removes a listener for the event.  No need to do any special buffering; just\n  remove the listener in a platform-specific way\n  \n  @param {Object} elem the target element if any. otherwise use document\n  @param {String} eventType click, mousedown, etc\n  @param {Function} func function to invoke\n  @returns {void}\n*\/\nremove = function(elem, eventType, func) {\n  if (!NATIVE_EVENTS[eventType]) return ;\n  if (!elem) elem = require.env.document;\n  if (elem.removeEventListener) elem.removeEventListener(eventType, func, NO);\n  else if (elem.detachEvent) elem.detachEvent(onStr(eventType), func);\n  else throw(\"cannot remove listener from element: \" + elem);\n};\n\n\/\/ make this API visible as the events property on package\nevent = exports;\n;exports.add = add;\nexports.remove = remove;\nexports.event = event;\nexports.ready = ready;\nexports.unload = unload;\nexports.NATIVE_EVENTS = NATIVE_EVENTS;\n");
/* >>>>>>>>>> BEGIN source/lib/info.js */
tiki.module('tiki/platform/classic:lib/info',"var browser,userAgent,info;\/\/ ==========================================================================\n\/\/ Project:   Tiki\n\/\/ Copyright: \u00a92009 Apple Inc.\n\/\/ ==========================================================================\n\/*globals browser userAgent info exports *\/\n\n\"export browser userAgent\";\n\"export package info\";\n\n\/**\n  @file\n\n  Export constants in this module that describe the capabilities of the target\n  platform.  The most important property you can define here is HTML\n*\/\n\n\n\/\/ ..........................................................\n\/\/ BROWSER DESCRIPTION\n\/\/ \n\nuserAgent = navigator.userAgent.toLowerCase();\n\nvar version = (userAgent.match( \/.+(?:rv|it|ra|ie)[\\\/: ]([\\d.]+)\/ ) || [])[1];\n\nbrowser = {\n  \n  \/** The current browser version *\/\n  version: version,\n  \n  \/** non-zero if webkit-based browser *\/\n  safari: (\/webkit\/).test( userAgent ) ? version : 0,\n  \n  \/** non-zero if this is an opera-based browser *\/\n  opera: (\/opera\/).test( userAgent ) ? version : 0,\n  \n  \/** non-zero if this is IE *\/\n  msie: (\/msie\/).test( userAgent ) && !(\/opera\/).test( userAgent ) ? version : 0,\n  \n  \/** non-zero if this is a miozilla based browser *\/\n  mozilla: (\/mozilla\/).test( userAgent ) && !(\/(compatible|webkit)\/).test( userAgent ) ? version : 0,\n  \n  \/** non-zero if this is mobile safari *\/\n  mobileSafari: (\/apple.*mobile.*safari\/).test(userAgent) ? version : 0,\n  \n  \/** non-zero if we are on windows *\/\n  windows: !!(\/(windows)\/).test(userAgent),\n  \n  \/** non-zero if we are on a mac *\/\n  mac: !!((\/(macintosh)\/).test(userAgent) || (\/(mac os x)\/).test(userAgent)),\n  \n  language: (navigator.language || navigator.browserLanguage).split('-', 1)[0]\n};\n\nbrowser.isOpera = !!browser.opera;\nbrowser.isIe = browser.msie;\nbrowser.isIE = browser.msie;\nbrowser.isSafari = browser.safari;\nbrowser.isMobileSafari = browser.mobileSafari;\nbrowser.isMozilla = browser.mozilla;\nbrowser.isWindows = browser.windows;\nbrowser.isMac = browser.mac;\n\n\/**\n  The current browser name.  This is useful for switch statements. \n*\/\nbrowser.current = \n  browser.msie ? 'msie' : \n  browser.mozilla ? 'mozilla' : \n  browser.safari ? 'safari' : \n  browser.opera ? 'opera' : 'unknown' ;\n\n\n\/\/ make this module visible as \"info\"\ninfo = exports ;\n;exports.browser = browser;\nexports.userAgent = userAgent;\nexports.info = info;\n");
/* >>>>>>>>>> BEGIN source/lib/timer.js */
tiki.module('tiki/platform/classic:lib/timer',function(require,exports,module){var schedule,repeat,cancel,timer;// ==========================================================================
// Project:   Tiki
// Copyright: ©2009 Apple Inc.
// ==========================================================================
/*globals schedule repeat cancel timer exports */

"export schedule repeat cancel";
"export package timer";
"use factory_format function";

/**
  @file

  The timer is part of the platform kernel that must be implemented for 
  tiki to run.  It provides three simple methods: schedule(), repeat() and
  cancel().
  
  h3. schedule()
  
  This method should invoke the passed callback/target after a set period of
  time.  The return object should be a handle that can be passed to cancel to
  abort the timer later.  The returned value is opaque; i.e. it's is 
  implementation dependent.
  
  h3. repeat()
  
  This method should invoke the passed callback/target repeatedly until it is
  cancelled.  The return object should be a handle that can be passed to
  cancel() to abort the timer later.  The returned value is opaque.
  
  h3. cancel()
  
  This method should cancel the passed timer object.  The timer should not 
  fire again.
  
  The browser implementation is based on setTimeout() and setInterval().  It 
  is not necessary to use any kind of invoke queue as that is handled by 
  higher levels of the library.
*/

/**
  Invoke the passed callback/target after the specified period of time.
  
  @param {Number} delay delay in milleseconds
  @param {Function} callback the method to invoke
  @param {Object} target optional context to use for this
  @returns {Object} the timer object
*/
schedule = function(after, callback, target) {
  var ret, func;
  func = target ? function() { callback.call(target, ret); } : callback; 
  ret = setTimeout(func, after);
  callback = null; // cleanup memory
  return ret ;
};

/**
  Repeatedly invoke the passed callback/target each period of time until
  the timer is invalidated.
  
  @param {Number} period delay between invocations in milleseconds
  @param {Function} callback the method to invoke
  @param {Object} target optional context to use for this
  @returns {Object} the timer object
*/
repeat = function(after, callback, target) {
  var ret, func;
  func = target ? function() { callback.call(target, ret); } : callback; 
  ret = setInterval(func, after); 
  callback = null; // cleanup memory
  return ret ;
};

/**
  Cancels the passed timer.  It will no longer be invoked.

  @param {Object} timer object returned from schedule() or repeat()
  @param {Boolean} repeats must be YES if this is repeating.
  @returns {void}
*/
cancel = function(timer, repeats) {
  if (repeats === undefined) {
    clearTimeout(timer);
    clearInterval(timer);
  
  } else if (repeats) {
    clearInterval(timer);
  
  } else {
    clearTimeout(timer) ;
  }
};

// make this API visible in the package as "timer"
timer = exports ;
;exports.schedule = schedule;
exports.repeat = repeat;
exports.cancel = cancel;
exports.timer = timer;
});
/* >>>>>>>>>> BEGIN package_exports.js */
tiki.module('tiki/platform/classic:index', function(require, exports, module) {
var m;
m = require('tiki/platform/classic:lib/console');
exports.console = m.console;
m = require('tiki/platform/classic:lib/event');
exports.event = m.event;
exports.ready = m.ready;
exports.unload = m.unload;
exports.NATIVE_EVENTS = m.NATIVE_EVENTS;
m = require('tiki/platform/classic:lib/info');
exports.info = m.info;
m = require('tiki/platform/classic:lib/timer');
exports.timer = m.timer;
});

; tiki.script('tiki/platform/classic:en/ded01fe6fcc484028c695eebd4c5274b4f895508/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('tiki/platform', {"scripts":[{"url":"\/static\/tiki\/platform\/en\/da39a3ee5e6b4b0d3255bfef95601890afd80709\/javascript.js","id":"tiki\/platform:en\/da39a3ee5e6b4b0d3255bfef95601890afd80709\/javascript.js"}]});
tiki.global('tiki/platform');

; tiki.script('tiki/platform:en/da39a3ee5e6b4b0d3255bfef95601890afd80709/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('tiki/system', {"packages":{"tiki\/platform\/classic":{"scripts":[{"url":"\/static\/tiki\/platform\/classic\/en\/ded01fe6fcc484028c695eebd4c5274b4f895508\/javascript.js","id":"tiki\/platform\/classic:en\/ded01fe6fcc484028c695eebd4c5274b4f895508\/javascript.js"}]},"tiki":{"scripts":[{"url":"\/static\/tiki\/en\/1fd621c214115da84f497cbcf3691b42074a4261\/javascript.js","id":"tiki:en\/1fd621c214115da84f497cbcf3691b42074a4261\/javascript.js"}]}},"depends":["tiki","tiki\/platform\/classic"],"scripts":[{"url":"\/static\/tiki\/system\/en\/d957ea8a3f571914826aca7f16e15f122ab3ce87\/javascript.js","id":"tiki\/system:en\/d957ea8a3f571914826aca7f16e15f122ab3ce87\/javascript.js"}]});

/* >>>>>>>>>> BEGIN source/core.js */
tiki.module('tiki/system:core',"var T_ERROR,T_OBJECT,T_NULL,T_CLASS,T_HASH,T_FUNCTION,T_UNDEFINED,T_NUMBER,T_BOOL,T_ARRAY,T_STRING,T_BOOLEAN,YES,NO,isArray,typeOf,A,generateGuid,guidFor,mixin,setupDisplayNames;\/\/ ==========================================================================\n\/\/ Project:   SproutCore System Package\n\/\/ Copyright: \u00a92009 Apple Inc.\n\/\/ ==========================================================================\n\/*globals T_ERROR T_OBJECT T_NULL T_CLASS T_HASH T_FUNCTION T_NUMBER T_BOOL T_ARRAY T_UNDEFINED T_STRING YES NO isArray typeOf A generateGuid guidFor *\/\n\n\"export T_ERROR T_OBJECT T_NULL T_CLASS T_HASH T_FUNCTION T_UNDEFINED T_NUMBER T_BOOL T_ARRAY T_STRING T_BOOLEAN\";\n\"export YES NO isArray typeOf A generateGuid guidFor mixin setupDisplayNames\";\n\n\/\/ define standard type constants\nT_ERROR     = 'error';\nT_OBJECT    = 'object';\nT_NULL      = 'null';\nT_CLASS     = 'class';\nT_HASH      = 'hash';\nT_FUNCTION  = 'function';\nT_UNDEFINED = 'undefined';\nT_NUMBER    = 'number';\nT_BOOL      = 'boolean';\nT_ARRAY     = 'array';\nT_STRING    = 'string';\nT_BOOLEAN   = 'boolean';\n\nYES         = true;\nNO          = false; \n\n\/**\n  Returns true if the passed item is an array.  Works regardless of source\n  of array.\n*\/\nisArray = function(obj) {\n  if (obj && obj.isArray) return true; \/\/ fast path\n  if (!obj) return false;\n  if (T_UNDEFINED !== typeof obj.length) {\n    if ((typeof obj !== T_FUNCTION) && (typeof obj !== T_STRING) && (obj.constructor !== String)) return true;\n  }\n  \/\/ TODO: add proper check that works across windows...\n  return false ;  \n};\n\nArray.prototype.isArray = true ;\n  \n\/**\n  Returns a consistant type for the passed item.\n\n  Use this instead of the built-in typeOf() to get the type of an item. \n  It will return the same result across all browsers and includes a bit \n  more detail.  Here is what will be returned:\n\n  | Return Value Constant | Meaning |\n  | SC.T_STRING | String primitive |\n  | SC.T_NUMBER | Number primitive |\n  | SC.T_BOOLEAN | Boolean primitive |\n  | SC.T_NULL | Null value |\n  | SC.T_UNDEFINED | Undefined value |\n  | SC.T_FUNCTION | A function |\n  | SC.T_ARRAY | An instance of Array |\n  | SC.T_CLASS | A SproutCore class (created using SC.Object.extend()) |\n  | SC.T_OBJECT | A SproutCore object instance |\n  | SC.T_HASH | A JavaScript object not inheriting from SC.Object |\n\n  @param item {Object} the item to check\n  @returns {String} the type\n*\/  \ntypeOf = function typeOf(item) {\n  if (item === undefined) return T_UNDEFINED ;\n  if (item === null) return T_NULL ; \n  \n  var ret = typeof(item) ;\n  if (ret == \"object\") {\n    if (isArray(item)) ret = T_ARRAY ;\n    else if (item instanceof Function) {\n      ret = item.isClass ? T_CLASS : T_FUNCTION ;\n    } else if ((item instanceof Error) || item.isError) ret = T_ERROR;\n    else if (item.isObject) ret = T_OBJECT ;\n    else if (item.isClass) ret = T_CLASS;\n    else if (item.constructor === Object) ret = T_HASH;\n    else if (item.constructor === Number) ret = T_NUMBER;\n    else if (item.constructor === String) ret = T_STRING;\n    else ret = T_OBJECT;\n\n  } else if (ret === T_FUNCTION) ret = item.isClass ? T_CLASS : T_FUNCTION;\n  \n  return ret ;\n};\n  \n\/**\n  Converts the passed object to an Array.  If the object appears to be \n  array-like, a new array will be cloned from it.  Otherwise, a new array\n  will be created with the item itself as the only item in the array.\n  \n  @param object {Object} any enumerable or array-like object.\n  @returns {Array} Array of items\n*\/\nA = function A(obj) {\n  \/\/ null or undefined -- fast path\n  if ((obj === null) || (obj === undefined)) return [] ;\n  \n  \/\/ primitive -- fast path\n  if (obj.slice instanceof Function) {\n    \/\/ do we have a string?\n    if (typeof(obj) === 'string') return [obj] ;\n    else return obj.slice() ;\n  }\n  \n  \/\/ enumerable -- fast path\n  if (obj.toArray) return obj.toArray() ;\n  \n  \/\/ if not array-like, then just wrap in array.\n  if (!isArray(obj)) return [obj];\n  \n  \/\/ when all else fails, do a manual convert...\n  var ret = [], len = obj.length;\n  while(--len >= 0) ret[len] = obj[len];\n  return ret ;\n};\n  \nvar guidKey = \"_sc_guid_\" + new Date().getTime();\nvar _nextGUID = 0, _numberGuids = [], _stringGuids = [];\n\n\/**\n  Generates a new guid, optionally saving the guid to the object that you\n  pass in.  You will rarely need to use this method.  Instead you should\n  call SC.guidFor(obj), which return an existing guid if available.\n\n  @param {Object} obj the object to assign the guid to\n  @returns {String} the guid\n*\/\ngenerateGuid = function generateGuid(obj) { \n  var ret = (\"sc\" + (_nextGUID++)); \n  if (obj) obj[guidKey] = ret ;\n  return ret ;\n};\n\n\/**\n  Returns a unique GUID for the object.  If the object does not yet have\n  a guid, one will be assigned to it.  You can call this on any object,\n  SC.Object-based or not, but be aware that it will add a _guid property.\n\n  You can also use this method on DOM Element objects.\n\n  @param obj {Object} any object, string, number, Element, or primitive\n  @returns {String} the unique guid for this instance.\n*\/\nguidFor = function guidFor(obj) {\n  \n  \/\/ special cases where we don't want to add a key to object\n  if (obj === undefined) return \"(undefined)\" ;\n  if (obj === null) return '(null)' ;\n  if (obj === Object) return '(Object)';\n  if (obj === Array) return '(Array)';\n  \n  if (obj[guidKey]) return obj[guidKey] ;\n\n  switch(typeof obj) {\n    case T_NUMBER:\n      return (_numberGuids[obj] = _numberGuids[obj] || (\"nu\" + obj));\n    case T_STRING:\n      return (_stringGuids[obj] = _stringGuids[obj] || (\"st\" + obj));\n    case T_BOOL:\n      return obj ? \"(true)\" : \"(false)\" ;\n    default:\n      return generateGuid(obj);\n  }\n};\n\n\/**\n  Mixin the passed properties onto the first parameter.  This is a convenient\n  way to add properties to an object.\n  \n  @param {Hash} t the target object to mixin to\n  @param {Hash..} one or more hashes to mix in\n  @returns {Hash} the first parameter\n*\/\nmixin = function mixin(t) {\n  \n  \/\/ copy reference to target object\n  var len    = arguments.length,\n      target = arguments[0] || {},\n      idx, options, key, src, copy;\n\n  for (idx=1; idx < len; idx++ ) {\n    if (!(options = arguments[idx])) continue ;\n    for(key in options) {\n      if (!options.hasOwnProperty(key)) continue ;\n\n      src  = target[key];\n      copy = options[key] ;\n      if (target===copy) continue ; \/\/ prevent never-ending loop\n      if (copy !== undefined) target[key] = copy ;\n    }\n  }\n  \n  return target;\n};\n\nvar TMP_ARY = [];\n\n\/**\n  Iterate over a property, setting display names on functions as needed. \n*\/\nsetupDisplayNames = function setupDisplayNames(obj, root) {\n  var a = TMP_ARY;\n  a[0] = root;\n  \n  var k,v;\n  for(k in obj) {\n    if (!obj.hasOwnProperty(k)) continue ;\n    v = obj[k];\n    if ('function' === typeof v) {\n      a[1] = k;\n      v.displayName = a.join('.');\n    }\n  }\n  \n  a.length = 0;\n  return obj;\n};;exports.T_ERROR = T_ERROR;\nexports.T_OBJECT = T_OBJECT;\nexports.T_NULL = T_NULL;\nexports.T_CLASS = T_CLASS;\nexports.T_HASH = T_HASH;\nexports.T_FUNCTION = T_FUNCTION;\nexports.T_UNDEFINED = T_UNDEFINED;\nexports.T_NUMBER = T_NUMBER;\nexports.T_BOOL = T_BOOL;\nexports.T_ARRAY = T_ARRAY;\nexports.T_STRING = T_STRING;\nexports.T_BOOLEAN = T_BOOLEAN;\nexports.YES = YES;\nexports.NO = NO;\nexports.isArray = isArray;\nexports.typeOf = typeOf;\nexports.A = A;\nexports.generateGuid = generateGuid;\nexports.guidFor = guidFor;\nexports.mixin = mixin;\nexports.setupDisplayNames = setupDisplayNames;\n");
/* >>>>>>>>>> BEGIN source/lib/event.js */
tiki.module('tiki/system:lib/event',"var $m__ = require('tiki\/system:core'), T_ERROR=$m__.T_ERROR,T_OBJECT=$m__.T_OBJECT,T_NULL=$m__.T_NULL,T_CLASS=$m__.T_CLASS,T_HASH=$m__.T_HASH,T_FUNCTION=$m__.T_FUNCTION,T_UNDEFINED=$m__.T_UNDEFINED,T_NUMBER=$m__.T_NUMBER,T_BOOL=$m__.T_BOOL,T_ARRAY=$m__.T_ARRAY,T_STRING=$m__.T_STRING,T_BOOLEAN=$m__.T_BOOLEAN,YES=$m__.YES,NO=$m__.NO,isArray=$m__.isArray,typeOf=$m__.typeOf,A=$m__.A,generateGuid=$m__.generateGuid,guidFor=$m__.guidFor,mixin=$m__.mixin,setupDisplayNames=$m__.setupDisplayNames;var $m__ = require('tiki\/system:lib\/invocation'), Invocation=$m__.Invocation;var $m__ = require('tiki\/system:lib\/platform'), PLATFORM=$m__.PLATFORM,PLATFORM_PACKAGE=$m__.PLATFORM_PACKAGE,info=$m__.info,env=$m__.env;var ready,unload;\/\/ ==========================================================================\n\/\/ Project:   Tiki\n\/\/ Copyright: \u00a92009 Apple Inc.\n\/\/ ==========================================================================\n\/*globals ready unload Invocation *\/\n\n\"import core\";\n\"import lib\/invocation\";\n\"import lib\/platform\";\n\"export package ready unload\";\n\nvar platform = require(PLATFORM_PACKAGE);\n\n\/\/ ..........................................................\n\/\/ READY HANDLER\n\/\/ \n\n\/\/ called when the document becomes ready.  work through the queue...\nfunction _ready() {\n  var queue = ready.queue, \n      mainQ = ready.mainQ,\n      len   = queue.length,\n      inv, idx;\n\n  ready.isReady = ready.scheduled = YES ;\n  ready.queue = []; \/\/ ok to alloc since it is usually only called once\n  ready.mainQ = [];\n  \n  for(idx=0;idx<len;idx++) {\n    inv = queue[idx];\n    inv.invoke();\n    inv.release(); \/\/ return to pool\n  }\n\n  \/\/ after calling ready handlers, invoke any main functions to start the app\n  len = mainQ.length;\n  for(idx=0;idx<len;idx++) {\n    inv = mainQ[idx];\n    inv.invoke();\n    inv.release(); \/\/ return to pool\n  }\n};\n\n\/**\n  Call to register methods you want run when the system is ready for the app\n  to run.\n*\/\nready = function(target, method, args) {\n  if (ready.isReady) {\n    Invocation.invoke(target, method, arguments, 2);\n    \n  } else {\n    if (!ready.scheduled && platform) platform.ready(_ready);\n    ready.scheduled = YES ;\n    ready.queue.push(Invocation.create(target, method, arguments, 2));\n  }\n  \n  return this ;\n};\n\nready.isReady = NO ;\nready.queue   = [] ;\nready.scheduled = NO ;\nready.mainQ   = [] ; \/\/ invocations for main. called after ready\n\nready.main = function(target, method, args) {\n  if (ready.isReady) {\n    Invocation.invoke(target, method, arguments, 2);\n    \n  } else {\n    if (!ready.scheduled && platform) platform.ready(_ready);\n    ready.scheduled = YES ;\n    ready.mainQ.push(Invocation.create(target, method, arguments, 2));\n  }\n  \n  return this ;\n};\n\n\/\/ ..........................................................\n\/\/ UNLOAD HANDLER\n\/\/ \n\nfunction _unload() {\n  var queue = unload.queue, \n      len   = queue.length,\n      inv, idx;\n\n  unload.isUnloaded = unload.scheduled = YES ;\n  unload.queue = [];\n  \n  for(idx=0;idx<len;idx++) {\n    inv = queue[idx];\n    inv.invoke();\n    inv.release(); \/\/ return to pool\n  }\n}\n\n\/**\n  Call to register methods you want run when the system is about to unload.\n*\/\nunload = function(target, method, args) {\n  if (unload.isUnloaded) {\n    Invocation.invoke(target, method, arguments, 2);\n    \n  } else {\n    if (!unload.scheduled && platform) platform.unload(_unload);\n    unload.scheduled = YES;\n    unload.queue.push(Invocation.create(target, method, arguments, 2));\n  }\n  \n  return this ;\n};\n\nunload.isUnloaded = NO;\nunload.queue      = [];\nunload.scheduled  = NO;\n;exports.ready = ready;\nexports.unload = unload;\n");
/* >>>>>>>>>> BEGIN source/lib/global.js */
tiki.module('tiki/system:lib/global',"var global,reset;\/\/ ==========================================================================\n\/\/ Project:   Tiki\n\/\/ Copyright: \u00a92009 Apple Inc.\n\/\/ ==========================================================================\n\n\"export package global\";\n\"export reset\";\n\n\/\/ exports a global object representing the current global.  you can reset \n\/\/ the global by importing this module directly.\n\n\/\/ if you have env.global, use that\nreset = function() {\n  global = require.env.global || {} ;\n};\n\nreset();\n;exports.global = global;\nexports.reset = reset;\n");
/* >>>>>>>>>> BEGIN source/lib/invocation.js */
tiki.module('tiki/system:lib/invocation',"var $m__ = require('tiki\/system:core'), T_ERROR=$m__.T_ERROR,T_OBJECT=$m__.T_OBJECT,T_NULL=$m__.T_NULL,T_CLASS=$m__.T_CLASS,T_HASH=$m__.T_HASH,T_FUNCTION=$m__.T_FUNCTION,T_UNDEFINED=$m__.T_UNDEFINED,T_NUMBER=$m__.T_NUMBER,T_BOOL=$m__.T_BOOL,T_ARRAY=$m__.T_ARRAY,T_STRING=$m__.T_STRING,T_BOOLEAN=$m__.T_BOOLEAN,YES=$m__.YES,NO=$m__.NO,isArray=$m__.isArray,typeOf=$m__.typeOf,A=$m__.A,generateGuid=$m__.generateGuid,guidFor=$m__.guidFor,mixin=$m__.mixin,setupDisplayNames=$m__.setupDisplayNames;var $m__ = require('tiki\/system:mixins\/retainable'), Retainable=$m__.Retainable;var Invocation;\/\/ ==========================================================================\n\/\/ Project:   Tiki\n\/\/ Copyright: \u00a92009 Apple Inc.\n\/\/ ==========================================================================\n\/*globals Invocation *\/\n\n\"import core\";\n\"import mixins\/retainable\";\n\"export package Invocation\";\n\n\/**\n  An Invocation captures a target, method and zero or more arguments to be \n  called at a later time.  Invocations are retainable so you can control when\n  they are destroyed.\n  \n  Note that if you pass a string to the method param, this string will not \n  be resolved to a function until you actually invoke it.  This means you can\n  potentially swap out the method between invocations.\n  \n  @since SproutCore 1.1\n*\/\nInvocation = function Invocation(target, method, args) {\n  return this.init(target, method, args);\n};\n\nmixin(Invocation.prototype, Retainable, {\n  \n  constructor: Invocation,\n  \n  \/**\n    Initializes the invocation.  This is called when you first create the \n    invocation.\n  *\/\n  init: function(target, method, args, ignore) {\n    if (args && (ignore !== undefined)) {\n      if (args.length>ignore) args = Array.prototype.slice.call(args, ignore);\n      else args = null; \/\/ nothing to curry\n    }\n    \n    this.inPool = NO; \/\/ for debug\n    this.target = target;\n    this.method = method;\n    this.args   = args;\n    return this ;\n  },\n  \n  \/**\n    Destroys the invocation.  Called when the retain count hits zero.  This\n    will return the invocation to the pool.\n  *\/\n  destroy: function() {\n    \/\/ reset retainable\n    this.isDestroyed = NO;\n    this.retainCount = 1;\n    this.inPool      = YES;\n    this.target = this.method = this.args = null;\n    pool.push(this); \/\/ add back to pool\n    \n    return this ;\n  },\n  \n  \/**\n    Invokes the method.  Any passed arguments will be curried onto existing\n    arguments.\n    \n    @returns {Object} return value of invoked method.\n  *\/\n  invoke: function() {\n    return Invocation.invoke(this.target, this.method, this.args, undefined, arguments);\n  }\n  \n});\n\nvar pool = [];\n\n\/**\n  Creates a new invocation.  This method will use a memory pool if possible to\n  avoid allocing memory.\n  \n  @param {Object} target target to invoke\n  @param {Function|String} method function or name of method to invoke\n  @param {Array} args zero or more arguments.  optional\n  @param {Number} ignore if passed, ignores this many items from the args\n  @returns {Invocation} new instance\n*\/\nInvocation.create = function(target, method, args, ignore) {\n  if (pool.length>0) return pool.pop().init(target,method,args,ignore);\n  else return new Invocation(target, method, args, ignore);\n};\n\n\/**\n  Invokes the passed target, method, and arguments.  This is an optimized \n  version that may not actually create an invocation.\n*\/\n\/**\n  Invokes the invocation.  Return value of invocation is returned.  Any \n  additional arguments will be curried onto the end of any existing args.\n*\/\nInvocation.invoke = function(target, method, args, ignore, extra) {\n\n  \/\/ normalize method\n  if (typeOf(method) === T_STRING) method = target[method];\n  if (!method) throw(\"Invocation: method \" + this.method + \" not defined\");\n  \n  \/\/ normalize arguments - also curry any extra arguments\n  if ((ignore !== undefined) && args) {\n    if (args.length>ignore) args = Array.prototype.slice.call(args, ignore);\n    else args = null;\n  }\n  if (extra && extra.length>0) args = args ? args.concat(extra) : extra;\n  \n  \/\/ and finally invoke\n  return args ? method.apply(target, args) : method.call(target);\n};\n\n;exports.Invocation = Invocation;\n");
/* >>>>>>>>>> BEGIN source/lib/logger.js */
tiki.module('tiki/system:lib/logger',"var core = require('tiki\/system:core');var $m__ = require('tiki\/system:lib\/platform'), PLATFORM=$m__.PLATFORM,PLATFORM_PACKAGE=$m__.PLATFORM_PACKAGE,info=$m__.info,env=$m__.env;var Logger,console;\/\/ ==========================================================================\n\/\/ Project:   Tiki\n\/\/ Copyright: \u00a92009 Apple Inc.\n\/\/ ==========================================================================\n\/*globals Logger console core *\/\n\n\"import core as core\";\n\"import lib\/platform\";\n\"export package Logger console\";\n\n\/** \n  @file\n\n  Module defines generic functions that can be used to log output into a \n  string or file.\n  \n  The Logger class defined in this module implements the generic Logger API,\n  which includes warn(), info(), error(), debug() and log().  \n  \n  The console object logs this to a system-provided console (on browsers this\n  is equivalent to the system-provided console).\n\n  @since Tiki 1.0\n*\/\n\n\/\/ get the platform console\nvar pconsole = require(PLATFORM_PACKAGE).console;\n\n\/**\n  Logger class defines a standard logger.  If you attach a console, then the\n  logger will record its own results AND forward any output to the console \n  itself.  \n  \n  A default logger is usually created and exported as \"console\" that will \n  attach to the platform console.  You can create a new logger if you want as \n  well.\n\n  @since Tiki 1.0\n*\/\nLogger = function Logger(id, console) {\n  this.id = id ;\n  this.console = console;\n  return this ;\n};\n\nvar COLON = ': ', NEWLINE = \"\\n\", TILDA = ' ~ ';\n\nLogger.prototype = {\n  \n  \/**\n    Emits count number of lines (or 100 lines if not specified) as a single\n    string.\n    \n    @property {Number} count number of lines to display. optional\n    @returns {String} logged output\n  *\/\n  tail: function(count) {\n    var lines = this._lines,\n        len   = lines ? lines.length : 0,\n        idx, ret;\n\n    if (len===0) return '';\n\n    if (count === undefined) count = 100;\n    ret = [];\n    for(idx = Math.max(0, len - count); idx < len; idx++) {\n      \n      if (this.id) {\n        ret.push(this.id);\n        ret.push(TILDA);\n      }\n      if (lines[idx].kind) {\n        ret.push(lines[idx].kind);\n        ret.push(COLON);\n      }\n      ret.push(lines[idx].message);\n      ret.push(NEWLINE);      \n    }\n    \n    return ret.join(\"\");\n  },\n  \n  \/**\n    Clears the log contents\n    \n    @returns {Logger} reciever\n  *\/\n  clear: function() {\n    this._lines = null;\n    return this ;\n  },\n  \n  \/**\n    Adds a line to the log.  Pass the log type and message.\n    \n    @param {String} kind kind of log message.  should be WARN, DBEUG, or null\n    @param {String} message message to log\n    @returns {Logger} reciever\n  *\/\n  push: function(kind, message) {\n    var lines = this._lines;\n    if (!lines) lines = this._lines = [];\n    lines.push({ kind: kind, message: message });\n  },\n  \n  \/**\n    Logs a debug statement.\n    \n    @param {String} msg one or more items to log\n    @returns {void} \n  *\/\n  debug: function(msg) {\n    this.push('DEBUG', core.A(arguments));\n    \n    var console = this.console;\n    if (console && console.debug) console.debug.apply(console, arguments);\n  },\n  \n  \/**\n    Logs an info statement.\n    \n    @param {String} msg one or more items to log\n    @returns {void}\n  *\/\n  info: function(msg) {\n    this.push('INFO', core.A(arguments));\n    \n    var console = this.console;\n    if (console && console.info) console.info.apply(console, arguments);\n  },\n  \n  \/**\n    Logs a warning\n    \n    @param {String} msg one or more items to log\n    @returns {void}\n  *\/\n  warn: function(msg) {\n    this.push('WARN', core.A(arguments));\n    \n    var console = this.console;\n    if (console && console.warn) console.warn.apply(console, arguments);\n  },\n  \n  \/**\n    Logs an error.\n    \n    @param {String} msg one or mroe items to log\n    @returns {void}\n  *\/\n  error: function(msg) {\n    this.push('ERROR', core.A(arguments));\n    \n    var console = this.console;\n    if (console && console.error) console.error.apply(console, arguments);\n  },\n  \n  \/**\n    Begins a group stack.\n  *\/\n  group: function(groupName) {\n    this.push('GROUP', groupName);\n    \n    var console = this.console;\n    if (console && console.group) console.group(groupName);\n  },\n  \n  \/**\n    Ends a group stack\n  *\/\n  groupEnd: function(groupName) {\n    this.push('','');\n    \n    var console = this.console;\n    if (console && console.groupEnd) console.groupEnd(groupName);\n  },\n  \n  toString: function() {\n    var len = this._lines ? this._lines.length : 0;\n    return \"Logger<id=\" + this.id + \" size=\" + len + \">\";\n  }\n    \n};\nLogger.prototype.log = Logger.prototype.info;\n\nconsole = new Logger('console', pconsole);\n;exports.Logger = Logger;\nexports.console = console;\n");
/* >>>>>>>>>> BEGIN source/lib/platform.js */
tiki.module('tiki/system:lib/platform',function(require,exports,module){var PLATFORM,PLATFORM_PACKAGE,info,env;// ==========================================================================
// Project:   Tiki
// Copyright: ©2009 Apple Inc.
// ==========================================================================

"export package PLATFORM PLATFORM_PACKAGE info env";
"use factory_format function";

/**
  @file

  The platform framework implements low-level modules for the current target
  platform.  To access a platform-specific module, use the require method 
  exported by this package like so:
  
  {{{
    sys = require('system');
    mod = require('module', sys.PLATFORM_PACKAGE);
  }}}
  
*/

/**
  Name of the package for the current platform.  Computed from the current
  global env hash passed in to tiki.setup().  You must include either a 
  platform name or a platformPackage property that describes the package 
  exactly.
  
  @property {String}
*/
PLATFORM_PACKAGE = null;

/**
  Name of the current platform.
  
  @property {String}
*/
PLATFORM = platform = 'unknown';

// compute the default platform package name
env = require.env;
if (env) {
  platform = PLATFORM = env.PLATFORM || env.platform;
  PLATFORM_PACKAGE = env.PLATFORM_PACKAGE || env.platformPackage;
  if (!PLATFORM_PACKAGE && platform) {
    PLATFORM_PACKAGE = 'platform/' + platform;
  }
}

// get the info for the current platform
info = require(PLATFORM_PACKAGE).info;
;exports.PLATFORM = PLATFORM;
exports.PLATFORM_PACKAGE = PLATFORM_PACKAGE;
exports.info = info;
exports.env = env;
});
/* >>>>>>>>>> BEGIN source/lib/reactor.js */
tiki.module('tiki/system:lib/reactor',"var $m__ = require('tiki\/system:core'), T_ERROR=$m__.T_ERROR,T_OBJECT=$m__.T_OBJECT,T_NULL=$m__.T_NULL,T_CLASS=$m__.T_CLASS,T_HASH=$m__.T_HASH,T_FUNCTION=$m__.T_FUNCTION,T_UNDEFINED=$m__.T_UNDEFINED,T_NUMBER=$m__.T_NUMBER,T_BOOL=$m__.T_BOOL,T_ARRAY=$m__.T_ARRAY,T_STRING=$m__.T_STRING,T_BOOLEAN=$m__.T_BOOLEAN,YES=$m__.YES,NO=$m__.NO,isArray=$m__.isArray,typeOf=$m__.typeOf,A=$m__.A,generateGuid=$m__.generateGuid,guidFor=$m__.guidFor,mixin=$m__.mixin,setupDisplayNames=$m__.setupDisplayNames;var $m__ = require('tiki\/system:lib\/platform'), PLATFORM=$m__.PLATFORM,PLATFORM_PACKAGE=$m__.PLATFORM_PACKAGE,info=$m__.info,env=$m__.env;var $m__ = require('tiki\/system:lib\/invocation'), Invocation=$m__.Invocation;var schedule,cancel,reactor;\/\/ ==========================================================================\n\/\/ Project:   Tiki\n\/\/ Copyright: \u00a92009 Apple Inc.\n\/\/ ==========================================================================\n\/*globals reactor PLATFORM_PACKAGE schedule reactor cancel Invocation exports *\/\n\n\"import core lib\/platform lib\/invocation\";\n\"export schedule cancel\";\n\"export package reactor\";\n\nvar platform = require(PLATFORM_PACKAGE);\n\n\/\/ internal method fires and invocation and cleans up\nfunction _invoke() {\n  this.invoke();\n  this._timer = this._repeats = null;\n  this.release();\n}\n\n\/**\n  Schedules the passed invocation to execute after the specified amount of \n  time.  Returns an invocation object you can use to cancel.\n*\/\nschedule = function(after, target, method, args) {\n  if (!(target instanceof Invocation)) {\n    target = Invocation.create(target, method, arguments, 3);\n  } else target.retain();\n\n  target._timer = platform.timer.schedule(after, target, _invoke);\n  target._repeats = false;\n  return target;\n};\n\n\/**\n  Cancels a scheduled invocation.\n*\/\ncancel = function(inv) {\n  if (inv._timer) platform.timer.cancel(inv._timer, inv._repeats);\n  inv._timer = inv._repeats = null;\n  return inv;\n};\n\n\nreactor = exports;\n\n;exports.schedule = schedule;\nexports.cancel = cancel;\nexports.reactor = reactor;\n");
/* >>>>>>>>>> BEGIN source/mixins/retainable.js */
tiki.module('tiki/system:mixins/retainable',"var Retainable;\/\/ ==========================================================================\n\/\/ Project:   Tiki\n\/\/ Copyright: \u00a92009 Apple Inc.\n\/\/ ==========================================================================\n\/*globals Retainable *\/\n\n\n\"export package Retainable\";\n\n\/**\n  Makes an object retainable.  Retainable objects have a retain count you can\n  increment and decrement.  When the retain count reaches zero, the object is\n  destroyed (by calling destroy).  \n  \n  Use this mixin for objects that need to have their memory carefully \n  controlled (such as events).  This also allows you to write objects that\n  are pooled.\n  \n  @since SproutCore 1.1\n*\/\nRetainable = {\n  \n  \/**\n    Number of objects retaining this object.  When this reaches zero, the\n    object will be destroyed.\n  *\/\n  retainCount: 1,\n  \n  \/**\n    Becomes true when the object is destroyed.\n  *\/\n  isDestroyed: false,\n  \n  \/**\n    Call to retain the object\n    \n    @returns {Object} receiver\n  *\/\n  retain: function() {\n    this.retainCount++;\n    return this ;\n  },\n  \n  \/** \n    Call to release the object.  May cause it to be destroyed.\n    \n    @returns {Object} receiver\n  *\/\n  release: function() {\n    if (--this.retainCount <= 0) this.__destroy();\n    return this;\n  },\n  \n  __destroy: function() {\n    if (!this.isDestroyed) {\n      this.isDestroyed = true;\n      if (this.destroy) this.destroy();\n    }\n  }\n  \n};\n\n\n;exports.Retainable = Retainable;\n");
/* >>>>>>>>>> BEGIN package_exports.js */
tiki.module('tiki/system:index', function(require, exports, module) {
var m;
m = require('tiki/system:lib/event');
exports.ready = m.ready;
exports.unload = m.unload;
m = require('tiki/system:lib/global');
exports.global = m.global;
m = require('tiki/system:lib/invocation');
exports.Invocation = m.Invocation;
m = require('tiki/system:lib/logger');
exports.Logger = m.Logger;
exports.console = m.console;
m = require('tiki/system:lib/platform');
exports.PLATFORM = m.PLATFORM;
exports.PLATFORM_PACKAGE = m.PLATFORM_PACKAGE;
exports.info = m.info;
exports.env = m.env;
m = require('tiki/system:lib/reactor');
exports.reactor = m.reactor;
m = require('tiki/system:mixins/retainable');
exports.Retainable = m.Retainable;
});

; tiki.script('tiki/system:en/d957ea8a3f571914826aca7f16e15f122ab3ce87/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/runtime', {"packages":{"tiki\/system":{"scripts":[{"url":"\/static\/tiki\/system\/en\/d957ea8a3f571914826aca7f16e15f122ab3ce87\/javascript.js","id":"tiki\/system:en\/d957ea8a3f571914826aca7f16e15f122ab3ce87\/javascript.js"}]}},"depends":["tiki\/system"],"scripts":[{"url":"\/static\/sproutcore\/runtime\/en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js","id":"sproutcore\/runtime:en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js"}]});

/* >>>>>>>>>> BEGIN source/license.js */
/*! @license
==========================================================================
SproutCore Costello -- Property Observing Library
Copyright ©2006-2009, Sprout Systems, Inc. and contributors.
Portions copyright ©2008-2009 Apple Inc. All rights reserved.

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 */

"use modules false";

/* >>>>>>>>>> BEGIN source/core.js */
tiki.module('sproutcore/runtime:core',function(require,exports,module){var system = require('tiki/system:index');var SC,SproutCore,YES,NO;// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================
/*global NodeList system */

"require license";
"import package tiki/system as system";
"export package SC SproutCore YES NO";

// ........................................
// GLOBAL CONSTANTS
// 
// Most global constants should be defined inside of the SC namespace.  
// However the following two are useful enough and generally benign enough
// to put into the global object.
YES = true ; 
NO = false ;

// prevent a console.log from blowing things up if we are on a browser that
// does not support it
var UNDEFINED = 'undefined';
if (UNDEFINED === typeof console) console = system.console;
if (UNDEFINED === typeof sc_require) sc_require = function() {};
if (UNDEFINED === typeof sc_resource) sc_resource = function() {} ;

// ........................................
// BOOTSTRAP
// 
// The root namespace and some common utility methods are defined here. The
// rest of the methods go into the mixin defined below.

/**
  @namespace
  
  The SproutCore namespace.  All SproutCore methods and functions are defined
  inside of this namespace.  You generally should not add new properties to
  this namespace as it may be overwritten by future versions of SproutCore.
  
  You can also use the shorthand "SC" instead of "SproutCore".
  
  SproutCore-Base is a framework that provides core functions for SproutCore
  including cross-platform functions, support for property observing and
  objects.  It's focus is on small size and performance.  You can use this 
  in place of or along-side other cross-platform libraries such as jQuery or
  Prototype.
  
  The core Base framework is based on the jQuery API with a number of 
  performance optimizations.
*/
SC = SproutCore = {} ; 

/**
  Register a new key/value pair in the global object.  You must register all
  top-level namespaces in this way for them to be found via bindings etc.
  
  @param {String} key the name to expose.
  @param {Object} value the value to expose
  @returns {Object} SC namespace
*/
SC.global = function(key, value) {
  system.global[key] = value;
  return SC ;
};

/**
  Removes a global object from the top-level namespace.  You should almost 
  never call this.  It is only used for unit testing.
  
  @param {String} key the name to expose.
  @returns {Object} SC namespace
*/
SC.global.remove = function(key) {
  delete system.global[key];
  return SC;
};

SC.global('SC', SC);

/**
  Adds properties to a target object.
  
  Takes the root object and adds the attributes for any additional 
  arguments passed.

  @param target {Object} the target object to extend
  @param properties {Object} one or more objects with properties to copy.
  @returns {Object} the target object.
  @static
*/
SC.mixin = function() {
  // copy reference to target object
  var target = arguments[0] || {};
  var idx = 1;
  var length = arguments.length ;
  var options ;

  // Handle case where we have only one item...extend SC
  if (length === 1) {
    target = this || {};
    idx=0;
  }

  for ( ; idx < length; idx++ ) {
    if (!(options = arguments[idx])) continue ;
    for(var key in options) {
      if (!options.hasOwnProperty(key)) continue ;
      var copy = options[key] ;
      if (target===copy) continue ; // prevent never-ending loop
      if (copy !== undefined) target[key] = copy ;
    }
  }
  
  return target;
} ;

/**
  Adds properties to a target object.  Unlike SC.mixin, however, if the target
  already has a value for a property, it will not be overwritten.
  
  Takes the root object and adds the attributes for any additional 
  arguments passed.

  @param target {Object} the target object to extend
  @param properties {Object} one or more objects with properties to copy.
  @returns {Object} the target object.
  @static
*/
SC.supplement = function() {
  // copy reference to target object
  var target = arguments[0] || {};
  var idx = 1;
  var length = arguments.length ;
  var options ;

  // Handle case where we have only one item...extend SC
  if (length === 1) {
    target = this || {};
    idx=0;
  }

  for ( ; idx < length; idx++ ) {
    if (!(options = arguments[idx])) continue ;
    for(var key in options) {
      if (!options.hasOwnProperty(key)) continue ;
      var src = target[key] ;
      var copy = options[key] ;
      if (target===copy) continue ; // prevent never-ending loop
      if (copy !== undefined  &&  src === undefined) target[key] = copy ;
    }
  }
  
  return target;
} ;

/** 
  Alternative to mixin.  Provided for compatibility with jQuery.
  @function 
*/
SC.extend = SC.mixin ;

// ..........................................................
// CORE FUNCTIONS
// 
// Enough with the bootstrap code.  Let's define some core functions

SC.mixin(/** @scope SC */ {
  
  // ........................................
  // GLOBAL CONSTANTS
  // 
  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',
  
  // ........................................
  // TYPING & ARRAY MESSAGING
  //   

  /**
    Returns a consistant type for the passed item.

    Use this instead of the built-in typeOf() to get the type of an item. 
    It will return the same result across all browsers and includes a bit 
    more detail.  Here is what will be returned:

    | Return Value Constant | Meaning |
    | SC.T_STRING | String primitive |
    | SC.T_NUMBER | Number primitive |
    | SC.T_BOOLEAN | Boolean primitive |
    | SC.T_NULL | Null value |
    | SC.T_UNDEFINED | Undefined value |
    | SC.T_FUNCTION | A function |
    | SC.T_ARRAY | An instance of Array |
    | SC.T_CLASS | A SproutCore class (created using SC.Object.extend()) |
    | SC.T_OBJECT | A SproutCore object instance |
    | SC.T_HASH | A JavaScript object not inheriting from SC.Object |

    @param item {Object} the item to check
    @returns {String} the type
  */  
  typeOf: function(item) {
    if (item === undefined) return SC.T_UNDEFINED ;
    if (item === null) return SC.T_NULL ; 
    var ret = typeof(item) ;
    if (ret == "object") {
      if (item instanceof Array) {
        ret = SC.T_ARRAY ;
      } else if (item instanceof Function) {
        ret = item.isClass ? SC.T_CLASS : SC.T_FUNCTION ;

      // NB: typeOf() may be called before SC.Error has had a chance to load
      // so this code checks for the presence of SC.Error first just to make
      // sure.  No error instance can exist before the class loads anyway so
      // this is safe.
      } else if (SC.Error && (item instanceof SC.Error)) {
        ret = SC.T_ERROR ;        
      } else if (item.isObject === true) {
        ret = SC.T_OBJECT ;
      } else ret = SC.T_HASH ;
    } else if (ret === SC.T_FUNCTION) ret = (item.isClass) ? SC.T_CLASS : SC.T_FUNCTION;
    return ret ;
  },

  /**
    Returns YES if the passed value is null or undefined.  This avoids errors
    from JSLint complaining about use of ==, which can be technically 
    confusing.
    
    @param {Object} obj value to test
    @returns {Boolean}
  */
  none: function(obj) {
    return obj===null || obj===undefined;  
  },

  /**
    Verifies that a value is either null or an empty string.  Return false if
    the object is not a string.
    
    @param {Object} obj value to test
    @returns {Boolean}
  */
  empty: function(obj) {
    return obj===null || obj===undefined || obj==='';
  },
  
  /**
    Returns YES if the passed object is an array or array-like. Instances
    of the NodeList class return NO.

    Unlike SC.typeOf this method returns true even if the passed object is 
    not formally array but appears to be array-like (i.e. has a length 
    property, responds to .objectAt, etc.)

    @param obj {Object} the object to test
    @returns {Boolean} 
  */
  isArray: function(obj) {
    if (obj && obj.objectAt) return YES ; // fast path

    var len = (obj ? obj.length : null), type = SC.typeOf(obj);
    return !(SC.none(len) || (type === SC.T_FUNCTION) || (type === SC.T_STRING) || obj.setInterval) ;
  },

  /**
    Makes an object into an Array if it is not array or array-like already.
    Unlike SC.A(), this method will not clone the object if it is already
    an array.
    
    @param {Object} obj object to convert
    @returns {Array} Actual array
  */
  makeArray: function(obj) {
    return SC.isArray(obj) ? obj : SC.A(obj);
  },
  
  /**
    Converts the passed object to an Array.  If the object appears to be 
    array-like, a new array will be cloned from it.  Otherwise, a new array
    will be created with the item itself as the only item in the array.
    
    @param object {Object} any enumerable or array-like object.
    @returns {Array} Array of items
  */
  A: function(obj) {
    // null or undefined -- fast path
    if (SC.none(obj)) return [] ;
    
    // primitive -- fast path
    if (obj.slice instanceof Function) {
      // do we have a string?
      if (typeof(obj) === 'string') return [obj] ;
      else return obj.slice() ;
    }
    
    // enumerable -- fast path
    if (obj.toArray) return obj.toArray() ;
    
    // if not array-like, then just wrap in array.
    if (!SC.isArray(obj)) return [obj];
    
    // when all else fails, do a manual convert...
    var ret = [], len = obj.length;
    while(--len >= 0) ret[len] = obj[len];
    return ret ;
  },
  
  // ..........................................................
  // GUIDS & HASHES
  // 
  
  guidKey: "_sc_guid_" + new Date().getTime(),

  // Used for guid generation...
  _nextGUID: 0, _numberGuids: [], _stringGuids: {}, _keyCache: {},

  /**
    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.

    You can also use this method on DOM Element objects.

    @param obj {Object} any object, string, number, Element, or primitive
    @returns {String} the unique guid for this instance.
  */
  guidFor: function(obj) {
    
    // special cases where we don't want to add a key to object
    if (obj === undefined) return "(undefined)" ;
    if (obj === null) return '(null)' ;
    if (obj === Object) return '(Object)';
    if (obj === Array) return '(Array)';
    
    var guidKey = this.guidKey ;
    if (obj[guidKey]) return obj[guidKey] ;

    switch(typeof obj) {
      case SC.T_NUMBER:
        return (this._numberGuids[obj] = this._numberGuids[obj] || ("nu" + obj));
      case SC.T_STRING:
        return (this._stringGuids[obj] = this._stringGuids[obj] || ("st" + obj));
      case SC.T_BOOL:
        return (obj) ? "(true)" : "(false)" ;
      default:
        return SC.generateGuid(obj);
    }
  },

  /**
    Returns a key name that combines the named key + prefix.  This is more 
    efficient than simply combining strings because it uses a cache  
    internally for performance.
    
    @param {String} prefix the prefix to attach to the key
    @param {String} key key
    @returns {String} result 
  */
  keyFor: function(prefix, key) {
    var ret, pcache = this._keyCache[prefix];
    if (!pcache) pcache = this._keyCache[prefix] = {}; // get cache for prefix
    ret = pcache[key];
    if (!ret) ret = pcache[key] = prefix + '_' + key ;
    return ret ;
  },

  /**
    Generates a new guid, optionally saving the guid to the object that you
    pass in.  You will rarely need to use this method.  Instead you should
    call SC.guidFor(obj), which return an existing guid if available.

    @param {Object} obj the object to assign the guid to
    @returns {String} the guid
  */
  generateGuid: function(obj) { 
    var ret = ("sc" + (this._nextGUID++)); 
    if (obj) obj[this.guidKey] = ret ;
    return ret ;
  },

  /**
    Returns a unique hash code for the object.  If the object implements
    a hash() method, the value of that method will be returned.  Otherwise,
    this will return the same value as guidFor().  

    Unlike guidFor(), this method allows you to implement logic in your 
    code to cause two separate instances of the same object to be treated as
    if they were equal for comparisons and other functions.

    IMPORTANT:  If you implement a hash() method, it MUST NOT return a 
    number or a string that contains only a number.  Typically hash codes 
    are strings that begin with a "%".

    @param obj {Object} the object
    @returns {String} the hash code for this instance.
  */
  hashFor: function(obj) {
    return (obj && obj.hash && (typeof obj.hash === SC.T_FUNCTION)) ? obj.hash() : this.guidFor(obj) ;
  },
    
  /**
    This will compare the two object values using their hash codes.

    @param a {Object} first value to compare
    @param b {Object} the second value to compare
    @returns {Boolean} YES if the two have equal hash code values.

  */
  isEqual: function(a,b) {
    // shortcut a few places.
    if (a === null) {
      return b === null ;
    } else if (a === undefined) {
      return b === undefined ;

    // finally, check their hash-codes
    } else return this.hashFor(a) === this.hashFor(b) ;
  },
  
  
  /**
   This will compare two javascript values of possibly different types.
   It will tell you which one is greater than the other by returning
   -1 if the first is smaller than the second,
    0 if both are equal,
    1 if the first is greater than the second.
  
   The order is calculated based on SC.ORDER_DEFINITION , if types are different.
   In case they have the same type an appropriate comparison for this type is made.

   @param v {Object} first value to compare
   @param w {Object} the second value to compare
   @returns {NUMBER} -1 if v < w, 0 if v = w and 1 if v > w.

  */
  compare: function (v, w) {
    
    var type1 = SC.typeOf(v);
    var type2 = SC.typeOf(w);
    var type1Index = SC.ORDER_DEFINITION.indexOf(type1);
    var type2Index = SC.ORDER_DEFINITION.indexOf(type2);
    
    if (type1Index < type2Index) return -1;
    if (type1Index > type2Index) return 1;
    
    // ok - types are equal - so we have to check values now
    switch (type1) {
      case SC.T_BOOL:
      case SC.T_NUMBER:
        if (v<w) return -1;
        if (v>w) return 1;
        return 0;

      case SC.T_STRING:
        if (v.localeCompare(w)<0) return -1;
        if (v.localeCompare(w)>0) return 1;
        return 0;

      case SC.T_ARRAY:
        var l = Math.min(v.length,w.length);
        var r = 0;
        var i = 0;
        while (r===0 && i < l) {
          r = arguments.callee(v[i],w[i]);
          if ( r !== 0 ) return r;
          i++;
        }
      
        // all elements are equal now
        // shorter array should be ordered first
        if (v.length < w.length) return -1;
        if (v.length > w.length) return 1;
        // arrays are equal now
        return 0;
        
      case SC.T_OBJECT:
        if (v.constructor.isComparable === YES) return v.constructor.compare(v, w);
        return 0;

      default:
        return 0;
    }
  },
  
  // ..........................................................
  // OBJECT MANAGEMENT
  // 
  
  /** 
    Empty function.  Useful for some operations. 
    
    @returns {Object}
  */
  K: function() { return this; },

  /** 
    Empty array.  Useful for some optimizations.
  
    @property {Array}
  */
  EMPTY_ARRAY: [],

  /**
    Empty hash.  Useful for some optimizations.
  
    @property {Hash}
  */
  EMPTY_HASH: {},

  /**
    Empty range. Useful for some optimizations.
    
    @property {Range}
  */
  EMPTY_RANGE: {start: 0, length: 0},
  
  /**
    Creates a new object with the passed object as its prototype.

    This method uses JavaScript's native inheritence method to create a new 
    object.    

    You cannot use beget() to create new SC.Object-based objects, but you
    can use it to beget Arrays, Hashes, Sets and objects you build yourself.
    Note that when you beget() a new object, this method will also call the
    didBeget() method on the object you passed in if it is defined.  You can
    use this method to perform any other setup needed.

    In general, you will not use beget() often as SC.Object is much more 
    useful, but for certain rare algorithms, this method can be very useful.

    For more information on using beget(), see the section on beget() in 
    Crockford's JavaScript: The Good Parts.

    @param obj {Object} the object to beget
    @returns {Object} the new object.
  */
  beget: function(obj) {
    if (SC.none(obj)) return null ;
    var K = SC.K; K.prototype = obj ;
    var ret = new K();
    K.prototype = null ; // avoid leaks
    if (SC.typeOf(obj.didBeget) === SC.T_FUNCTION) ret = obj.didBeget(ret); 
    return ret ;
  },

  /**
    Creates a clone of the passed object.  This function can take just about
    any type of object and create a clone of it, including primitive values
    (which are not actually cloned because they are immutable).

    If the passed object implements the clone() method, then this function
    will simply call that method and return the result.

    @param object {Object} the object to clone
    @returns {Object} the cloned object
  */
  copy: function(object) {
    var ret = object ;
    
    // fast path
    if (object && object.isCopyable) return object.copy();
    
    switch (SC.typeOf(object)) {
    case SC.T_ARRAY:
      if (object.clone && SC.typeOf(object.clone) === SC.T_FUNCTION) {
        ret = object.clone() ;
      } else ret = object.slice() ;
      break ;

    case SC.T_HASH:
    case SC.T_OBJECT:
      if (object.clone && SC.typeOf(object.clone) === SC.T_FUNCTION) {
        ret = object.clone() ;
      } else {
        ret = {} ;
        for(var key in object) ret[key] = object[key] ;
      }
    }

    return ret ;
  },

  /**
    Returns a new object combining the values of all passed hashes.

    @param object {Object} one or more objects
    @returns {Object} new Object
  */
  merge: function() {
    var ret = {}, len = arguments.length, idx;
    for(idx=0;idx<len;idx++) SC.mixin(ret, arguments[idx]);
    return ret ;
  },

  /**
    Returns all of the keys defined on an object or hash.  This is useful
    when inspecting objects for debugging.

    @param {Object} obj
    @returns {Array} array of keys
  */
  keys: function(obj) {
    var ret = [];
    for(var key in obj) ret.push(key);
    return ret;
  },

  /**
    Convenience method to inspect an object.  This method will attempt to 
    convert the object into a useful string description.
  */
  inspect: function(obj) {
    var v, ret = [] ;
    for(var key in obj) {
      v = obj[key] ;
      if (v === 'toString') continue ; // ignore useless items
      if (SC.typeOf(v) === SC.T_FUNCTION) v = "function() { ... }" ;
      ret.push(key + ": " + v) ;
    }
    return "{" + ret.join(" , ") + "}" ;
  },

  /**
    Returns a tuple containing the object and key for the specified property 
    path.  If no object could be found to match the property path, then 
    returns null.

    This is the standard method used throughout SproutCore to resolve property
    paths.

    @param path {String} the property path
    @param root {Object} optional parameter specifying the place to start
    @returns {Array} array with [object, property] if found or null
  */
  tupleForPropertyPath: function(path, root) {

    // if the passed path is itself a tuple, return it
    if (SC.typeOf(path) === SC.T_ARRAY) return path ;

    // find the key.  It is the last . or first *
    var key ;
    var stopAt = path.indexOf('*') ;
    if (stopAt < 0) stopAt = path.lastIndexOf('.') ;
    key = (stopAt >= 0) ? path.slice(stopAt+1) : path ;

    // convert path to object.
    var obj = this.objectForPropertyPath(path, root, stopAt) ;
    return (obj && key) ? [obj,key] : null ;
  },

  /** 
    Finds the object for the passed path or array of path components.  This is 
    the standard method used in SproutCore to traverse object paths.

    @param path {String} the path
    @param root {Object} optional root object.  global is used otherwise
    @param stopAt {Integer} optional point to stop searching the path.
    @returns {Object} the found object or undefined.
  */
  objectForPropertyPath: function(path, root, stopAt) {

    var loc, nextDotAt, key, max ;

    if (!root) root = system.global ;

    // faster method for strings
    if (SC.typeOf(path) === SC.T_STRING) {
      if (stopAt === undefined) stopAt = path.length ;
      loc = 0 ;
      while((root) && (loc < stopAt)) {
        nextDotAt = path.indexOf('.', loc) ;
        if ((nextDotAt < 0) || (nextDotAt > stopAt)) nextDotAt = stopAt;
        key = path.slice(loc, nextDotAt);
        root = root.get ? root.get(key) : root[key] ;
        loc = nextDotAt+1; 
      }
      if (loc < stopAt) root = undefined; // hit a dead end. :(

    // older method using an array
    } else {

      loc = 0; max = path.length; key = null;
      while((loc < max) && root) {
        key = path[loc++];
        if (key) root = (root.get) ? root.get(key) : root[key] ;
      }
      if (loc < max) root = undefined ;
    }

    return root ;
  },
  
  
  // ..........................................................
  // LOCALIZATION SUPPORT
  // 
  
  /**
    Known loc strings
    
    @property {Hash}
  */
  STRINGS: {},
  
  /**
    This is a simplified handler for installing a bunch of strings.  This
    ignores the language name and simply applies the passed strings hash.
    
    @param {String} lang the language the strings are for
    @param {Hash} strings hash of strings
    @returns {SC} receiver
  */
  stringsFor: function(lang, strings) {
    SC.mixin(SC.STRINGS, strings);
    return this ;
  }
  
  
}); // end mixin

/** @private Aliasn for SC.clone() */
SC.clone = SC.copy ;

/** @private Alias for SC.A() */
SC.$A = SC.A;

/** @private Provided for compatibility with old HTML templates. */
SC.didLoad = SC.K ;

/** @private Used by SC.compare */
SC.ORDER_DEFINITION = [ SC.T_ERROR,
                        SC.T_UNDEFINED,
                        SC.T_NULL,
                        SC.T_BOOL,
                        SC.T_NUMBER,
                        SC.T_STRING,
                        SC.T_ARRAY,
                        SC.T_HASH,
                        SC.T_OBJECT,
                        SC.T_FUNCTION,
                        SC.T_CLASS ];


// ........................................
// FUNCTION ENHANCEMENTS
//

SC.mixin(Function.prototype, 
/** @lends 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')
        
      }) ;
      
    }}}
    
    h2. 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 = SC.$A(arguments) ;
    var guid = SC.guidFor(this) ;
    this.cacheKey = "__cache__" + guid ;
    this.lastSetValueKey = "__lastValue__" + guid ;
    this.isProperty = YES ;
    return this ;
  },
  
  /**
    You can call this method on a computed property to indicate that the 
    property is cacheable (or not cacheable).  By default all computed 
    properties are not cached.  Enabling this feature will allow SproutCore
    to cache the return value of your computed property and to use that
    value until one of your dependent properties changes or until you 
    invoke propertyDidChange() and name the computed property itself.
    
    If you do not specify this option, computed properties are assumed to be
    not cacheable.
    
    @param {Boolean} aFlag optionally indicate cacheable or no, default YES
    @returns {Function} reciever
  */
  cacheable: function(aFlag) {
    this.isProperty = YES ;  // also make a property just in case
    if (!this.dependentKeys) this.dependentKeys = [] ;
    this.isCacheable = (aFlag === undefined) ? YES : aFlag ;
    return this ;
  },
  
  /**
    Indicates that the computed property is volatile.  Normally SproutCore 
    assumes that your computed property is idempotent.  That is, calling 
    set() on your property more than once with the same value has the same
    effect as calling it only once.  
    
    All non-computed properties are idempotent and normally you should make
    your computed properties behave the same way.  However, if you need to
    make your property change its return value everytime your method is
    called, you may chain this to your property to make it volatile.
    
    If you do not specify this option, properties are assumed to be 
    non-volatile. 
    
    @param {Boolean} aFlag optionally indicate state, default to YES
    @returns {Function} receiver
  */
  idempotent: function(aFlag) {
    this.isProperty = YES;  // also make a property just in case
    if (!this.dependentKeys) this.dependentKeys = [] ;
    this.isVolatile = (aFlag === undefined) ? YES : aFlag ;
    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.
    
    @returns {Function} receiver
  */
  observes: function(propertyPaths) { 
    // sort property paths into local paths (i.e just a property name) and
    // full paths (i.e. those with a . or * in them)
    var loc = arguments.length, local = null, paths = null ;
    while(--loc >= 0) {
      var path = arguments[loc] ;
      // local
      if ((path.indexOf('.')<0) && (path.indexOf('*')<0)) {
        if (!local) local = this.localPropertyPaths = [] ;
        local.push(path);
        
      // regular
      } else {
        if (!paths) paths = this.propertyPaths = [] ;
        paths.push(path) ;
      }
    }
    return this ;
  }
  
});

// ..........................................................
// STRING ENHANCEMENT
// 

// Interpolate string. looks for %@ or %@1; to control the order of params.
/**
  Apply formatting options to the string.  This will look for occurrences
  of %@ in your string and substitute them with the arguments you pass into
  this method.  If you want to control the specific order of replacement, 
  you can add a number after the key as well to indicate which argument 
  you want to insert.  

  Ordered insertions are most useful when building loc strings where values
  you need to insert may appear in different orders.

  h3. Examples
  
  {{{
    "Hello %@ %@".fmt('John', 'Doe') => "Hello John Doe"
    "Hello %@2, %@1".fmt('John', 'Doe') => "Hello Doe, John"
  }}}
  
  @param args {Object...} optional arguments
  @returns {String} formatted string
*/
String.prototype.fmt = function() {
  // first, replace any ORDERED replacements.
  var args = arguments;
  var idx  = 0; // the current index for non-numerical replacements
  return this.replace(/%@([0-9]+)?/g, function(s, argIndex) {
    argIndex = (argIndex) ? parseInt(argIndex,0)-1 : idx++ ;
    s =args[argIndex];
    return ((s===null) ? '(null)' : (s===undefined) ? '' : s).toString(); 
  }) ;
};

/**
  Localizes the string.  This will look up the reciever string as a key 
  in the current Strings hash.  If the key matches, the loc'd value will be
  used.  The resulting string will also be passed through fmt() to insert
  any variables.
  
  @param args {Object...} optional arguments to interpolate also
  @returns {String} the localized and formatted string.
*/
String.prototype.loc = function() {
  var str = SC.STRINGS[this] || this;
  return str.fmt.apply(str,arguments) ;
};


  
/**
  Splits the string into words, separated by spaces. Empty strings are
  removed from the results.
  
  @returns {Array} an array of non-empty strings
*/
String.prototype.w = function() { 
  var ary = [], ary2 = this.split(' '), len = ary2.length ;
  for (var idx=0; idx<len; ++idx) {
    var str = ary2[idx] ;
    if (str.length !== 0) ary.push(str) ; // skip empty strings
  }
  return ary ;
};


;exports.SC = SC;
exports.SproutCore = SproutCore;
exports.YES = YES;
exports.NO = NO;
});
/* >>>>>>>>>> BEGIN source/mixins/array.js */
tiki.module('sproutcore/runtime:mixins/array',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:mixins/observable');require('sproutcore/runtime:mixins/enumerable');require('sproutcore/runtime:system/range_observer');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

// note: SC.Observable also enhances array.  make sure we are called after
// SC.Observable so our version of unknownProperty wins.
"import core";
"import mixins/observable";
"import mixins/enumerable";
"import system/range_observer";
"export package";

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.
  
  Unlike SC.Enumerable, this mixin defines methods specifically for 
  collections that provide index-ordered access to their contents.  When you
  are designing code that needs to accept any kind of Array-like object, you
  should use these methods instead of Array primitives because these will 
  properly notify observers of changes to the array. 
  
  Although these methods are efficient, they do add a layer of indirection to
  your application so it is a good idea to use them only when you need the 
  flexibility of using both true JavaScript arrays and "virtual" arrays such
  as controllers and collections.
  
  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 changing the syntax of the property to
  .observes('*myProperty.[]') .
  
  To support SC.Array in your own class, you must override two
  primitives to use it: replace() and objectAt().  
  
  Note that the SC.Array mixin also incorporates the SC.Enumerable mixin.  All
  SC.Array-like objects are also enumerable.
  
  @extends SC.Enumerable
  @since SproutCore 0.9.0
*/
SC.Array = {
  
  /**
    Walk like a duck - use isSCArray to avoid conflicts
  */
  isSCArray: YES,
  
  /**
    @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.enumerableContentDidChange() ;
    
    @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);
  },
  
  /**
    @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.
    
    This property overrides the default property defined in SC.Enumerable.
  */
  '[]': 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.  You can pass either a single index, a start and a length or an
    index set.
    
    If you pass a single index or a start and length that is beyond the 
    length this method will throw an SC.OUT_OF_RANGE_EXCEPTION
    
    @param {Number|SC.IndexSet} start index, start of range, or index set
    @param {Number} length length of passing range
    @returns {Object} receiver
  */
  removeAt: function(start, length) {
    
    var delta = 0, // used to shift range
        empty = [];
    
    if (typeof start === SC.T_NUMBER) {
      
      if ((start < 0) || (start >= this.get('length'))) {
        throw SC.OUT_OF_RANGE_EXCEPTION;
      }
      
      // fast case
      if (length === undefined) {
        this.replace(start,1,empty);
        return this ;
      } else {
        start = SC.IndexSet.create(start, length);
      }
    }
    
    this.beginPropertyChanges();
    start.forEachRange(function(start, length) {
      start -= delta ;
      delta += length ;
      this.replace(start, length, empty); // remove!
    }, this);
    this.endPropertyChanges();
    
    return this ;
  },
    
  /**
    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 ;
  },
  
  /**
    Search the array for the passed set of objects and remove any occurrences
    of the. 
    
    @param {SC.Enumerable} objects the objects to remove
    @returns {SC.Array} receiver
  */
  removeObjects: function(objects) {
    this.beginPropertyChanges();
    objects.forEach(function(obj) { this.removeObject(obj); }, this);
    this.endPropertyChanges();
    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 ;
  },
  
  
  /**
    Add the objects in the passed numerable to the end of the array.  Defers
    notifying observers of the change until all objects are added.
    
    @param {SC.Enumerable} objects the objects to add
    @returns {SC.Array} receiver
  */
  pushObjects: function(objects) {
    this.beginPropertyChanges();
    objects.forEach(function(obj) { this.pushObject(obj); }, this);
    this.endPropertyChanges();
    return this;
  },

  /**
    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 undefined ;
    
    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 undefined ;
    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 ;
  },

  
  /**
    Adds the named objects to the beginning of the array.  Defers notifying
    observers until all objects have been added.
    
    @param {SC.Enumerable} objects the objects to add
    @returns {SC.Array} receiver
  */
  unshiftObjects: function(objects) {
    this.beginPropertyChanges();
    objects.forEach(function(obj) { this.unshiftObject(obj); }, this);
    this.endPropertyChanges();
    return this;
  },
  
  /**  
    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 (!SC.isEqual(ary.objectAt(loc), this.objectAt(loc))) return false ;
    }
    return true ;
  },
  
  /**
    Generates a new array with the contents of the old array, sans any null
    values.
    
    @returns {Array}
  */
  compact: function() { return this.without(null); },
  
  /**
    Generates a new array with the contents of the old array, sans the passed
    value.
    
    @param {Object} value
    @returns {Array}
  */
  without: function(value) {
    if (this.indexOf(value)<0) return this; // value not present.
    var ret = [] ;
    this.forEach(function(k) { 
      if (k !== value) ret[ret.length] = k; 
    }) ;
    return ret ;
  },

  /**
    Generates a new array with only unique values from the contents of the
    old array.
    
    @returns {Array}
  */
  uniq: function() {
    var ret = [] ;
    this.forEach(function(k){
      if (ret.indexOf(k)<0) ret[ret.length] = k;
    });
    return ret ;
  },
  
  rangeObserverClass: SC.RangeObserver,
  
  /**
    Creates a new range observer on the receiver.  The target/method callback
    you provide will be invoked anytime any property on the objects in the 
    specified range changes.  It will also be invoked if the objects in the
    range itself changes also.
    
    The callback for a range observer should have the signature:
    
    {{{
      function rangePropertyDidChange(array, objects, key, indexes, conext)
    }}}
    
    If the passed key is '[]' it means that the object itself changed.
    
    The return value from this method is an opaque reference to the 
    range observer object.  You can use this reference to destroy the 
    range observer when you are done with it or to update its range.
    
    @param {SC.IndexSet} indexes indexes to observe
    @param {Object} target object to invoke on change
    @param {String|Function} method the method to invoke
    @param {Object} context optional context
    @returns {SC.RangeObserver} range observer
  */
  addRangeObserver: function(indexes, target, method, context) {
    var rangeob = this._array_rangeObservers;
    if (!rangeob) rangeob = this._array_rangeObservers = SC.CoreSet.create() ;

    // The first time a range observer is added, cache the current length so
    // we can properly notify observers the first time through
    if (this._array_oldLength===undefined) {
      this._array_oldLength = this.get('length') ;
    }
    
    var C = this.rangeObserverClass ;
    var isDeep = NO; //disable this feature for now
    var ret = C.create(this, indexes, target, method, context, isDeep) ;
    rangeob.add(ret);
    
    // first time a range observer is added, begin observing the [] property
    if (!this._array_isNotifyingRangeObservers) {
      this._array_isNotifyingRangeObservers = YES ;
      this.addObserver('[]', this, this._array_notifyRangeObservers);
    }
    
    return ret ;
  },
  
  /**
    Moves a range observer so that it observes a new range of objects on the 
    array.  You must have an existing range observer object from a call to
    addRangeObserver().
    
    The return value should replace the old range observer object that you
    pass in.
    
    @param {SC.RangeObserver} rangeObserver the range observer
    @param {SC.IndexSet} indexes new indexes to observe
    @returns {SC.RangeObserver} the range observer (or a new one)
  */
  updateRangeObserver: function(rangeObserver, indexes) {
    return rangeObserver.update(this, indexes);
  },

  /**
    Removes a range observer from the receiver.  The range observer must
    already be active on the array.
    
    The return value should replace the old range observer object.  It will
    usually be null.
    
    @param {SC.RangeObserver} rangeObserver the range observer
    @returns {SC.RangeObserver} updated range observer or null
  */
  removeRangeObserver: function(rangeObserver) {
    var ret = rangeObserver.destroy(this);
    var rangeob = this._array_rangeObservers;
    if (rangeob) rangeob.remove(rangeObserver) ; // clear
    return ret ;
  },
  
  /**
    Updates observers with content change.  To support range observers, 
    you must pass three change parameters to this method.  Otherwise this
    method will assume the entire range has changed.
    
    This also assumes you have already updated the length property.
    @param {Number} start the starting index of the change
    @param {Number} amt the final range of objects changed
    @param {Number} delta if you added or removed objects, the delta change
    @returns {SC.Array} receiver
  */
  enumerableContentDidChange: function(start, amt, delta) {
    var rangeob = this._array_rangeObservers, 
        oldlen  = this._array_oldLength,
        newlen, length, changes ;

    this.beginPropertyChanges();    
    this.notifyPropertyChange('length'); // flush caches

    // schedule info for range observers
    if (rangeob && rangeob.length>0) {

      // if no oldLength has been cached, just assume 0
      if (oldlen === undefined) oldlen = 0;    
      this._array_oldLength = newlen = this.get('length');
      
      // normalize input parameters
      // if delta was not passed, assume it is the different between the 
      // new and old length.
      if (start === undefined) start = 0;
      if (delta === undefined) delta = newlen - oldlen ;
      if (delta !== 0 || amt === undefined) {
        length = newlen - start ;
        if (delta<0) length -= delta; // cover removed range as well
      } else {
        length = amt ;
      }
      
      changes = this._array_rangeChanges;
      if (!changes) changes = this._array_rangeChanges = SC.IndexSet.create();
      changes.add(start, length);
    }
    
    this.notifyPropertyChange('[]') ;
    this.endPropertyChanges();
    
    return this ;
  },
  
  /**  @private
    Observer fires whenever the '[]' property changes.  If there are 
    range observers, will notify observers of change.
  */
  _array_notifyRangeObservers: function() {
    var rangeob = this._array_rangeObservers,
        changes = this._array_rangeChanges,
        len     = rangeob ? rangeob.length : 0, 
        idx, cur;
        
    if (len > 0 && changes && changes.length > 0) {
      for(idx=0;idx<len;idx++) rangeob[idx].rangeDidChange(changes);
      changes.clear(); // reset for later notifications
    }
  }
  
} ;

// Add SC.Array to the built-in array before we add SC.Enumerable to SC.Array
// since built-in Array's are already enumerable.
SC.mixin(Array.prototype, SC.Array) ; 
SC.Array = SC.mixin({}, SC.Enumerable, SC.Array) ;

// Add any extra methods to SC.Array that are native to the built-in 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.     
  @param endIndex {Integer} (Optional) index to end the slice at.
  @returns {Array} New array with specified slice
*/
SC.Array.slice = function(beginIndex, endIndex) {
  var ret = []; 
  var length = this.get('length') ;
  if (SC.none(beginIndex)) beginIndex = 0 ;
  if (SC.none(endIndex) || (endIndex > length)) endIndex = length ;
  while(beginIndex < endIndex) ret[ret.length] = this.objectAt(beginIndex++) ;
  return ret ;
}  ;

/**
  Returns the index for a particular object in the index.
  
  @param {Object} object the item to search for
  @param {NUmber} startAt optional starting location to search, default 0
  @returns {Number} index of -1 if not found
*/
SC.Array.indexOf = function(object, startAt) {
  var idx, len = this.get('length');
  
  if (startAt === undefined) startAt = 0;
  else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
  if (startAt < 0) startAt += len;
  
  for(idx=startAt;idx<len;idx++) {
    if (this.objectAt(idx) === object) return idx ;
  }
  return -1;
};

// Some browsers do not support indexOf natively.  Patch if needed
if (!Array.prototype.indexOf) Array.prototype.indexOf = SC.Array.indexOf;

/**
  Returns the last index for a particular object in the index.
  
  @param {Object} object the item to search for
  @param {NUmber} startAt optional starting location to search, default 0
  @returns {Number} index of -1 if not found
*/
SC.Array.lastIndexOf = function(object, startAt) {
  var idx, len = this.get('length');
  
  if (startAt === undefined) startAt = len-1;
  else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
  if (startAt < 0) startAt += len;
  
  for(idx=startAt;idx>=0;idx--) {
    if (this.objectAt(idx) === object) return idx ;
  }
  return -1;
};

// Some browsers do not support lastIndexOf natively.  Patch if needed
if (!Array.prototype.lastIndexOf) {
  Array.prototype.lastIndexOf = SC.Array.lastIndexOf;
}

// ......................................................
// ARRAY SUPPORT
//
// Implement the same enhancements on Array.  We use specialized methods
// because working with arrays are so common.
(function() {
  SC.mixin(Array.prototype, {
    
    // primitive for array support.
    replace: function(idx, amt, objects) {
      if (this.isFrozen) throw SC.FROZEN_ERROR ;
      if (!objects || objects.length === 0) {
        this.splice(idx, amt) ;
      } else {
        var args = [idx, amt].concat(objects) ;
        this.splice.apply(this,args) ;
      }
      
      // if we replaced exactly the same number of items, then pass only the
      // replaced range.  Otherwise, pass the full remaining array length 
      // since everything has shifted
      var len = objects ? (objects.get ? objects.get('length') : objects.length) : 0;
      this.enumerableContentDidChange(idx, amt, len - amt) ;
      return this ;
    },
    
    // If you ask for an unknown property, then try to collect the value
    // from member items.
    unknownProperty: function(key, value) {
      var ret = this.reducedProperty(key, value) ;
      if ((value !== undefined) && ret === undefined) {
        ret = this[key] = value;
      }
      return ret ;
    }
    
  });
    
  // If browser did not implement indexOf natively, then override with
  // specialized version
  var indexOf = Array.prototype.indexOf;
  if (!indexOf || (indexOf === SC.Array.indexOf)) {
    Array.prototype.indexOf = function(object, startAt) {
      var idx, len = this.length;
      
      if (startAt === undefined) startAt = 0;
      else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
      if (startAt < 0) startAt += len;
      
      for(idx=startAt;idx<len;idx++) {
        if (this[idx] === object) return idx ;
      }
      return -1;
    } ; 
  }
  
  var lastIndexOf = Array.prototype.lastIndexOf ;
  if (!lastIndexOf || (lastIndexOf === SC.Array.lastIndexOf)) {
    Array.prototype.lastIndexOf = function(object, startAt) {
      var idx, len = this.length;
      
      if (startAt === undefined) startAt = len-1;
      else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
      if (startAt < 0) startAt += len;
      
      for(idx=startAt;idx>=0;idx--) {
        if (this[idx] === object) return idx ;
      }
      return -1;
    };
  }
  
})();
;});
/* >>>>>>>>>> BEGIN source/mixins/comparable.js */
tiki.module('sproutcore/runtime:mixins/comparable',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"export package";

/**
  @namespace
  
  Implements some standard methods for comparing objects. Add this mixin to
  any class you create that can compare its instances.
  
  You should implement the compare() method.
  
  @since SproutCore 1.0
*/
SC.Comparable = {
  
  /**
    walk like a duck. Indicates that the object can be compared.
    
    @type Boolean
  */
  isComparable: YES,
  
  /**
    Override to return the result of the comparison of the two parameters. The
    compare method should return
      -1 if a < b
       0 if a == b
       1 if a > b
    
    Default implementation raises
    an exception.
    
    @param a {Object} the first object to compare
    @param b {Object} the second object to compare
    @returns {Integer} the result of the comparison
  */
  compare: function(a, b) {
    throw "%@.compare() is not implemented".fmt(this.toString());
  }
  
};
;});
/* >>>>>>>>>> BEGIN source/mixins/copyable.js */
tiki.module('sproutcore/runtime:mixins/copyable',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"export package";

/**
  @namespace
  
  Impelements some standard methods for copying an object.  Add this mixin to
  any object you create that can create a copy of itself.  This mixin is 
  added automatically to the built-in array.
  
  You should generally implement the copy() method to return a copy of the 
  receiver.
  
  Note that frozenCopy() will only work if you also implement SC.Freezable.

  @since SproutCore 1.0
*/
SC.Copyable = {
  
  /**
    Walk like a duck.  Indicates that the object can be copied.
    
    @type Boolean
  */
  isCopyable: YES,
  
  /**
    Override to return a copy of the receiver.  Default implementation raises
    an exception.
    
    @returns {Object} copy of receiver
  */
  copy: function() {
    throw "%@.copy() is not implemented";
  },
  
  /**
    If the object implements SC.Freezable, then this will return a new copy 
    if the object is not frozen and the receiver if the object is frozen.  
    
    Raises an exception if you try to call this method on a object that does
    not support freezing.
    
    You should use this method whenever you want a copy of a freezable object
    since a freezable object can simply return itself without actually 
    consuming more memory.
  
    @returns {Object} copy of receiver or receiver
  */
  frozenCopy: function() {
    var isFrozen = this.get ? this.get('isFrozen') : this.isFrozen;
    if (isFrozen === YES) return this;
    else if (isFrozen === undefined) throw "%@ does not support freezing".fmt(this);
    else return this.copy().freeze();
  }
};

// Make Array copyable
SC.mixin(Array.prototype, SC.Copyable);
Array.prototype.copy = Array.prototype.slice;
;});
/* >>>>>>>>>> BEGIN source/mixins/delegate_support.js */
tiki.module('sproutcore/runtime:mixins/delegate_support',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"export package";

/**
  @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.
  
  @since SproutCore 1.0
  
*/
SC.DelegateSupport = {  
  
  /**
    Selects the delegate that implements the specified method name.  Pass one
    or more delegates.  The receiver is automatically included as a default.
    
    This can be more efficient than using invokeDelegateMethod() which has
    to marshall arguments into a delegate call.
    
    @param {String} methodName
    @param {Object...} delegate one or more delegate arguments
    @returns {Object} delegate or null
  */
  delegateFor: function(methodName) {
    var idx = 1,
        len = arguments.length,
        ret ;
        
    while(idx<len) {
      ret = arguments[idx];
      if (ret && ret[methodName] !== undefined) return ret ;
      idx++;      
    }
    
    return (this[methodName] !== undefined) ? this : null;
  },
  
  /**
    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 {Object...} args (OPTIONAL) any additional arguments
    
    @returns {Object} value returned by delegate
  */
  invokeDelegateMethod: function(delegate, methodName, args) {
    args = SC.A(arguments); args = args.slice(2, args.length) ;
    if (!delegate || !delegate[methodName]) delegate = this ;
    
    var method = delegate[methodName];
    return method ? method.apply(delegate, args) : null;
  },
  
  /**
    Search the named delegates for the passed property.  If one is found, 
    gets the property value and returns it.  If none of the passed delegates 
    implement the property, search the receiver for the property as well.
    
    @param {String} key the property to get.
    @param {Object} delegate one or more delegate
    @returns {Object} property value or undefined
  */
  getDelegateProperty: function(key, delegate) {
    var idx = 1,
        len = arguments.length,
        ret ;
        
    while(idx<len) {
      ret = arguments[idx++];
      if (ret && ret[key] !== undefined) {
        return ret.get ? ret.get(key) : ret[key] ;
      }
    }
    
    return (this[key] !== undefined) ? this.get(key) : undefined ;
  }
  
};
;});
/* >>>>>>>>>> BEGIN source/mixins/enumerable.js */
tiki.module('sproutcore/runtime:mixins/enumerable',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:system/enumerator');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import system/enumerator";
"export package";

/*globals Prototype */

/**
  @namespace

  This mixin defines the common interface implemented by enumerable objects 
  in SproutCore.  Most of these methods follow the standard Array iteration
  API defined up to JavaScript 1.8 (excluding language-specific features that
  cannot be emulated in older versions of JavaScript).
  
  This mixin is applied automatically to the Array class on page load, so you
  can use any of these methods on simple arrays.  If Array already implements
  one of these methods, the mixin will not override them.
  
  h3. Writing Your Own Enumerable

  To make your own custom class enumerable, you need two items:
  
  1. You must have a length property.  This property should change whenever
     the number of items in your enumerable object changes.  If you using this
     with an SC.Object subclass, you should be sure to change the length 
     property using set().
     
  2. If you must implement nextObject().  See documentation.
    
  Once you have these two methods implement, apply the SC.Enumerable mixin
  to your class and you will be able to enumerate the contents of your object
  like any other collection.
  
  h3. Using SproutCore Enumeration with Other Libraries
  
  Many other libraries provide some kind of iterator or enumeration like
  facility.  This is often where the most common API conflicts occur. 
  SproutCore's API is designed to be as friendly as possible with other 
  libraries by implementing only methods that mostly correspond to the
  JavaScript 1.8 API.  
  
  @since SproutCore 1.0
*/
SC.Enumerable = {

  /** 
    Walk like a duck.
    
    @property {Boolean}
  */
  isEnumerable: YES,
  
  /**
    Implement this method to make your class enumerable.
    
    This method will be call repeatedly during enumeration.  The index value
    will always begin with 0 and increment monotonically.  You don't have to
    rely on the index value to determine what object to return, but you should
    always check the value and start from the beginning when you see the
    requested index is 0.
    
    The previousObject is the object that was returned from the last call
    to nextObject for the current iteration.  This is a useful way to 
    manage iteration if you are tracing a linked list, for example.
    
    Finally the context paramter will always contain a hash you can use as 
    a "scratchpad" to maintain any other state you need in order to iterate
    properly.  The context object is reused and is not reset between 
    iterations so make sure you setup the context with a fresh state whenever
    the index parameter is 0.
    
    Generally iterators will continue to call nextObject until the index
    reaches the your current length-1.  If you run out of data before this 
    time for some reason, you should simply return undefined.
    
    The default impementation of this method simply looks up the index.
    This works great on any Array-like objects.
    
    @param index {Number} the current index of the iteration
    @param previousObject {Object} the value returned by the last call to nextObject.
    @param context {Object} a context object you can use to maintain state.
    @returns {Object} the next object in the iteration or undefined   
  */ 
  nextObject: function(index, previousObject, context) {
    return this.objectAt ? this.objectAt(index) : this[index] ;
  },
  
  /**
    Helper method returns the first object from a collection.  This is usually
    used by bindings and other parts of the framework to extract a single 
    object if the enumerable contains only one item.
    
    If you override this method, you should implement it so that it will 
    always return the same value each time it is called.  If your enumerable
    contains only one object, this method should always return that object.
    If your enumerable is empty, this method should return undefined.
    
    @returns {Object} the object or undefined
  */
  firstObject: function() {
    if (this.get('length')===0) return undefined ;
    if (this.objectAt) return this.objectAt(0); // support arrays out of box
    
    // handle generic enumerables
    var context = SC.Enumerator._popContext(), ret;
    ret = this.nextObject(0, null, context);
    context = SC.Enumerator._pushContext(context);  
    return ret ;
  }.property(),
  
  /**
    Returns a new enumerator for this object.  See SC.Enumerator for
    documentation on how to use this object.  Enumeration is an alternative
    to using one of the other iterators described here.
    
    @returns {SC.Enumerator} an enumerator for the receiver
  */
  enumerator: function() { return SC.Enumerator.create(this); },
  
  /**
    Iterates through the enumerable, calling the passed function on each
    item.  This method corresponds to the forEach() method defined in 
    JavaScript 1.6.
    
    The callback method you provide should have the following signature (all
    parameters are optional):
    
    {{{
      function(item, index, enumerable) ;      
    }}}
    
    - *item* is the current item in the iteration.
    - *index* is the current index in the iteration
    - *enumerable* is the enumerable object itself.
    
    Note that in addition to a callback, you can also pass an optional target
    object that will be set as "this" on the context.  This is a good way
    to give your iterator function access to the current object.
    
    @params callback {Function} the callback to execute
    @params target {Object} the target object to use
    @returns {Object} this
  */
  forEach: function(callback, target) {
    if (typeof callback !== "function") throw new TypeError() ;
    var len = this.get ? this.get('length') : this.length ;
    if (target === undefined) target = null;
    
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;idx<len;idx++) {
      var next = this.nextObject(idx, last, context) ;
      callback.call(target, next, idx, this);
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return this ;
  },
  
  /**
    Retrieves the named value on each member object.  This is more efficient
    than using one of the wrapper methods defined here.  Objects that 
    implement SC.Observable will use the get() method, otherwise the property
    will be accessed directly.
    
    @param {String} key the key to retrieve
    @returns {Array} extracted values
  */
  getEach: function(key) {
    return this.map(function(next) {
      return next ? (next.get ? next.get(key) : next[key]) : null;
    }, this);
  },

  /**
    Sets the value on the named property for each member.  This is more
    efficient than using other methods defined on this helper.  If the object
    implements SC.Observable, the value will be changed to set(), otherwise
    it will be set directly.  null objects are skipped.
    
    @param {String} key the key to set
    @param {Object} value the object to set
    @returns {Object} receiver
  */
  setEach: function(key, value) {
    this.forEach(function(next) {
      if (next) {
        if (next.set) next.set(key, value) ;
        else next[key] = value ;
      }
    }, this);
    return this ;
  },
  
  /**
    Maps all of the items in the enumeration to another value, returning 
    a new array.  This method corresponds to map() defined in JavaScript 1.6.
    
    The callback method you provide should have the following signature (all
    parameters are optional):
    
    {{{
      function(item, index, enumerable) ;      
    }}}
    
    - *item* is the current item in the iteration.
    - *index* is the current index in the iteration
    - *enumerable* is the enumerable object itself.
    
    It should return the mapped value.
    
    Note that in addition to a callback, you can also pass an optional target
    object that will be set as "this" on the context.  This is a good way
    to give your iterator function access to the current object.
    
    @params callback {Function} the callback to execute
    @params target {Object} the target object to use
    @returns {Array} The mapped array.
  */
  map: function(callback, target) {
    if (typeof callback !== "function") throw new TypeError() ;
    var len = this.get ? this.get('length') : this.length ;
    if (target === undefined) target = null;
    
    var ret  = [];
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;idx<len;idx++) {
      var next = this.nextObject(idx, last, context) ;
      ret[idx] = callback.call(target, next, idx, this) ;
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },

  /**
    Similar to map, this specialized function returns the value of the named
    property on all items in the enumeration.
    
    @params key {String} name of the property
    @returns {Array} The mapped array.
  */
  mapProperty: function(key) {
    return this.map(function(next) { 
      return next ? (next.get ? next.get(key) : next[key]) : null;
    });
  },

  /**
    Returns an array with all of the items in the enumeration that the passed
    function returns YES for. This method corresponds to filter() defined in 
    JavaScript 1.6.
    
    The callback method you provide should have the following signature (all
    parameters are optional):
    
    {{{
      function(item, index, enumerable) ;      
    }}}
    
    - *item* is the current item in the iteration.
    - *index* is the current index in the iteration
    - *enumerable* is the enumerable object itself.
    
    It should return the YES to include the item in the results, NO otherwise.
    
    Note that in addition to a callback, you can also pass an optional target
    object that will be set as "this" on the context.  This is a good way
    to give your iterator function access to the current object.
    
    @params callback {Function} the callback to execute
    @params target {Object} the target object to use
    @returns {Array} A filtered array.
  */
  filter: function(callback, target) {
    if (typeof callback !== "function") throw new TypeError() ;
    var len = this.get ? this.get('length') : this.length ;
    if (target === undefined) target = null;
    
    var ret  = [];
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;idx<len;idx++) {
      var next = this.nextObject(idx, last, context) ;
      if(callback.call(target, next, idx, this)) ret.push(next) ;
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },

  /** 
    Returns an array sorted by the value of the passed key parameters.
    null objects will be sorted first.  You can pass either an array of keys
    or multiple parameters which will act as key names
    
    @param {String} key one or more key names
    @returns {Array}
  */
  sortProperty: function(key) {
    var keys = (typeof key === SC.T_STRING) ? arguments : key,
        len  = keys.length,
        src;
     
    // get the src array to sort   
    if (this instanceof Array) src = this;
    else {
      src = [];
      this.forEach(function(i) { src.push(i); });
    }
    
    if (!src) return [];
    return src.sort(function(a,b) {
      var idx, key, aValue, bValue, ret = 0;
      
      for(idx=0;ret===0 && idx<len;idx++) {
        key = keys[idx];
        aValue = a ? (a.get ? a.get(key) : a[key]) : null;
        bValue = b ? (b.get ? b.get(key) : b[key]) : null;
        ret = SC.compare(aValue, bValue);
      }
      return ret ;
    });
  },
  

  /**
    Returns an array with just the items with the matched property.  You
    can pass an optional second argument with the target value.  Otherwise
    this will match any property that evaluates to true.
    
    @params key {String} the property to test
    @param value {String} optional value to test against.
    @returns {Array} filtered array
  */
  filterProperty: function(key, value) {
    var len = this.get ? this.get('length') : this.length ;
    var ret  = [];
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;idx<len;idx++) {
      var next = this.nextObject(idx, last, context) ;
      var cur = next ? (next.get ? next.get(key) : next[key]) : null;
      var matched = (value === undefined) ? !!cur : SC.isEqual(cur, value);
      if (matched) ret.push(next) ;
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },
    
  /**
    Returns the first item in the array for which the callback returns YES.
    This method works similar to the filter() method defined in JavaScript 1.6
    except that it will stop working on the array once a match is found.

    The callback method you provide should have the following signature (all
    parameters are optional):

    {{{
      function(item, index, enumerable) ;      
    }}}

    - *item* is the current item in the iteration.
    - *index* is the current index in the iteration
    - *enumerable* is the enumerable object itself.

    It should return the YES to include the item in the results, NO otherwise.

    Note that in addition to a callback, you can also pass an optional target
    object that will be set as "this" on the context.  This is a good way
    to give your iterator function access to the current object.

    @params callback {Function} the callback to execute
    @params target {Object} the target object to use
    @returns {Object} Found item or null.
  */
  find: function(callback, target) {
    if (typeof callback !== "function") throw new TypeError() ;
    var len = this.get ? this.get('length') : this.length ;
    if (target === undefined) target = null;

    var last = null, next, found = NO, ret = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;idx<len && !found;idx++) {
      next = this.nextObject(idx, last, context) ;
      if (found = callback.call(target, next, idx, this)) ret = next ;
      last = next ;
    }
    next = last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },

  /**
    Returns an the first item with a property matching the passed value.  You
    can pass an optional second argument with the target value.  Otherwise
    this will match any property that evaluates to true.
    
    This method works much like the more generic find() method.
    
    @params key {String} the property to test
    @param value {String} optional value to test against.
    @returns {Object} found item or null
  */
  findProperty: function(key, value) {
    var len = this.get ? this.get('length') : this.length ;
    var found = NO, ret = null, last = null, next, cur ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;idx<len && !found;idx++) {
      next = this.nextObject(idx, last, context) ;
      cur = next ? (next.get ? next.get(key) : next[key]) : null;
      found = (value === undefined) ? !!cur : SC.isEqual(cur, value);
      if (found) ret = next ;
      last = next ;
    }
    last = next = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },
      
  /**
    Returns YES if the passed function returns YES for every item in the
    enumeration.  This corresponds with the every() method in JavaScript 1.6.
    
    The callback method you provide should have the following signature (all
    parameters are optional):
    
    {{{
      function(item, index, enumerable) ;      
    }}}
    
    - *item* is the current item in the iteration.
    - *index* is the current index in the iteration
    - *enumerable* is the enumerable object itself.
    
    It should return the YES or NO.
    
    Note that in addition to a callback, you can also pass an optional target
    object that will be set as "this" on the context.  This is a good way
    to give your iterator function access to the current object.
    
    h4. Example Usage
    
    {{{
      if (people.every(isEngineer)) { Paychecks.addBigBonus(); }
    }}}
    
    @params callback {Function} the callback to execute
    @params target {Object} the target object to use
    @returns {Boolean} 
  */
  every: function(callback, target) {
    if (typeof callback !== "function") throw new TypeError() ;
    var len = this.get ? this.get('length') : this.length ;
    if (target === undefined) target = null;
    
    var ret  = YES;
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;ret && (idx<len);idx++) {
      var next = this.nextObject(idx, last, context) ;
      if(!callback.call(target, next, idx, this)) ret = NO ;
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },

  /**
    Returns YES if the passed property resolves to true for all items in the
    enumerable.  This method is often simpler/faster than using a callback.

    @params key {String} the property to test
    @param value {String} optional value to test against.
    @returns {Array} filtered array
  */
  everyProperty: function(key, value) {
    var len = this.get ? this.get('length') : this.length ;
    var ret  = YES;
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;ret && (idx<len);idx++) {
      var next = this.nextObject(idx, last, context) ;
      var cur = next ? (next.get ? next.get(key) : next[key]) : null;
      ret = (value === undefined) ? !!cur : SC.isEqual(cur, value);
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },
  
  
  /**
    Returns YES if the passed function returns true for any item in the 
    enumeration. This corresponds with the every() method in JavaScript 1.6.
    
    The callback method you provide should have the following signature (all
    parameters are optional):
    
    {{{
      function(item, index, enumerable) ;      
    }}}
    
    - *item* is the current item in the iteration.
    - *index* is the current index in the iteration
    - *enumerable* is the enumerable object itself.
    
    It should return the YES to include the item in the results, NO otherwise.
    
    Note that in addition to a callback, you can also pass an optional target
    object that will be set as "this" on the context.  This is a good way
    to give your iterator function access to the current object.
    
    h4. Usage Example
    
    {{{
      if (people.some(isManager)) { Paychecks.addBiggerBonus(); }
    }}}
    
    @params callback {Function} the callback to execute
    @params target {Object} the target object to use
    @returns {Array} A filtered array.
  */
  some: function(callback, target) {
    if (typeof callback !== "function") throw new TypeError() ;
    var len = this.get ? this.get('length') : this.length ;
    if (target === undefined) target = null;
    
    var ret  = NO;
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;(!ret) && (idx<len);idx++) {
      var next = this.nextObject(idx, last, context) ;
      if(callback.call(target, next, idx, this)) ret = YES ;
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },

  /**
    Returns YES if the passed property resolves to true for any item in the
    enumerable.  This method is often simpler/faster than using a callback.

    @params key {String} the property to test
    @param value {String} optional value to test against.
    @returns {Boolean} YES 
  */
  someProperty: function(key, value) {
    var len = this.get ? this.get('length') : this.length ;
    var ret  = NO;
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0; !ret && (idx<len); idx++) {
      var next = this.nextObject(idx, last, context) ;
      var cur = next ? (next.get ? next.get(key) : next[key]) : null;
      ret = (value === undefined) ? !!cur : SC.isEqual(cur, value);
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;  // return the invert
  },

  /**
    This will combine the values of the enumerator into a single value. It 
    is a useful way to collect a summary value from an enumeration.  This
    corresponds to the reduce() method defined in JavaScript 1.8.
    
    The callback method you provide should have the following signature (all
    parameters are optional):
    
    {{{
      function(previousValue, item, index, enumerable) ;      
    }}}
    
    - *previousValue* is the value returned by the last call to the iterator.
    - *item* is the current item in the iteration.
    - *index* is the current index in the iteration
    - *enumerable* is the enumerable object itself.

    Return the new cumulative value.

    In addition to the callback you can also pass an initialValue.  An error
    will be raised if you do not pass an initial value and the enumerator is
    empty.

    Note that unlike the other methods, this method does not allow you to 
    pass a target object to set as this for the callback.  It's part of the
    spec. Sorry.
    
    @params callback {Function} the callback to execute
    @params initialValue {Object} initial value for the reduce
    @params reducerProperty {String} internal use only.  May not be available.
    @returns {Array} A filtered array.
  */
  reduce: function(callback, initialValue, reducerProperty) {
    if (typeof callback !== "function") throw new TypeError() ;
    var len = this.get ? this.get('length') : this.length ;

    // no value to return if no initial value & empty
    if (len===0 && initialValue === undefined) throw new TypeError();
    
    var ret  = initialValue;
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(var idx=0;idx<len;idx++) {
      var next = this.nextObject(idx, last, context) ;
      
      // while ret is still undefined, just set the first value we get as ret.
      // this is not the ideal behavior actually but it matches the FireFox
      // implementation... :(
      if (next !== null) {
        if (ret === undefined) {
          ret = next ;
        } else {
          ret = callback.call(null, ret, next, idx, this, reducerProperty);
        }
      }
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    
    // uh oh...we never found a value!
    if (ret === undefined) throw new TypeError() ;
    return ret ;
  },
  
  /**
    Invokes the named method on every object in the receiver that
    implements it.  This method corresponds to the implementation in
    Prototype 1.6.
    
    @param methodName {String} the name of the method
    @param args {Object...} optional arguments to pass as well.
    @returns {Array} return values from calling invoke.
  */
  invoke: function(methodName) {
    var len = this.get ? this.get('length') : this.length ;
    if (len <= 0) return [] ; // nothing to invoke....
    
    var idx;
    
    // collect the arguments
    var args = [] ;
    var alen = arguments.length ;
    if (alen > 1) {
      for(idx=1;idx<alen;idx++) args.push(arguments[idx]) ;
    }
    
    // call invoke
    var ret = [] ;
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(idx=0;idx<len;idx++) {
      var next = this.nextObject(idx, last, context) ;
      var method = next ? next[methodName] : null ;
      if (method) ret[idx] = method.apply(next, args) ;
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },

  /**
    Invokes the passed method and optional arguments on the receiver elements
    as long as the methods return value matches the target value.  This is 
    a useful way to attempt to apply changes to a collection of objects unless
    or until one fails.

    @param targetValue {Object} the target return value
    @param methodName {String} the name of the method
    @param args {Object...} optional arguments to pass as well.
    @returns {Array} return values from calling invoke.
  */
  invokeWhile: function(targetValue, methodName) {
    var len = this.get ? this.get('length') : this.length ;
    if (len <= 0) return null; // nothing to invoke....

    var idx;

    // collect the arguments
    var args = [] ;
    var alen = arguments.length ;
    if (alen > 2) {
      for(idx=2;idx<alen;idx++) args.push(arguments[idx]) ;
    }
    
    // call invoke
    var ret = targetValue ;
    var last = null ;
    var context = SC.Enumerator._popContext();
    for(idx=0;(ret === targetValue) && (idx<len);idx++) {
      var next = this.nextObject(idx, last, context) ;
      var method = next ? next[methodName] : null ;
      if (method) ret = method.apply(next, args) ;
      last = next ;
    }
    last = null ;
    context = SC.Enumerator._pushContext(context);
    return ret ;
  },
  
  /**
    Simply converts the enumerable into a genuine array.  The order, of
    course, is not gauranteed.  Corresponds to the method implemented by 
    Prototype.
        
    @returns {Array} the enumerable as an array.
  */
  toArray: function() {
    var ret = [];
    this.forEach(function(o) { ret.push(o); }, this);
    return ret ;
  }        
  
} ;

// Build in a separate function to avoid unintential leaks through closures...
SC._buildReducerFor = function(reducerKey, reducerProperty) {
  return function(key, value) {
    var reducer = this[reducerKey] ;
    if (SC.typeOf(reducer) !== SC.T_FUNCTION) {
      return this.unknownProperty ? this.unknownProperty(key, value) : null;
    } else {
      // Invoke the reduce method defined in enumerable instead of using the
      // one implemented in the receiver.  The receiver might be a native 
      // implementation that does not support reducerProperty.
      var ret = SC.Enumerable.reduce.call(this, reducer, null, reducerProperty) ;
      return ret ;
    }
  }.property('[]') ;
};

SC.Reducers = /** @lends SC.Enumerable */ {
  /**
    This property will trigger anytime the enumerable's content changes.
    You can observe this property to be notified of changes to the enumerables
    content.
    
    For plain enumerables, this property is read only.  SC.Array overrides
    this method.
    
    @property {SC.Array}
  */
  '[]': function(key, value) { return this ; }.property(),

  /**
    Invoke this method when the contents of your enumerable has changed.
    This will notify any observers watching for content changes.  If your are
    implementing an ordered enumerable (such as an array), also pass the 
    start and end values where the content changed so that it can be used to
    notify range observers.
    
    @param {Number} start optional start offset for the content change
    @param {Number} length optional length of change
    @returns {Object} receiver 
  */
  enumerableContentDidChange: function(start, length) {
    this.notifyPropertyChange('[]') ;
    return this ;
  },
  
  /**
    Call this method from your unknownProperty() handler to implement 
    automatic reduced properties.  A reduced property is a property that 
    collects its contents dynamically from your array contents.  Reduced 
    properties always begin with "@".  Getting this property will call 
    reduce() on your array with the function matching the key name as the
    processor.
    
    The return value of this will be either the return value from the 
    reduced property or undefined, which means this key is not a reduced 
    property.  You can call this at the top of your unknownProperty handler
    like so:
    
    {{{
      unknownProperty: function(key, value) {
        var ret = this.handleReduceProperty(key, value) ;
        if (ret === undefined) {
          // process like normal
        }
      }
    }}}
     
    @param {String} key
      the reduce property key
    
    @param {Object} value
      a value or undefined.
    
    @param {Boolean} generateProperty
      only set to false if you do not want an optimized computed property 
      handler generated for this.  Not common.
  
    @returns {Object} the reduced property or undefined
  */
  reducedProperty: function(key, value, generateProperty) {
     
    if (!key || key.charAt(0) !== '@') return undefined ; // not a reduced property
    
    // get the reducer key and the reducer
    var matches = key.match(/^@([^(]*)(\(([^)]*)\))?$/) ;
    if (!matches || matches.length < 2) return undefined ; // no match
    
    var reducerKey = matches[1]; // = 'max' if key = '@max(balance)'
    var reducerProperty = matches[3] ; // = 'balance' if key = '@max(balance)'
    reducerKey = "reduce" + reducerKey.slice(0,1).toUpperCase() + reducerKey.slice(1);
    var reducer = this[reducerKey] ;

    // if there is no reduce function defined for this key, then we can't 
    // build a reducer for it.
    if (SC.typeOf(reducer) !== SC.T_FUNCTION) return undefined;
    
    // if we can't generate the property, just run reduce
    if (generateProperty === NO) {
      return SC.Enumerable.reduce.call(this, reducer, null, reducerProperty) ;
    }

    // ok, found the reducer.  Let's build the computed property and install
    var func = SC._buildReducerFor(reducerKey, reducerProperty);
    var p = this.constructor.prototype ;
    
    if (p) {
      p[key] = func ;
      
      // add the function to the properties array so that new instances
      // will have their dependent key registered.
      var props = p._properties || [] ;
      props.push(key) ;
      p._properties = props ;
      this.registerDependentKey(key, '[]') ;
    }
    
    // and reduce anyway...
    return SC.Enumerable.reduce.call(this, reducer, null, reducerProperty) ;
  },
  
  /** 
    Reducer for @max reduced property.
  */
  reduceMax: function(previousValue, item, index, e, reducerProperty) {
    if (reducerProperty && item) {
      item = item.get ? item.get(reducerProperty) : item[reducerProperty];
    }
    if (previousValue === null) return item ;
    return (item > previousValue) ? item : previousValue ;
  },

  /** 
    Reducer for @maxObject reduced property.
  */
  reduceMaxObject: function(previousItem, item, index, e, reducerProperty) {
    
    // get the value for both the previous and current item.  If no
    // reducerProperty was supplied, use the items themselves.
    var previousValue = previousItem, itemValue = item ;
    if (reducerProperty) {
      if (item) {
        itemValue = item.get ? item.get(reducerProperty) : item[reducerProperty] ;
      }
      
      if (previousItem) {
        previousValue = previousItem.get ? previousItem.get(reducerProperty) : previousItem[reducerProperty] ;
      }
    }
    if (previousValue === null) return item ;
    return (itemValue > previousValue) ? item : previousItem ;
  },

  /** 
    Reducer for @min reduced property.
  */
  reduceMin: function(previousValue, item, index, e, reducerProperty) {
    if (reducerProperty && item) {
      item = item.get ? item.get(reducerProperty) : item[reducerProperty];
    }
    if (previousValue === null) return item ;
    return (item < previousValue) ? item : previousValue ;
  },

  /** 
    Reducer for @maxObject reduced property.
  */
  reduceMinObject: function(previousItem, item, index, e, reducerProperty) {

    // get the value for both the previous and current item.  If no
    // reducerProperty was supplied, use the items themselves.
    var previousValue = previousItem, itemValue = item ;
    if (reducerProperty) {
      if (item) {
        itemValue = item.get ? item.get(reducerProperty) : item[reducerProperty] ;
      }
      
      if (previousItem) {
        previousValue = previousItem.get ? previousItem.get(reducerProperty) : previousItem[reducerProperty] ;
      }
    }
    if (previousValue === null) return item ;
    return (itemValue < previousValue) ? item : previousItem ;
  },

  /** 
    Reducer for @average reduced property.
  */
  reduceAverage: function(previousValue, item, index, e, reducerProperty) {
    if (reducerProperty && item) {
      item = item.get ? item.get(reducerProperty) : item[reducerProperty];
    }
    var ret = (previousValue || 0) + item ;
    var len = e.get ? e.get('length') : e.length;
    if (index >= len-1) ret = ret / len; //avg after last item.
    return ret ; 
  },

  /** 
    Reducer for @sum reduced property.
  */
  reduceSum: function(previousValue, item, index, e, reducerProperty) {
    if (reducerProperty && item) {
      item = item.get ? item.get(reducerProperty) : item[reducerProperty];
    }
    return (previousValue === null) ? item : previousValue + item ;
  }
} ;

// Apply reducers...
SC.mixin(SC.Enumerable, SC.Reducers) ;
SC.mixin(Array.prototype, SC.Reducers) ;
Array.prototype.isEnumerable = YES ;

// ......................................................
// ARRAY SUPPORT
//

// Implement the same enhancements on Array.  We use specialized methods
// because working with arrays are so common.
(function() {
  
  // These methods will be applied even if they already exist b/c we do it
  // better.
  var alwaysMixin = {
    
    // this is supported so you can get an enumerator.  The rest of the
    // methods do not use this just to squeeze every last ounce of perf as
    // possible.
    nextObject: SC.Enumerable.nextObject,
    enumerator: SC.Enumerable.enumerator,
    firstObject: SC.Enumerable.firstObject,
    sortProperty: SC.Enumerable.sortProperty,
    
    // see above...
    mapProperty: function(key) {
      var len = this.length ;
      var ret  = [];
      for(var idx=0;idx<len;idx++) {
        var next = this[idx] ;
        ret[idx] = next ? (next.get ? next.get(key) : next[key]) : null;
      }
      return ret ;
    },

    filterProperty: function(key, value) {
      var len = this.length ;
      var ret  = [];
      for(var idx=0;idx<len;idx++) {
        var next = this[idx] ;
        var cur = next ? (next.get ? next.get(key) : next[key]) : null;
        var matched = (value === undefined) ? !!cur : SC.isEqual(cur, value);
        if (matched) ret.push(next) ;
      }
      return ret ;
    },    
    
    find: function(callback, target) {
      if (typeof callback !== "function") throw new TypeError() ;
      var len = this.length ;
      if (target === undefined) target = null;

      var next, ret = null, found = NO;
      for(var idx=0;idx<len && !found;idx++) {
        next = this[idx] ;
        if(found = callback.call(target, next, idx, this)) ret = next ;
      }
      next = null;
      return ret ;
    },

    findProperty: function(key, value) {
      var len = this.length ;
      var next, cur, found=NO, ret=null;
      for(var idx=0;idx<len && !found;idx++) {
        cur = (next=this[idx]) ? (next.get ? next.get(key): next[key]):null;
        found = (value === undefined) ? !!cur : SC.isEqual(cur, value);
        if (found) ret = next ;
      }
      next=null;
      return ret ;
    },    

    everyProperty: function(key, value) {
      var len = this.length ;
      var ret  = YES;
      for(var idx=0;ret && (idx<len);idx++) {
        var next = this[idx] ;
        var cur = next ? (next.get ? next.get(key) : next[key]) : null;
        ret = (value === undefined) ? !!cur : SC.isEqual(cur, value);
      }
      return ret ;
    },
    
    someProperty: function(key, value) {
      var len = this.length ;
      var ret  = NO;
      for(var idx=0; !ret && (idx<len); idx++) {
        var next = this[idx] ;
        var cur = next ? (next.get ? next.get(key) : next[key]) : null;
        ret = (value === undefined) ? !!cur : SC.isEqual(cur, value);
      }
      return ret ;  // return the invert
    },
    
    invoke: function(methodName) {
      var len = this.length ;
      if (len <= 0) return [] ; // nothing to invoke....

      var idx;

      // collect the arguments
      var args = [] ;
      var alen = arguments.length ;
      if (alen > 1) {
        for(idx=1;idx<alen;idx++) args.push(arguments[idx]) ;
      }

      // call invoke
      var ret = [] ;
      for(idx=0;idx<len;idx++) {
        var next = this[idx] ;
        var method = next ? next[methodName] : null ;
        if (method) ret[idx] = method.apply(next, args) ;
      }
      return ret ;
    },

    invokeWhile: function(targetValue, methodName) {
      var len = this.length ;
      if (len <= 0) return null ; // nothing to invoke....

      var idx;

      // collect the arguments
      var args = [] ;
      var alen = arguments.length ;
      if (alen > 2) {
        for(idx=2;idx<alen;idx++) args.push(arguments[idx]) ;
      }

      // call invoke
      var ret = targetValue ;
      for(idx=0;(ret === targetValue) && (idx<len);idx++) {
        var next = this[idx] ;
        var method = next ? next[methodName] : null ;
        if (method) ret = method.apply(next, args) ;
      }
      return ret ;
    },

    toArray: function() {
      var len = this.length ;
      if (len <= 0) return [] ; // nothing to invoke....

      // call invoke
      var ret = [] ;
      for(var idx=0;idx<len;idx++) {
        var next = this[idx] ;
        ret.push(next) ;
      }
      return ret ;
    },
    
    getEach: function(key) {
      var ret = [];
      var len = this.length ;
      for(var idx=0;idx<len;idx++) {
        var obj = this[idx];
        ret[idx] = obj ? (obj.get ? obj.get(key) : obj[key]) : null;
      }
      return ret ;
    },
    
    setEach: function(key, value) {
      var len = this.length;
      for(var idx=0;idx<len;idx++) {
        var obj = this[idx];
        if (obj) {
          if (obj.set) {
            obj.set(key, value);
          } else obj[key] = value ;
        }
      }
      return this ;
    }
    
  }; 
  
  // These methods will only be applied if they are not already defined b/c 
  // the browser is probably getting it.
  var mixinIfMissing = {

    forEach: function(callback, target) {
      if (typeof callback !== "function") throw new TypeError() ;
      var len = this.length ;
      if (target === undefined) target = null;

      for(var idx=0;idx<len;idx++) {
        var next = this[idx] ;
        callback.call(target, next, idx, this);
      }
      return this ;
    },

    map: function(callback, target) {
      if (typeof callback !== "function") throw new TypeError() ;
      var len = this.length ;
      if (target === undefined) target = null;

      var ret  = [];
      for(var idx=0;idx<len;idx++) {
        var next = this[idx] ;
        ret[idx] = callback.call(target, next, idx, this) ;
      }
      return ret ;
    },

    filter: function(callback, target) {
      if (typeof callback !== "function") throw new TypeError() ;
      var len = this.length ;
      if (target === undefined) target = null;

      var ret  = [];
      for(var idx=0;idx<len;idx++) {
        var next = this[idx] ;
        if(callback.call(target, next, idx, this)) ret.push(next) ;
      }
      return ret ;
    },

    every: function(callback, target) {
      if (typeof callback !== "function") throw new TypeError() ;
      var len = this.length ;
      if (target === undefined) target = null;

      var ret  = YES;
      for(var idx=0;ret && (idx<len);idx++) {
        var next = this[idx] ;
        if(!callback.call(target, next, idx, this)) ret = NO ;
      }
      return ret ;
    },

    some: function(callback, target) {
      if (typeof callback !== "function") throw new TypeError() ;
      var len = this.length ;
      if (target === undefined) target = null;

      var ret  = NO;
      for(var idx=0;(!ret) && (idx<len);idx++) {
        var next = this[idx] ;
        if(callback.call(target, next, idx, this)) ret = YES ;
      }
      return ret ;
    },

    reduce: function(callback, initialValue, reducerProperty) {
      if (typeof callback !== "function") throw new TypeError() ;
      var len = this.length ;

      // no value to return if no initial value & empty
      if (len===0 && initialValue === undefined) throw new TypeError();

      var ret  = initialValue;
      for(var idx=0;idx<len;idx++) {
        var next = this[idx] ;

        // while ret is still undefined, just set the first value we get as 
        // ret. this is not the ideal behavior actually but it matches the 
        // FireFox implementation... :(
        if (next !== null) {
          if (ret === undefined) {
            ret = next ;
          } else {
            ret = callback.call(null, ret, next, idx, this, reducerProperty);
          }
        }
      }

      // uh oh...we never found a value!
      if (ret === undefined) throw new TypeError() ;
      return ret ;
    }   
  };
  
  // Apply methods if missing...
  for(var key in mixinIfMissing) {
    if (!mixinIfMissing.hasOwnProperty(key)) continue ;
    
    // The mixinIfMissing methods should be applied if they are not defined.
    // If Prototype 1.6 is included, some of these methods will be defined
    // already, but we want to override them anyway in this special case 
    // because our version is faster and functionally identitical.
    if (!Array.prototype[key] || ((typeof Prototype === 'object') && Prototype.Version.match(/^1\.6/))) {
      Array.prototype[key] = mixinIfMissing[key] ;
    }
  }
  
  // Apply other methods...
  SC.mixin(Array.prototype, alwaysMixin) ;
  
})() ;

;});
/* >>>>>>>>>> BEGIN source/mixins/freezable.js */
tiki.module('sproutcore/runtime:mixins/freezable',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"export package";

/**
  Standard Error that should be raised when you try to modify a frozen object.
  
  @property {Error}
*/
SC.FROZEN_ERROR = new Error("Cannot modify a frozen object");

/** 
  @namespace
  
  The SC.Freezable mixin implements some basic methods for marking an object
  as frozen.  Once an object is frozen it should be read only.  No changes 
  may be made the internal state of the object.
  
  h2. Enforcement

  To fully support freezing in your subclass, you must include this mixin and
  override any method that might alter any property on the object to instead
  raise an exception.  You can check the state of an object by checking the
  isFrozen property.

  Although future versions of JavaScript may support language-level freezing
  object objects, that is not the case today.  Even if an object is freezable,
  it is still technically possible to modify the object, even though it could
  break other parts of your application that do not expect a frozen object to
  change.  It is, therefore, very important that you always respect the 
  isFrozen property on all freezable objects.
  
  h2. Example

  The example below shows a simple object that implement the SC.Freezable 
  protocol.  
  
  {{{
    Contact = SC.Object.extend(SC.Freezable, {
      
      firstName: null,
      
      lastName: null,
      
      // swaps the names
      swapNames: function() {
        if (this.get('isFrozen')) throw SC.FROZEN_ERROR;
        var tmp = this.get('firstName');
        this.set('firstName', this.get('lastName'));
        this.set('lastName', tmp);
        return this;
      }
      
    });
    
    c = Context.create({ firstName: "John", lastName: "Doe" });
    c.swapNames();  => returns c
    c.freeze();
    c.swapNames();  => EXCEPTION
    
  }}}
  
  h2. Copying
  
  Usually the SC.Freezable protocol is implemented in cooperation with the
  SC.Copyable protocol, which defines a frozenCopy() method that will return
  a frozen object, if the object implements this method as well.
  
*/
SC.Freezable = {
  
  /**
    Walk like a duck.
    
    @property {Boolean}
  */
  isFreezable: YES,
  
  /**
    Set to YES when the object is frozen.  Use this property to detect whether
    your object is frozen or not.
    
    @property {Boolean}
  */
  isFrozen: NO,
  
  /**
    Freezes the object.  Once this method has been called the object should
    no longer allow any properties to be edited.
    
    @returns {Object} reciever
  */
  freeze: function() {
    // NOTE: Once someone actually implements Object.freeze() in the browser,
    // add a call to that here also.
    
    if (this.set) this.set('isFrozen', YES);
    else this.isFrozen = YES;
    return this;
  }
    
};


// Add to Array
SC.mixin(Array.prototype, SC.Freezable);
;});
/* >>>>>>>>>> BEGIN source/mixins/observable.js */
tiki.module('sproutcore/runtime:mixins/observable',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:private/observer_set');require('sproutcore/runtime:private/chain_observer');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import private/observer_set";
"import private/chain_observer";
"export package";

var _inited = false; // see initObservable().  avoid cyclical loop

/*globals logChange */

/**
  Set to YES to have all observing activity logged to the console.  This 
  should be used for debugging only.
  
  @property {Boolean}
*/
SC.LOG_OBSERVERS = NO ;

/**
  @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.
  
  h2. Observer Parameters
  
  An observer function typically does not need to accept any parameters, 
  however you can accept certain arguments when writing generic observers. 
  An observer function can have the following arguments:
  
  {{{
    propertyObserver(target, key, value, revision) ;
  }}}
  
  - *target* - This is the object whose value changed.  Usually this.
  - *key* - The key of the value that changed
  - *value* - this property is no longer used.  It will always be null
  - *revision* - this is the revision of the target object
  
  h2. Implementing Manual Change Notifications
  
  Sometimes you may want to control the rate at which notifications for 
  a property are delivered, for example by checking first to make sure 
  that the value has changed.
  
  To do this, you need to implement a computed property for the property 
  you want to change and override automaticallyNotifiesObserversFor().
  
  The example below will only notify if the "balance" property value actually
  changes:
  
  {{{
    
    automaticallyNotifiesObserversFor: function(key) {
      return (key === 'balance') ? NO : arguments.callee.base.apply(this,arguments) ;
    },
    
    balance: function(key, value) {
      var balance = this._balance ;
      if ((value !== undefined) && (balance !== value)) {
        this.propertyWillChange(key) ;
        balance = this._balance = value ;
        this.propertyDidChange(key) ;
      }
      return balance ;
    }
    
  }}}
  
  h1. Implementation Details
  
  Internally, SproutCore keeps track of observable information by adding a
  number of properties to the object adopting the observable.  All of these
  properties begin with "_kvo_" to separate them from the rest of your object.
  
  @static
  @since SproutCore 1.0
*/
SC.Observable = {

  /** 
    Walk like that ol' duck 
    
    @property {Boolean}
  */
  isObservable: YES,
  
  /**
    Determines whether observers should be automatically notified of changes
    to a key.
    
    If you are manually implementing change notifications for a property, you
    can override this method to return NO for properties you do not want the
    observing system to automatically notify for.
    
    The default implementation always returns YES.
    
    @param key {String} the key that is changing
    @returns {Boolean} YES if automatic notification should occur.
  */
  automaticallyNotifiesObserversFor: function(key) { 
    return YES;
  },

  // ..........................................
  // 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], cache ;
    if (ret === undefined) {
      return this.unknownProperty(key) ;
    } else if (ret && ret.isProperty) {
      if (ret.isCacheable) {
        cache = this._kvo_cache ;
        if (!cache) cache = this._kvo_cache = {};
        return (cache[ret.cacheKey] !== undefined) ? cache[ret.cacheKey] : (cache[ret.cacheKey] = ret.call(this,key)) ;
      } else 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 {SC.Observable}
  */
  set: function(key, value) {
    var func   = this[key], 
        notify = this.automaticallyNotifiesObserversFor(key),
        ret    = value, 
        cachedep, cache, idx, dfunc ;

    // if there are any dependent keys and they use caching, then clear the
    // cache.
    if (this._kvo_cacheable && (cache = this._kvo_cache)) {
      // lookup the cached dependents for this key.  if undefined, compute.
      // note that if cachdep is set to null is means we figure out it has no
      // cached dependencies already.  this is different from undefined.
      cachedep = this._kvo_cachedep;
      if (!cachedep || (cachedep = cachedep[key])===undefined) {
        cachedep = this._kvo_computeCachedDependentsFor(key);
      }
      
      if (cachedep) {
        idx = cachedep.length;
        while(--idx>=0) {
          dfunc = cachedep[idx];
          cache[dfunc.cacheKey] = cache[dfunc.lastSetValueKey] = undefined;
        }
      }
    }

    // set the value.
    if (func && func.isProperty) {
      cache = this._kvo_cache;
      if (func.isVolatile || !cache || (cache[func.lastSetValueKey] !== value)) {
        if (!cache) cache = this._kvo_cache = {};

        cache[func.lastSetValueKey] = value ;
        if (notify) this.propertyWillChange(key) ;
        ret = func.call(this,key,value) ;

        // update cached value
        if (func.isCacheable) cache[func.cacheKey] = ret ;
        if (notify) this.propertyDidChange(key, ret, YES) ;
      }

    } else if (func === undefined) {
      if (notify) this.propertyWillChange(key) ;
      this.unknownProperty(key,value) ;
      if (notify) this.propertyDidChange(key, ret) ;

    } else {
      if (this[key] !== value) {
        if (notify) this.propertyWillChange(key) ;
        ret = this[key] = value ;
        if (notify) this.propertyDidChange(key, ret) ;
      }
    }

    return this ;
  },

  /**  
    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 ;
  },

  /**  
    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 {SC.Observable}
  */
  beginPropertyChanges: function() {
    this._kvo_changeLevel = (this._kvo_changeLevel || 0) + 1; 
    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 
    beginPropertyChanges() 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 {SC.Observable}
  */
  endPropertyChanges: function() {
    this._kvo_changeLevel = (this._kvo_changeLevel || 1) - 1 ;
    var level = this._kvo_changeLevel, changes = this._kvo_changes;
    if ((level<=0) && changes && (changes.length>0) && !SC.Observers.isObservingSuspended) {
      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 {SC.Observable}
  */
  propertyWillChange: function(key) {
    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 {SC.Observable}
  */
  propertyDidChange: function(key,value, _keepCache) {

    this._kvo_revision = (this._kvo_revision || 0) + 1; 
    var level = this._kvo_changeLevel || 0,
        cachedep, idx, dfunc, cache, func,
        log = SC.LOG_OBSERVERS && !(this.LOG_OBSERVING===NO);

    if (this._kvo_cacheable && (cache = this._kvo_cache)) {

      // clear any cached value
      if (!_keepCache) {
        func = this[key] ;
        if (func && func.isProperty) {
          cache[func.cacheKey] = cache[func.lastSetValueKey] = undefined ;
        }
      }

      // if there are any dependent keys and they use caching, then clear the
      // cache.  This is the same code as is in set.  It is inlined for perf.
      cachedep = this._kvo_cachedep;
      if (!cachedep || (cachedep = cachedep[key])===undefined) {
        cachedep = this._kvo_computeCachedDependentsFor(key);
      }

      if (cachedep) {
        idx = cachedep.length;
        while(--idx>=0) {
          dfunc = cachedep[idx];
          cache[dfunc.cacheKey] = cache[dfunc.lastSetValueKey] = undefined;
        }
      }
    }

    // save in the change set if queuing changes
    var suspended = SC.Observers.isObservingSuspended;
    if ((level > 0) || suspended) {
      var changes = this._kvo_changes ;
      if (!changes) changes = this._kvo_changes = SC.CoreSet.create() ;
      changes.add(key) ;
      
      if (suspended) {
        if (log) console.log("%@%@: will not notify observers because observing is suspended".fmt(SC.KVO_SPACES,this));
        SC.Observers.objectHasPendingChanges(this) ;
      }
      
    // otherwise notify property observers immediately
    } else this._notifyPropertyObservers(key) ;
    
    return this ;
  },

  // ..........................................
  // DEPENDENT KEYS
  // 

  /**
    Use this to indicate that one key changes if other keys it depends on 
    change.  Pass the key that is dependent and additional keys it depends
    upon.  You can either pass the additional keys inline as arguments or 
    in a single array.
    
    You generally do not call this method, but instead pass dependent keys to
    your property() method when you declare a computed property.
    
    You can call this method during your init to register the keys that should
    trigger a change notification for your computed properties.  
    
    @param {String} key the dependent key
    @param {Array|String} dependentKeys one or more dependent keys 
    @returns {Object} this
  */  
  registerDependentKey: function(key, dependentKeys) {
    var dependents = this._kvo_dependents,
        func       = this[key],
        keys, idx, lim, dep, queue;

    // normalize input.
    if (SC.typeOf(dependentKeys) === SC.T_ARRAY) {
      keys = dependentKeys;
      lim  = 0;
    } else {
      keys = arguments;
      lim  = 1;
    }
    idx  = keys.length;

    // define dependents if not defined already.
    if (!dependents) this._kvo_dependents = dependents = {} ;

    // for each key, build array of dependents, add this key...
    // note that we ignore the first argument since it is the key...
    while(--idx >= lim) {
      dep = keys[idx] ;

      // add dependent key to dependents array of key it depends on
      queue = dependents[dep] ;
      if (!queue) queue = dependents[dep] = [] ;
      queue.push(key) ;
    }
  },

  /** @private 
  
    Helper method used by computeCachedDependents.  Just loops over the 
    array of dependent keys.  If the passed function is cacheable, it will
    be added to the queue.  Also, recursively call on each keys dependent 
    keys.
  
    @param {Array} queue the queue to add functions to
    @param {Array} keys the array of dependent keys for this key
    @param {Hash} dependents the _kvo_dependents cache
    @param {SC.Set} seen already seen keys
    @returns {void}
  */
  _kvo_addCachedDependents: function(queue, keys, dependents, seen) {
    var idx = keys.length,
        func, key, deps ;
        
    while(--idx >= 0) {
      key  = keys[idx];
      seen.add(key);
      
      // if the value for this key is a computed property, then add it to the
      // set if it is cacheable, and process any of its dependent keys also.
      func = this[key];
      if (func && (func instanceof Function) && func.isProperty) {
        if (func.isCacheable) queue.push(func); // handle this func
        if ((deps = dependents[key]) && deps.length>0) { // and any dependents
          this._kvo_addCachedDependents(queue, deps, dependents, seen);
        }
      } 
    }
        
  },
  
  /** @private

    Called by set() whenever it needs to determine which cached dependent
    keys to clear.  Recursively searches dependent keys to determine all 
    cached property direcly or indirectly affected.
    
    The return value is also saved for future reference
    
    @param {String} key the key to compute
    @returns {Array}
  */
  _kvo_computeCachedDependentsFor: function(key) {
    var cached     = this._kvo_cachedep,
        dependents = this._kvo_dependents,
        keys       = dependents ? dependents[key] : null,
        queue, seen ;
    if (!cached) cached = this._kvo_cachedep = {};

    // if there are no dependent keys, then just set and return null to avoid
    // this mess again.
    if (!keys || keys.length===0) return cached[key] = null;

    // there are dependent keys, so we need to do the work to find out if 
    // any of them or their dependent keys are cached.
    queue = cached[key] = [];
    seen  = SC._TMP_SEEN_SET = (SC._TMP_SEEN_SET || SC.CoreSet.create());
    seen.add(key);
    this._kvo_addCachedDependents(queue, keys, dependents, seen);
    seen.clear(); // reset
    
    if (queue.length === 0) queue = cached[key] = null ; // turns out nothing
    return queue ;
  },
  
  // ..........................................
  // OBSERVERS
  // 
  
  _kvo_for: function(kvoKey, type) {
    var ret = this[kvoKey] ;

    if (!this._kvo_cloned) this._kvo_cloned = {} ;
    
    // if the item does not exist, create it.  Unless type is passed, 
    // assume array.
    if (!ret) {
      ret = this[kvoKey] = (type === undefined) ? [] : type.create();
      this._kvo_cloned[kvoKey] = YES ;
      
    // if item does exist but has not been cloned, then clone it.  Note
    // that all types must implement copy().0
    } else if (!this._kvo_cloned[kvoKey]) {
      ret = this[kvoKey] = ret.copy();
      this._kvo_cloned[kvoKey] = YES; 
    }
    
    return ret ;
  },

  /**  
    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.
    
    You can also pass an optional context parameter to this method.  The 
    context will be passed to your observer method whenever it is triggered.
    Note that if you add the same target/method pair on a key multiple times
    with different context parameters, your observer will only be called once
    with the last context you passed.
    
    h2. Observer Methods
    
    Observer methods you pass should generally have the following signature if
    you do not pass a "context" parameter:
    
    {{{
      fooDidChange: function(sender, key, value, rev);
    }}}
    
    The sender is the object that changed.  The key is the property that
    changes.  The value property is currently reserved and unused.  The rev
    is the last property revision of the object when it changed, which you can
    use to detect if the key value has really changed or not.
    
    If you pass a "context" parameter, the context will be passed before the
    revision like so:
    
    {{{
      fooDidChange: function(sender, key, value, context, rev);
    }}}
    
    Usually you will not need the value, context or revision parameters at 
    the end.  In this case, it is common to write observer methods that take
    only a sender and key value as parameters or, if you aren't interested in
    any of these values, to write an observer that has no parameters at all.
    
    @param key {String} the key to observer
    @param target {Object} the target object to invoke
    @param method {String|Function} the method to invoke.
    @param context {Object} optional context
    @returns {SC.Object} self
  */
  addObserver: function(key, target, method, context) {
    
    var kvoKey, chain, chains, observers;
    
    // normalize.  if a function is passed to target, make it the method.
    if (method === undefined) {
      method = target; target = this ;
    }
    if (!target) target = this ;
    if (SC.typeOf(method) === SC.T_STRING) method = target[method] ;
    if (!method) throw "You must pass a method to addObserver()" ;

    // Normalize key...
    key = key.toString() ;
    if (key.indexOf('.') >= 0) {
      
      // create the chain and save it for later so we can tear it down if 
      // needed.
      chain = SC._ChainObserver.createChain(this, key, target, method, context);
      chain.masterTarget = target;  
      chain.masterMethod = method ;
      
      // Save in set for chain observers.
      this._kvo_for(SC.keyFor('_kvo_chains', key)).push(chain);
      
    // Create observers if needed...
    } else {
      
      // Special case to support reduced properties.  If the property 
      // key begins with '@' and its value is unknown, then try to get its
      // value.  This will configure the dependent keys if needed.
      if ((this[key] === undefined) && (key.indexOf('@') === 0)) {
        this.get(key) ;
      }

      if (target === this) target = null ; // use null for observers only.
      kvoKey = SC.keyFor('_kvo_observers', key);
      this._kvo_for(kvoKey, SC.ObserverSet).add(target, method, context);
      this._kvo_for('_kvo_observed_keys', SC.CoreSet).add(key) ;
    }

    if (this.didAddObserver) this.didAddObserver(key, target, method);
    return this;
  },

  /**
    Remove an observer you have previously registered on this object.  Pass
    the same key, target, and method you passed to addObserver() and your 
    target will no longer receive notifications.
    
    @returns {SC.Observable} reciever
  */
  removeObserver: function(key, target, method) {
    
    var kvoKey, chains, chain, observers, idx ;
    
    // normalize.  if a function is passed to target, make it the method.
    if (method === undefined) {
      method = target; target = this ;
    }
    if (!target) target = this ;
    if (SC.typeOf(method) === SC.T_STRING) method = target[method] ;
    if (!method) throw "You must pass a method to addObserver()" ;

    // if the key contains a '.', this is a chained observer.
    key = key.toString() ;
    if (key.indexOf('.') >= 0) {
      
      // try to find matching chains
      kvoKey = SC.keyFor('_kvo_chains', key);
      if (chains = this[kvoKey]) {
        
        // if chains have not been cloned yet, do so now.
        chains = this._kvo_for(kvoKey) ;
        
        // remove any chains
        idx = chains.length;
        while(--idx >= 0) {
          chain = chains[idx];
          if (chain && (chain.masterTarget===target) && (chain.masterMethod===method)) {
            chains[idx] = chain.destroyChain() ;
          }
        }
      }
      
    // otherwise, just like a normal observer.
    } else {
      if (target === this) target = null ; // use null for observers only.
      kvoKey = SC.keyFor('_kvo_observers', key) ;
      if (observers = this[kvoKey]) {
        // if observers have not been cloned yet, do so now
        observers = this._kvo_for(kvoKey) ;
        observers.remove(target, method) ;
        if (observers.targets <= 0) {
          this._kvo_for('_kvo_observed_keys', SC.CoreSet).remove(key);
        }
      }
    }

    if (this.didRemoveObserver) this.didRemoveObserver(key, target, method);
    return this;
  },
  
  /**
    Returns YES if the object currently has observers registered for a 
    particular key.  You can use this method to potentially defer performing
    an expensive action until someone begins observing a particular property
    on the object.
    
    @param {String} key key to check
    @returns {Boolean}
  */
  hasObserverFor: function(key) {
    SC.Observers.flush(this) ; // hookup as many observers as possible.
    
    var observers = this[SC.keyFor('_kvo_observers', key)],
        locals    = this[SC.keyFor('_kvo_local', key)],
        members ;

    if (locals && locals.length>0) return YES ;
    if (observers && observers.getMembers().length>0) return YES ;
    return NO ;
  },

  /**
    This method will register any observers and computed properties saved on
    the object.  Normally you do not need to call this method youself.  It
    is invoked automatically just before property notifications are sent and
    from the init() method of SC.Object.  You may choose to call this
    from your own initialization method if you are using SC.Observable in
    a non-SC.Object-based object.
    
    This method looks for several private variables, which you can setup,
    to initialize:
    
      - _observers: this should contain an array of key names for observers
        you need to configure.
        
      - _bindings: this should contain an array of key names that configure
        bindings.
        
      - _properties: this should contain an array of key names for computed
        properties.
        
    @returns {Object} this
  */
  initObservable: function() {
    if (this._observableInited) return ;
    this._observableInited = YES ;

    if (!_inited) {
      _inited = YES;
      require('sproutcore/runtime:system/binding');
      require('sproutcore/runtime:private/observer_queue');
    }
    
    var loc, keys, key, value, observer, propertyPaths, propertyPathsLength ;
    
    // Loop through observer functions and register them
    if (keys = this._observers) {
      var len = keys.length ;
      for(loc=0;loc<len;loc++) {
        key = keys[loc]; observer = this[key] ;
        propertyPaths = observer.propertyPaths ;
        propertyPathsLength = (propertyPaths) ? propertyPaths.length : 0 ;
        for(var ploc=0;ploc<propertyPathsLength;ploc++) {
          var path = propertyPaths[ploc] ;
          var dotIndex = path.indexOf('.') ;
          // handle most common case, observing a local property
          if (dotIndex < 0) {
            this.addObserver(path, this, observer) ;

          // next most common case, use a chained observer
          } else if (path.indexOf('*') === 0) {
            this.addObserver(path.slice(1), this, observer) ;
            
          // otherwise register the observer in the observers queue.  This 
          // will add the observer now or later when the named path becomes
          // available.
          } else {
            var root = null ;
            
            // handle special cases for observers that look to the local root
            if (dotIndex === 0) {
              root = this; path = path.slice(1) ;
            } else if (dotIndex===4 && path.slice(0,5) === 'this.') {
              root = this; path = path.slice(5) ;
            } else if (dotIndex<0 && path.length===4 && path === 'this') {
              root = this; path = '';
            }
            
            SC.Observers.addObserver(path, this, observer, root); 
          }
        }
      }
    }

    // Add Bindings
    this.bindings = []; // will be filled in by the bind() method.
    if (keys = this._bindings) {
      for(loc=0;loc<keys.length;loc++) {
        // get propertyKey
        key = keys[loc] ; value = this[key] ;
        var propertyKey = key.slice(0,-7) ; // contentBinding => content
        this[key] = this.bind(propertyKey, value) ;
      }
    }

    // Add Properties
    if (keys = this._properties) {
      for(loc=0;loc<keys.length;loc++) {
        key = keys[loc];
        if (value = this[key]) {

          // activate cacheable only if needed for perf reasons
          if (value.isCacheable) this._kvo_cacheable = YES; 

          // register dependent keys
          if (value.dependentKeys && (value.dependentKeys.length>0)) {
            this.registerDependentKey(key, value.dependentKeys) ;
          }
        }
      }
    }
    
  },
  
  // ..........................................
  // NOTIFICATION
  // 

  /**
    Returns an array with all of the observers registered for the specified
    key.  This is intended for debugging purposes only.  You generally do not
    want to rely on this method for production code.
    
    @params key {String} the key to evaluate
    @returns {Array} array of Observer objects, describing the observer.
  */
  observersForKey: function(key) {
    var observers = this._kvo_for('_kvo_observers', key) ;
    return observers.getMembers() || [] ;
  },
  
  // this private method actually notifies the observers for any keys in the
  // observer queue.  If you pass a key it will be added to the queue.
  _notifyPropertyObservers: function(key) {

    if (!this._observableInited) this.initObservable() ;
    
    SC.Observers.flush(this) ; // hookup as many observers as possible.

    var log = SC.LOG_OBSERVERS && !(this.LOG_OBSERVING===NO) ;
    var observers, changes, dependents, starObservers, idx, keys, rev ;
    var members, membersLength, member, memberLoc, target, method, loc, func ;
    var context, spaces, cache ;

    if (log) {
      spaces = SC.KVO_SPACES = (SC.KVO_SPACES || '') + '  ';
      console.log('%@%@: notifying observers after change to key "%@"'.fmt(spaces, this, key));
    }
    
    // Get any starObservers -- they will be notified of all changes.
    starObservers =  this['_kvo_observers_*'] ;
    
    // prevent notifications from being sent until complete
    this._kvo_changeLevel = (this._kvo_changeLevel || 0) + 1; 

    // keep sending notifications as long as there are changes
    while(((changes = this._kvo_changes) && (changes.length > 0)) || key) {
      
      // increment revision
      rev = ++this.propertyRevision ;
      
      // save the current set of changes and swap out the kvo_changes so that
      // any set() calls by observers will be saved in a new set.
      if (!changes) changes = SC.CoreSet.create() ;
      this._kvo_changes = null ;

      // Add the passed key to the changes set.  If a '*' was passed, then
      // add all keys in the observers to the set...
      // once finished, clear the key so the loop will end.
      if (key === '*') {
        changes.add('*') ;
        changes.addEach(this._kvo_for('_kvo_observed_keys', SC.CoreSet));

      } else if (key) changes.add(key) ;

      // Now go through the set and add all dependent keys...
      if (dependents = this._kvo_dependents) {

        // NOTE: each time we loop, we check the changes length, this
        // way any dependent keys added to the set will also be evaluated...
        for(idx=0;idx<changes.length;idx++) {
          key = changes[idx] ;
          keys = dependents[key] ;
          
          // for each dependent key, add to set of changes.  Also, if key
          // value is a cacheable property, clear the cached value...
          if (keys && (loc = keys.length)) {
            if (log) {
              console.log("%@...including dependent keys for %@: %@".fmt(spaces, key, keys));
            }
            cache = this._kvo_cache;
            if (!cache) cache = this._kvo_cache = {};
            while(--loc >= 0) {
              changes.add(key = keys[loc]);
              if (func = this[key]) {
                this[func.cacheKey] = undefined;
                cache[func.cacheKey] = cache[func.lastSetValueKey] = undefined;
              } // if (func=)
            } // while (--loc)
          } // if (keys && 
        } // for(idx...
      } // if (dependents...)

      // now iterate through all changed keys and notify observers.
      while(changes.length > 0) {
        key = changes.pop() ; // the changed key

        // find any observers and notify them...
        observers = this[SC.keyFor('_kvo_observers', key)];
        if (observers) {
          members = observers.getMembers() ;
          membersLength = members.length ;
          for(memberLoc=0;memberLoc < membersLength; memberLoc++) {
            member = members[memberLoc] ;
            if (member[3] === rev) continue ; // skip notified items.

            target = member[0] || this; 
            method = member[1] ; 
            context = member[2];
            member[3] = rev;
            
            if (log) console.log('%@...firing observer on %@ for key "%@"'.fmt(spaces, target, key));
            if (context !== undefined) {
              method.call(target, this, key, null, context, rev);
            } else {
              method.call(target, this, key, null, rev) ;
            }
          }
        }

        // look for local observers.  Local observers are added by SC.Object
        // as an optimization to avoid having to add observers for every 
        // instance when you are just observing your local object.
        members = this[SC.keyFor('_kvo_local', key)];
        if (members) {
          membersLength = members.length ;
          for(memberLoc=0;memberLoc<membersLength;memberLoc++) {
            member = members[memberLoc];
            method = this[member] ; // try to find observer function
            if (method) {
              if (log) console.log('%@...firing local observer %@.%@ for key "%@"'.fmt(spaces, this, member, key));
              method.call(this, this, key, null, rev);
            }
          }
        }
        
        // if there are starObservers, do the same thing for them
        if (starObservers && key !== '*') {          
          members = starObservers.getMembers() ;
          membersLength = members.length ;
          for(memberLoc=0;memberLoc < membersLength; memberLoc++) {
            member = members[memberLoc] ;
            target = member[0] || this; 
            method = member[1] ;
            context = member[2] ;
            
            if (log) console.log('%@...firing * observer on %@ for key "%@"'.fmt(spaces, target, key));
            if (context !== undefined) {
              method.call(target, this, key, null, context, rev);
            } else {
              method.call(target, this, key, null, rev) ;
            }
          }
        }

        // if there is a default property observer, call that also
        if (this.propertyObserver) {
          if (log) console.log('%@...firing %@.propertyObserver for key "%@"'.fmt(spaces, this, key));
          this.propertyObserver(this, key, null, rev);
        }
      } // while(changes.length>0)

      // changes set should be empty. release it for reuse
      if (changes) changes.destroy() ;
      
      // key is no longer needed; clear it to avoid infinite loops
      key = null ; 
      
    } // while (changes)
    
    // done with loop, reduce change level so that future sets can resume
    this._kvo_changeLevel = (this._kvo_changeLevel || 1) - 1; 
    
    if (log) SC.KVO_SPACES = spaces.slice(0, -2);
    
    return YES ; // finished successfully
  },

  // ..........................................
  // BINDINGS
  // 
    
  /**  
    Manually add a new binding to an object.  This is the same as doing
    the more familiar propertyBinding: 'property.path' approach.
    
    @param {String} toKey the key to bind to
    @param {Object} target target or property path to bind from
    @param {String|Function} method method for target to bind from
    @returns {SC.Binding} new binding instance
  */
  bind: function(toKey, target, method) {

    var binding ;

    // normalize...
    if (method !== undefined) target = [target, method];

    // if a string or array (i.e. tuple) is passed, convert this into a
    // binding.  If a binding default was provided, use that.
    var pathType = SC.typeOf(target) ;
    if (pathType === SC.T_STRING || pathType === SC.T_ARRAY) {
      binding = this[toKey + 'BindingDefault'] || SC.Binding;
      binding = binding.beget().from(target) ;
    } else binding = target ;

    // finish configuring the binding and then connect it.
    binding = binding.to(toKey, this).connect() ;
    this.bindings.push(binding) ;
    
    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) { 
    
    context = SC.hashFor(context) ; // get a hash key we can use in caches.
    
    // setup caches...
    var valueCache = this._kvo_didChange_valueCache ;
    if (!valueCache) valueCache = this._kvo_didChange_valueCache = {};
    var revisionCache = this._kvo_didChange_revisionCache;
    if (!revisionCache) revisionCache=this._kvo_didChange_revisionCache={};

    // get the cache of values and revisions already seen in this context
    var seenValues = valueCache[context] || {} ;
    var seenRevisions = revisionCache[context] || {} ;
    
    // prepare too loop!
    var ret = false ;
    var currentRevision = this._kvo_revision || 0  ;
    var idx = arguments.length ;
    while(--idx >= 1) {  // NB: loop only to 1 to ignore context arg.
      var key = arguments[idx];
      
      // has the kvo revision changed since the last time we did this?
      if (seenRevisions[key] != currentRevision) {
        // yes, check the value with the last seen value
        var value = this.get(key) ;
        if (seenValues[key] !== value) {
          ret = true ; // did change!
          seenValues[key] = value;
        }
      }
      seenRevisions[key] = currentRevision;
    }
    
    valueCache[context] = seenValues ;
    revisionCache[context] = seenRevisions ;
    return ret ;
  },



  /**
    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 {SC.Observable}
  */
  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.tupleForPropertyPath(path, this) ;
    if (tuple === null || 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 {SC.Observable}
  */
  setPath: function(path, value) {
    if (path.indexOf('.') >= 0) {
      var tuple = SC.tupleForPropertyPath(path, this) ;
      if (!tuple || !tuple[0]) return null ;
      tuple[0].set(tuple[1], value) ;
    } else this.set(path, value) ; // shortcut
    return this;
  },

  /**
    Navigates the property path, finally setting the value but only if 
    the value does not match the current value.  This will avoid sending
    unecessary change notifications.
    
    @param path {String} the property path to set
    @param value {Object} the value to set
    @returns {Object} this
  */
  setPathIfChanged: function(path, value) {
    if (path.indexOf('.') >= 0) {
      var tuple = SC.tupleForPropertyPath(path, this) ;
      if (!tuple || !tuple[0]) return null ;
      if (tuple[0].get(tuple[1]) !== value) {
        tuple[0].set(tuple[1], value) ;
      }
    } else this.setIfChanged(path, value) ; // shortcut
    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 = SC.A(arguments) ;
    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) ;
  },

  /**
    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 {SC.Observable}
  */
  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 {SC.Observable}
  */
  allPropertiesDidChange: function() {
    this._kvo_cache = null; //clear cached props
    this._notifyPropertyObservers('*') ;
    return this ;
  },

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

  /**
    Logs the named properties to the console.
    
    @param {String...} propertyNames one or more property names
  */
  logProperty: function() {
    var props = SC.$A(arguments) ;
    for(var idx=0;idx<props.length; idx++) {
      var prop = props[idx] ;
      console.log('%@:%@: '.fmt(SC.guidFor(this), prop), this.get(prop)) ;
    }
  },

  propertyRevision: 1
    
} ;

/** @private used by addProbe/removeProbe */
SC.logChange = function logChange(target, key, value) {
  console.log("CHANGE: %@[%@] => %@".fmt(target, key, target.get(key))) ;
};

// Make all Array's observable
SC.mixin(Array.prototype, SC.Observable) ;
;});
/* >>>>>>>>>> BEGIN source/private/chain_observer.js */
tiki.module('sproutcore/runtime:private/chain_observer',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";

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

// This is a private class used by the observable mixin to support chained
// properties.

// 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 = function(property) {
  this.property = property ;
} ;

// This is the primary entry point.  Configures the chain.
SC._ChainObserver.createChain = function(rootObject, path, target, method, context) {

  // First we create the chain.
  var parts = path.split('.'),
      root  = new SC._ChainObserver(parts[0]),
      tail  = root,
      len   = parts.length;

  for(var idx=1;idx<len;idx++) {
    tail = tail.next = new SC._ChainObserver(parts[idx]) ;
  }
  
  // Now root has the first observer and tail has the last one.
  // Feed the rootObject into the front to setup the chain...
  // do this BEFORE we set the target/method so they will not be triggered.
  root.objectDidChange(rootObject);
  
  // Finally, set the target/method on the tail so that future changes will
  // trigger.
  tail.target = target; tail.method = method ; tail.context = context ;
  
  // and return the root to save
  return root ;
};

SC._ChainObserver.prototype = {
  isChainObserver: true,
  
  // the object this instance is observing
  object: null,
  
  // the property on the object this link is observing.
  property: null,
  
  // if not null, this is the next link in the chain.  Whenever the 
  // current property changes, the next observer will be notified.
  next: null,
  
  // if not null, this is the final target observer.
  target: null,
  
  // if not null, this is the final target method
  method: null,
  
  // invoked when the source object changes.  removes observer on old 
  // object, sets up new observer, if needed.
  objectDidChange: function(newObject) {
    if (newObject === this.object) return; // nothing to do.
    
    // if an old object, remove observer on it.
    if (this.object && this.object.removeObserver) {
      this.object.removeObserver(this.property, this, this.propertyDidChange);
    }
    
    // if a new object, add observer on it...
    this.object = newObject ;
    if (this.object && this.object.addObserver) {
      this.object.addObserver(this.property, this, this.propertyDidChange); 
    }
    
    // now, notify myself that my property value has probably changed.
    this.propertyDidChange() ;
  },
  
  // the observer method invoked when the observed property changes.
  propertyDidChange: function() {
    
    // get the new value
    var object = this.object ;
    var property = this.property ;
    var value = (object && object.get) ? object.get(property) : null ;
    
    // if we have a next object in the chain, notify it that its object 
    // did change...
    if (this.next) this.next.objectDidChange(value) ;
    
    // if we have a target/method, call it.
    var target  = this.target,
        method  = this.method,
        context = this.context ;
    if (target && method) {
      var rev = object ? object.propertyRevision : null ;
      if (context) {
        method.call(target, object, property, value, context, rev);
      } else {
        method.call(target, object, property, value, rev) ;
      }
    } 
  },
  
  // teardown the chain...
  destroyChain: function() {
    
    // remove observer
    var obj = this.object ;
    if (obj && obj.removeObserver) {
      obj.removeObserver(this.property, this, this.propertyDidChange) ;
    }  
    
    // destroy next item in chain
    if (this.next) this.next.destroyChain() ;
    
    // and clear left overs...
    this.next = this.target = this.method = this.object = this.context = null;
    return null ;
  }
  
} ;
;});
/* >>>>>>>>>> BEGIN source/private/observer_queue.js */
tiki.module('sproutcore/runtime:private/observer_queue',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:system/set');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import system/set";

// ........................................................................
// 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.

/**
  @namespace 
  
  The private ObserverQueue is used to maintain a set of pending observers. 
  This allows you to setup an observer on an object before the object exists.
  
  Whenever the observer fires, the queue will be flushed to connect any 
  pending observers.
  
  @since SproutCore 1.0
*/
SC.Observers = {

  queue: [],
  
  /**
   @private 
  
   Attempt to add the named observer.  If the observer cannot be found, put
   it into a queue for later.
  */
  addObserver: function(propertyPath, target, method, pathRoot) {
    var tuple ;

    // try to get the tuple for this.
    if (SC.typeOf(propertyPath) === SC.T_STRING) {
      tuple = SC.tupleForPropertyPath(propertyPath, pathRoot) ;
    } else {
      tuple = propertyPath; 
    }

    // if a tuple was found, add the observer immediately...
    if (tuple) {
      tuple[0].addObserver(tuple[1],target, method) ;
      
    // otherwise, save this in the queue.
    } else {
      this.queue.push([propertyPath, target, method, pathRoot]) ;
    }
  },

  /** 
    @private 
  
    Remove the observer.  If it is already in the queue, remove it.  Also
    if already found on the object, remove that.
  */
  removeObserver: function(propertyPath, target, method, pathRoot) {
    var idx, queue, tuple, item;
    
    tuple = SC.tupleForPropertyPath(propertyPath, pathRoot) ;
    if (tuple) {
      tuple[0].removeObserver(tuple[1], target, method) ;
    } 

    idx = this.queue.length; queue = this.queue ;
    while(--idx >= 0) {
      item = queue[idx] ;
      if ((item[0] === propertyPath) && (item[1] === target) && (item[2] == method) && (item[3] === pathRoot)) queue[idx] = null ;
    }
  },
  
  /**
    @private 
    
    Range Observers register here to indicate that they may potentially 
    need to start observing.
  */
  addPendingRangeObserver: function(observer) {
    var ro = this.rangeObservers;
    if (!ro) ro = this.rangeObservers = SC.CoreSet.create();
    ro.add(observer);
    return this ;
  },
  
  _TMP_OUT: [],
  
  /** 
    Flush the queue.  Attempt to add any saved observers.
  */
  flush: function(object) { 
       
    // flush any observers that we tried to setup but didn't have a path yet
    var oldQueue = this.queue ;
    if (oldQueue && oldQueue.length > 0) {
      var newQueue = (this.queue = []) ; 
      var idx = oldQueue.length ;
      while(--idx >= 0) {
        var item = oldQueue[idx] ;
        if (!item) continue ;

        var tuple = SC.tupleForPropertyPath(item[0], item[3]);
        if (tuple) {
          tuple[0].addObserver(tuple[1], item[1], item[2]) ;
        } else newQueue.push(item) ;
      }
    }
    
    // if this object needsRangeObserver then see if any pending range 
    // observers need it.
    if (object._kvo_needsRangeObserver) {
      var set = this.rangeObservers,
          len = set ? set.get('length') : 0,
          out = this._TMP_OUT,
          ro;
          
      for(idx=0;idx<len;idx++) {
        ro = set[idx]; // get the range observer
        if (ro.setupPending(object)) {
          out.push(ro); // save to remove later
        }
      }
      
      // remove any that have setup
      if (out.length > 0) set.removeEach(out);
      out.length = 0; // reset
      object._kvo_needsRangeObserver = NO ;
    }
    
  },
  
  /** @private */
  isObservingSuspended: 0,

  _pending: SC.CoreSet.create(),

  /** @private */
  objectHasPendingChanges: function(obj) {
    this._pending.add(obj) ; // save for later
  },

  /** @private */
  // temporarily suspends all property change notifications.
  suspendPropertyObserving: function() {
    this.isObservingSuspended++ ;
  },
  
  // resume change notifications.  This will call notifications to be
  // delivered for all pending objects.
  /** @private */
  resumePropertyObserving: function() {
    var pending ;
    if(--this.isObservingSuspended <= 0) {
      pending = this._pending ;
      this._pending = SC.CoreSet.create() ;
      
      var idx, len = pending.length;
      for(idx=0;idx<len;idx++) {
        pending[idx]._notifyPropertyObservers() ;
      }
      pending.clear();
      pending = null ;
    }
  }
  
} ;
;});
/* >>>>>>>>>> BEGIN source/private/observer_set.js */
tiki.module('sproutcore/runtime:private/observer_set',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";

// ........................................................................
// ObserverSet
//

/**
  @namespace

  This private class is used to store information about obversers on a 
  particular key.  Note that this object is not observable.  You create new
  instances by calling SC.beget(SC.ObserverSet) ;

  @since SproutCore 1.0
*/
SC.ObserverSet = {

  /**
    the number of targets in the set.
  */
  targets: 0,
  
  _membersCacheIsValid: NO,
  
  /**
    adds the named target/method observer to the set.  The method must be
    a function, not a string..
  */
  add: function(target, method, context) {
    var targetGuid = (target) ? SC.guidFor(target) : "__this__";
    
    // get the set of methods
    var methods = this[targetGuid] ;
    if (!methods) {
      methods = this[targetGuid] = SC.CoreSet.create() ;
      methods.target = target ;
      methods.isTargetSet = YES ; // used for getMembers().
      this.targets++ ;
    }
    methods.add(method) ;
    
    // context is really useful sometimes but not used that often so this
    // implementation is intentionally lazy.
    if (context !== undefined) {
      var contexts = methods.contexts ;
      if (!context) contexts = {};
      contexts[SC.guidFor(method)] = context ;
    }
    
    this._membersCacheIsValid = NO ;
  },
  
  /**
    removes the named target/method observer from the set.  If this is the
    last method for the named target, then the number of targets will also
    be reduced.
  
    returns YES if the items was removed, NO if it was not found.
  */
  remove: function(target, method) {
    var targetGuid = (target) ? SC.guidFor(target) : "__this__";
    
    // get the set of methods
    var methods = this[targetGuid] ;    
    if (!methods) return NO ;
    
    methods.remove(method) ;
    if (methods.length <= 0) {
      methods.target = null;
      methods.isTargetSet = NO ;
      methods.contexts = null ;
      delete this[targetGuid] ;
      this.targets-- ;
      
    } else if (methods.contexts) {
      delete methods.contexts[SC.guidFor(method)];
    }

    this._membersCacheIsValid = NO;
    
    return YES ;
  },
  
  /**
    Invokes the target/method pairs in the receiver.  Used by SC.RunLoop
    Note: does not support context
  */
  invokeMethods: function() {
    // iterate through the set, look for sets.
    for(var key in this) {
      if (!this.hasOwnProperty(key)) continue ;
      var value = this[key] ;
      if (value && value.isTargetSet) {
        var idx = value.length;
        var target = value.target ;
        while(--idx>=0) value[idx].call(target);
      }
    }
  },
  
  /**
    Returns an array of target/method pairs.  This is cached.
  */
  getMembers: function() {
    if (this._membersCacheIsValid) return this._members ;
    
    // need to recache, reset the array...
    if (!this._members) {
      this._members = [] ;
    } else this._members.length = 0 ; // reset
    var ret = this._members ;

    // iterate through the set, look for sets.
    for(var key in this) {
      if (!this.hasOwnProperty(key)) continue ;
      var value = this[key] ;
      if (value && value.isTargetSet) {
        var idx = value.length;
        var target = value.target ;
        
        // slightly slower - only do if we have contexts
        var contexts = value.contexts ;
        if (contexts) {
          while(--idx>=0) {
            var method = value[idx] ;
            ret.push([target, method, contexts[SC.guidFor(method)]]) ;
          }
        } else {
          while(--idx>=0) ret.push([target, value[idx]]);
        }
      }
    }

    this._membersCacheIsValid = YES ;
    return ret ;
  },
  
  /**
    Returns a new instance of the set with the contents cloned.
  */
  clone: function() {
    var oldSet, newSet, key, ret = SC.ObserverSet.create() ;
    for(key in this) {
      if (!this.hasOwnProperty(key)) continue ;
      oldSet = this[key];
      if (oldSet && oldSet.isTargetSet) {
        newSet = oldSet.clone();
        newSet.target = oldSet.target ;
        if (oldSet.contexts) newSet.contexts = SC.clone(oldSet.contexts);
        ret[key] = newSet ;
      }
    }
    ret.targets = this.targets ;
    ret._membersCacheIsValid = NO ;
    return ret ;
  },
  
  /**
    Creates a new instance of the observer set.
  */
  create: function() { return SC.beget(this); }
  
} ;

SC.ObserverSet.slice = SC.ObserverSet.clone ;


;});
/* >>>>>>>>>> BEGIN source/system/binding.js */
tiki.module('sproutcore/runtime:system/binding',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:system/set');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import system/set";
"export package";

/**
  Debug parameter you can turn on.  This will log all bindings that fire to
  the console.  This should be disabled in production code.  Note that you
  can also enable this from the console or temporarily.
  
  @property {Boolean}
*/
SC.LOG_BINDINGS = NO ;

/**
  Performance paramter.  This will benchmark the time spent firing each 
  binding.
  
  @property {Boolean}
*/
SC.BENCHMARK_BINDING_NOTIFICATIONS = NO ;

/**
  Performance parameter.  This will benchmark the time spend configuring each
  binding.  
  
  @property {Boolean}
*/
SC.BENCHMARK_BINDING_SETUP = NO;
  
/** 
  Default placeholder for multiple values in bindings.
  
  @property {String}
*/
SC.MULTIPLE_PLACEHOLDER = '@@MULT@@' ;

/**
  Default placeholder for null values in bindings.
  
  @property {String}
*/
SC.NULL_PLACEHOLDER = '@@NULL@@' ;

/**
  Default placeholder for empty values in bindings.
  
  @property {String}
*/
SC.EMPTY_PLACEHOLDER = '@@EMPTY@@' ;


/**
  @namespace 
  
  A binding simply connects the properties of two objects so that whenever the
  value of one property changes, the other property will be changed also.  You
  do not usually work with Binding objects directly but instead describe
  bindings in your class definition using something like:
  
  {{{
    valueBinding: "MyApp.someController.title"
  }}}
    
  This will create a binding from "MyApp.someController.title" to the "value"
  property of your object instance automatically.  Now the two values will be
  kept in sync.
  
  h2. Customizing Your Bindings
  
  In addition to synchronizing values, bindings can also perform some basic 
  transforms on values.  These transforms can help to make sure the data fed 
  into one object always meets the expectations of that object regardless of
  what the other object outputs.
  
  To customize a binding, you can use one of the many helper methods defined 
  on SC.Binding like so:
  
  {{{
    valueBinding: SC.Binding.single("MyApp.someController.title") 
  }}}
    
  This will create a binding just like the example above, except that now the
  binding will convert the value of MyApp.someController.title to a single 
  object (removing any arrays) before applying it to the "value" property of
  your object.
  
  You can also chain helper methods to build custom bindings like so:
  
  {{{
    valueBinding: SC.Binding.single("MyApp.someController.title").notEmpty("(EMPTY)")
  }}}
    
  This will force the value of MyApp.someController.title to be a single value
  and then check to see if the value is "empty" (null, undefined, empty array,
  or an empty string).  If it is empty, the value will be set to the string 
  "(EMPTY)".
  
  h2. One Way Bindings
  
  One especially useful binding customization you can use is the oneWay() 
  helper.  This helper tells SproutCore that you are only interested in 
  receiving changes on the object you are binding from.  For example, if you
  are binding to a preference and you want to be notified if the preference 
  has changed, but your object will not be changing the preference itself, you
  could do:
  
  {{{
    bigTitlesBinding: SC.Binding.oneWay("MyApp.preferencesController.bigTitles")
  }}}
    
  This way if the value of MyApp.preferencesController.bigTitles changes the
  "bigTitles" property of your object will change also.  However, if you 
  change the value of your "bigTitles" property, it will not update the 
  preferencesController.
  
  One way bindings are almost twice as fast to setup and twice as fast to 
  execute because the binding only has to worry about changes to one side.
  
  You should consider using one way bindings anytime you have an object that 
  may be created frequently and you do not intend to change a property; only 
  to monitor it for changes. (such as in the example above).
      
  h2. Adding Custom Transforms
  
  In addition to using the standard helpers provided by SproutCore, you can 
  also defined your own custom transform functions which will be used to 
  convert the value.  To do this, just define your transform function and add
  it to the binding with the transform() helper.  The following example will 
  not allow Integers less than ten.  Note that it checks the value of the 
  bindings and allows all other values to pass:
  
  {{{
    valueBinding: SC.Binding.transform(function(value, binding) {
      return ((SC.typeOf(value) === SC.T_NUMBER) && (value < 10)) ? 10 : value;      
    }).from("MyApp.someController.value")
  }}}
  
  If you would like to instead use this transform on a number of bindings,
  you can also optionally add your own helper method to SC.Binding.  This
  method should simply return the value of this.transform(). The example 
  below adds a new helper called notLessThan() which will limit the value to
  be not less than the passed minimum:
  
  {{{
    SC.Binding.notLessThan = function(minValue) {
      return this.transform(function(value, binding) {
        return ((SC.typeOf(value) === SC.T_NUMBER) && (value < minValue)) ? minValue : value ;
      }) ;
    } ;
  }}}
  
  You could specify this in your core.js file, for example.  Then anywhere in 
  your application you can use it to define bindings like so:
  
  {{{
    valueBinding: SC.Binding.from("MyApp.someController.value").notLessThan(10)
  }}}
  
  Also, remember that helpers are chained so you can use your helper along with
  any other helpers.  The example below will create a one way binding that 
  does not allow empty values or values less than 10:
  
  {{{
    valueBinding: SC.Binding.oneWay("MyApp.someController.value").notEmpty().notLessThan(10)
  }}}
  
  Note that the built in helper methods all allow you to pass a "from" 
  property path so you don't have to use the from() helper to set the path.  
  You can do the same thing with your own helper methods if you like, but it 
  is not required.
  
  h2. Creating Custom Binding Templates
  
  Another way you can customize bindings is to create a binding template.  A
  template is simply a binding that is already partially or completely 
  configured.  You can specify this template anywhere in your app and then use 
  it instead of designating your own custom bindings.  This is a bit faster on
  app startup but it is mostly useful in making your code less verbose.
  
  For example, let's say you will be frequently creating one way, not empty 
  bindings that allow values greater than 10 throughout your app.  You could
  create a binding template in your core.js like this:
  
  {{{
    MyApp.LimitBinding = SC.Binding.oneWay().notEmpty().notLessThan(10);
  }}}
  
  Then anywhere you want to use this binding, just refer to the template like 
  so:

  {{{
    valueBinding: MyApp.LimitBinding.beget("MyApp.someController.value")
  }}}
    
  Note that when you use binding templates, it is very important that you 
  always start by using beget() to extend the template.  If you do not do 
  this, you will end up using the same binding instance throughout your app 
  which will lead to erratic behavior.
  
  h2. How to Manually Activate a Binding

  All of the examples above show you how to configure a custom binding, but 
  the result of these customizations will be a binding template, not a fully 
  active binding.  The binding will actually become active only when you 
  instantiate the object the binding belongs to.  It is useful however, to 
  understand what actually happens when the binding is activated.
  
  For a binding to function it must have at least a "from" property and a "to"
  property.  The from property path points to the object/key that you want to
  bind from while the to path points to the object/key you want to bind to.  
  
  When you define a custom binding, you are usually describing the property 
  you want to bind from (such as "MyApp.someController.value" in the examples
  above).  When your object is created, it will automatically assign the value
  you want to bind "to" based on the name of your binding key.  In the 
  examples above, during init, SproutCore objects will effectively call 
  something like this on your binding:
  
  {{{
    binding = this.valueBinding.beget().to("value", this) ;
  }}}
    
  This creates a new binding instance based on the template you provide, and 
  sets the to path to the "value" property of the new object.  Now that the 
  binding is fully configured with a "from" and a "to", it simply needs to be
  connected to become active.  This is done through the connect() method:
  
  {{{
    binding.connect() ;
  }}}
    
  Now that the binding is connected, it will observe both the from and to side 
  and relay changes.
  
  If you ever needed to do so (you almost never will, but it is useful to 
  understand this anyway), you could manually create an active binding by 
  doing the following:
  
  {{{
    SC.Binding.from("MyApp.someController.value")
     .to("MyApp.anotherObject.value")
     .connect();
  }}}
     
  You could also use the bind() helper method provided by SC.Object. (This is 
  the same method used by SC.Object.init() to setup your bindings):
  
  {{{
    MyApp.anotherObject.bind("value", "MyApp.someController.value") ;
  }}}

  Both of these code fragments have the same effect as doing the most friendly
  form of binding creation like so:
  
  {{{
    MyApp.anotherObject = SC.Object.create({
      valueBinding: "MyApp.someController.value",
      
      // OTHER CODE FOR THIS OBJECT...
      
    }) ;
  }}}
  
  SproutCore's built in binding creation method make it easy to automatically
  create bindings for you.  You should always use the highest-level APIs 
  available, even if you understand how to it works underneath.
  
  @since SproutCore 1.0
*/
SC.Binding = {
  
  /**
    This is the core method you use to create a new binding instance.  The
    binding instance will have the receiver instance as its parent which means
    any configuration you have there will be inherited.  
    
    The returned instance will also have its parentBinding property set to the 
    receiver.

    @param fromPath {String} optional from path.
    @returns {SC.Binding} new binding instance
  */
  beget: function(fromPath) {
    var ret = SC.beget(this) ;
    ret.parentBinding = this;
    if (fromPath !== undefined) ret = ret.from(fromPath) ;
    return ret ;
  },
  
  /**
    Returns a builder function for compatibility.  
  */
  builder: function() {
    var binding = this ;
    var ret = function(fromProperty) { return binding.beget().from(fromProperty); };
    ret.beget = function() { return binding.beget(); } ;
    return ret ;
  },
  
  /**
    This will set "from" property path to the specified value.  It will not
    attempt to resolve this property path to an actual object/property tuple
    until you connect the binding.

    The binding will search for the property path starting at the root level 
    unless you specify an alternate root object as the second paramter to this 
    method.  Alternatively, you can begin your property path with either "." or
    "*", which will use the root object of the to side be default.  This special
    behavior is used to support the high-level API provided by SC.Object.
    
    @param propertyPath {String|Tuple} A property path or tuple
    @param root {Object} optional root object to use when resolving the path.
    @returns {SC.Binding} this
  */
  from: function(propertyPath, root) {
    
    // if the propertyPath is null/undefined, return this.  This allows the
    // method to be called from other methods when the fromPath might be 
    // optional. (cf single(), multiple())
    if (!propertyPath) return this ;
    
    // beget if needed.
    var binding = (this === SC.Binding) ? this.beget() : this ;
    binding._fromPropertyPath = propertyPath ;
    binding._fromRoot = root ;
    binding._fromTuple = null ;
    return binding ;
  },
  
  /**
   This will set the "to" property path to the specified value.  It will not 
   attempt to reoslve this property path to an actual object/property tuple
   until you connect the binding.
    
    @param propertyPath {String|Tuple} A property path or tuple
    @param root {Object} optional root object to use when resolving the path.
    @returns {SC.Binding} this
  */
  to: function(propertyPath, root) {
    // beget if needed.
    var binding = (this === SC.Binding) ? this.beget() : this ;
    binding._toPropertyPath = propertyPath ;
    binding._toRoot = root ;
    binding._toTuple = null ; // clear out any existing one.
    return binding ;
  },

  /**
    Attempts to connect this binding instance so that it can receive and relay
    changes.  This method will raise an exception if you have not set the 
    from/to properties yet.
    
    @returns {SC.Binding} this
  */
  connect: function() {
    // If the binding is already connected, do nothing.
    if (this.isConnected) return this ;
    this.isConnected = YES ;
    this._connectionPending = YES ; // its connected but not really...    
    this._syncOnConnect = YES ;
    SC.Binding._connectQueue.add(this) ;
    return this; 
  },
  
  /** @private
    Actually connects the binding.  This is done at the end of the runloop
    to give you time to setup your entire object graph before the bindings 
    try to activate.
  */
  _connect: function() {
    if (!this._connectionPending) return; //nothing to do
    this._connectionPending = NO ;

    var path, root ;
    var bench = SC.BENCHMARK_BINDING_SETUP;

    if (bench) SC.Benchmark.start("SC.Binding.connect()");
    
    // try to connect the from side.
    // as a special behavior, if the from property path begins with either a 
    // . or * and the fromRoot is null, use the toRoot instead.  This allows 
    // for support for the SC.Object shorthand:
    //
    // contentBinding: "*owner.value"
    //
    path = this._fromPropertyPath; root = this._fromRoot ;
    if (SC.typeOf(path) === SC.T_STRING) {
      
      // if the first character is a '.', this is a static path.  make the 
      // toRoot the default root.
      if (path.indexOf('.') === 0) {
        path = path.slice(1);
        if (!root) root = this._toRoot ;
        
      // if the first character is a '*', then setup a tuple since this is a 
      // chained path.
      } else if (path.indexOf('*') === 0) {
        path = [this._fromRoot || this._toRoot, path.slice(1)] ;
        root = null ;
      }
    }
    SC.Observers.addObserver(path, this, this.fromPropertyDidChange, root) ;
    
    // try to connect the to side
    if (!this._oneWay) {
      path = this._toPropertyPath; root = this._toRoot ;
      SC.Observers.addObserver(path, this, this.toPropertyDidChange, root) ;  
    }

    if (bench) SC.Benchmark.end("SC.Binding.connect()");

    // now try to sync if needed
    if (this._syncOnConnect) {
      this._syncOnConnect = NO ;
      if (bench) SC.Benchmark.start("SC.Binding.connect().sync");
      this.sync();
      if (bench) SC.Benchmark.end("SC.Binding.connect().sync");
    }
  },
  
  /**
    Disconnects the binding instance.  Changes will no longer be relayed.  You
    will not usually need to call this method.
    
    @returns {SC.Binding} this
  */
  disconnect: function() {
    if (!this.isConnected) return this; // nothing to do.
    
    // if connection is still pending, just cancel
    if (this._connectionPending) {
      this._connectionPending = NO ;
      
    // connection is completed, disconnect.
    } else {
      SC.Observers.removeObserver(this._fromPropertyPath, this, this.fromPropertyDidChange, this._fromRoot) ;
      if (!this._oneWay) {
        SC.Observers.removeObserver(this._toPropertyPath, this, this.toPropertyDidChange, this._toRoot) ;
      }
    }
    
    this.isConnected = NO ;
    return this ;  
  },

  /**
    Invoked whenever the value of the "from" property changes.  This will mark
    the binding as dirty if the value has changed.
  */
  fromPropertyDidChange: function(target, key) {
    var v = target ? target.get(key) : null;

    //console.log("fromPropertyDidChange: %@ v = %@".fmt(this, v)) ;
    
    // if the new value is different from the current binding value, then 
    // schedule to register an update.
    if (v !== this._bindingValue) {

      this._setBindingValue(target, key) ;
      this._changePending = YES ;
      SC.Binding._changeQueue.add(this) ; // save for later.  
    }
  },

  /**
    Invoked whenever the value of the "to" property changes.  This will mark the
    binding as dirty only if:
    
    - the binding is not one way
    - the value does not match the stored transformedBindingValue
    
    if the value does not match the transformedBindingValue, then it will 
    become the new bindingValue. 
  */
  toPropertyDidChange: function(target, key) {
    if (this._oneWay) return; // nothing to do
    
    var v = target.get(key) ;
    
    // if the new value is different from the current binding value, then 
    // schedule to register an update.
    if (v !== this._transformedBindingValue) {
      this._setBindingValue(target, key) ;
      this._changePending = YES ;
      SC.Binding._changeQueue.add(this) ; // save for later.  
    }
  },
  
  /** @private
    Saves the source location for the binding value.  This will be used later
    to actually update the binding value.
  */
  _setBindingValue: function(source, key) {
    this._bindingSource = source;
    this._bindingKey    = key;
  },
  
  /** @private 
    Updates the binding value from the current binding source if needed.  This
    should be called just before using this._bindingValue.
  */
  _computeBindingValue: function() {
    var source = this._bindingSource,
        key    = this._bindingKey,
        v;
        
    if (!source) return ; // nothing to do
    this._bindingValue = v = source.getPath(key);
    
    // apply any transforms to get the to property value also
    var transforms = this._transforms;
    if (transforms) {
      var len = transforms.length ;
      for(var idx=0;idx<len;idx++) {
        var transform = transforms[idx] ;
        v = transform(v, this) ;
      }
    }

    // if error objects are not allowed, and the value is an error, then
    // change it to null.
    if (this._noError && SC.typeOf(v) === SC.T_ERROR) v = null ;
    
    this._transformedBindingValue = v;
  },
  
  _connectQueue: SC.CoreSet.create(),
  _alternateConnectQueue: SC.CoreSet.create(),
  _changeQueue: SC.CoreSet.create(),
  _alternateChangeQueue: SC.CoreSet.create(),
  _changePending: NO,

  /**
    Call this method on SC.Binding to flush all bindings with changed pending.
    
    @returns {Boolean} YES if changes were flushed.
  */
  flushPendingChanges: function() {
    
    // don't allow flushing more than one at a time
    if (this._isFlushing) return NO; 
    this._isFlushing = YES ;
    SC.Observers.suspendPropertyObserving();

    var didFlush = NO ;
    var log = SC.LOG_BINDINGS ;
    
    // connect any bindings
    var queue, binding ;
    while((queue = this._connectQueue).length >0) {
      this._connectQueue = this._alternateConnectQueue ;
      this._alternateConnectQueue = queue ;
      while(binding = queue.pop()) binding._connect() ;
    }
    
    // loop through the changed queue...
    while ((queue = this._changeQueue).length > 0) {
      if (log) console.log("Begin: Trigger changed bindings") ;
      
      didFlush = YES ;
      
      // first, swap the change queues.  This way any binding changes that
      // happen while we flush the current queue can be queued up.
      this._changeQueue = this._alternateChangeQueue ;
      this._alternateChangeQueue = queue ;
      
      // next, apply any bindings in the current queue.  This may cause 
      // additional bindings to trigger, which will end up in the new active 
      // queue.
      while(binding = queue.pop()) binding.applyBindingValue() ;
      
      // now loop back and see if there are additional changes pending in the
      // active queue.  Repeat this until all bindings that need to trigger 
      // have triggered.
      if (log) console.log("End: Trigger changed bindings") ;
    }
    
    // clean up
    this._isFlushing = NO ;
    SC.Observers.resumePropertyObserving();

    return didFlush ;
  },
  
  /**
    This method is called at the end of the Run Loop to relay the changed 
    binding value from one side to the other.
  */
  applyBindingValue: function() {
    this._changePending = NO ;

    // compute the binding targets if needed.
    this._computeBindingTargets() ;
    this._computeBindingValue();
    
    var v = this._bindingValue ;
    var tv = this._transformedBindingValue ;
    var bench = SC.BENCHMARK_BINDING_NOTIFICATIONS ;
    var log = SC.LOG_BINDINGS ; 
    
    // the from property value will always be the binding value, update if 
    // needed.
    if (!this._oneWay && this._fromTarget) {
      if (log) console.log("%@: %@ -> %@".fmt(this, v, tv)) ;
      if (bench) SC.Benchmark.start(this.toString() + "->") ;
      this._fromTarget.setPathIfChanged(this._fromPropertyKey, v) ;
      if (bench) SC.Benchmark.end(this.toString() + "->") ;
    }
    
    // update the to value with the transformed value if needed.
    if (this._toTarget) {
      if (log) console.log("%@: %@ <- %@".fmt(this, v, tv)) ;
      if (bench) SC.Benchmark.start(this.toString() + "<-") ;
      this._toTarget.setPathIfChanged(this._toPropertyKey, tv) ;
      if (bench) SC.Benchmark.start(this.toString() + "<-") ;
    }
  },

  /**
    Calling this method on a binding will cause it to check the value of the 
    from side of the binding matches the current expected value of the 
    binding. If not, it will relay the change as if the from side's value has 
    just changed.
    
    This method is useful when you are dynamically connecting bindings to a 
    network of objects that may have already been initialized.
  */
  sync: function() {

    // do nothing if not connected
    if (!this.isConnected) return this;
    
    // connection is pending, just note that we should sync also
    if (this._connectionPending) {
      this._syncOnConnect = YES ;
      
    // we are connected, go ahead and sync
    } else {
      this._computeBindingTargets() ;
      var target = this._fromTarget ;
      var key = this._fromPropertyKey ;
      if (!target || !key) return this ; // nothing to do

      // get the new value
      var v = target.getPath(key) ;

      // if the new value is different from the current binding value, then 
      // schedule to register an update.
      if (v !== this._bindingValue) {
        this._setBindingValue(target, key) ;
        this._changePending = YES ;
        SC.Binding._changeQueue.add(this) ; // save for later.  
      }
    }
    
    return this ;
  },
  
  // set if you call sync() when the binding connection is still pending.
  _syncOnConnect: NO,
  
  _computeBindingTargets: function() {
    if (!this._fromTarget) {

      var path, root, tuple ;
      
      // if the fromPropertyPath begins with a . or * then we may use the 
      // toRoot as the root object.  Similar code exists in connect() so if 
      // you make a change to one be sure to update the other.
      path = this._fromPropertyPath; root = this._fromRoot ;
      if (SC.typeOf(path) === SC.T_STRING) {
        
        // static path beginning with the toRoot
        if (path.indexOf('.') === 0) {
          path = path.slice(1) ; // remove the .
          if (!root) root = this._toRoot; // use the toRoot optionally
          
        // chained path beginning with toRoot.  Setup a tuple
        } else if (path.indexOf('*') === 0) {
          path = [root || this._toRoot, path.slice(1)];
          root = null ;
        }
      }
      
      tuple = SC.tupleForPropertyPath(path, root) ;
      if (tuple) {
        this._fromTarget = tuple[0]; this._fromPropertyKey = tuple[1] ;
      }
    }

    if (!this._toTarget) {
      path = this._toPropertyPath; root = this._toRoot ;
      tuple = SC.tupleForPropertyPath(path, root) ;
      if (tuple) {
        this._toTarget = tuple[0]; this._toPropertyKey = tuple[1] ;
      }
    }
  },
  
  /**
    Configures the binding as one way.  A one-way binding will relay changes
    on the "from" side to the "to" side, but not the other way around.  This
    means that if you change the "to" side directly, the "from" side may have 
    a different value.
    
    @param fromPath {String} optional from path to connect.
    @param aFlag {Boolean} Optionally pass NO to set the binding back to two-way
    @returns {SC.Binding} this
  */
  oneWay: function(fromPath, aFlag) {
    
    // If fromPath is a bool but aFlag is undefined, swap.
    if ((aFlag === undefined) && (SC.typeOf(fromPath) === SC.T_BOOL)) {
      aFlag = fromPath; fromPath = null ;
    }
    
    // beget if needed.
    var binding = this.from(fromPath) ;
    if (binding === SC.Binding) binding = binding.beget() ;
    binding._oneWay = (aFlag === undefined) ? YES : aFlag ;
    return binding ;
  },
  
  /**
    Adds the specified transform function to the array of transform functions.
    
    The function you pass must have the following signature:
    
    {{{
      function(value) {} ;
    }}}
    
    It must return either the transformed value or an error object.  
        
    Transform functions are chained, so they are called in order.  If you are
    extending a binding and want to reset the transforms, you can call
    resetTransform() first.
    
    @param transformFunc {Function} the transform function.
    @returns {SC.Binding} this
  */
  transform: function(transformFunc) {
    var binding = (this === SC.Binding) ? this.beget() : this ;
    var t = binding._transforms ;
    
    // clone the transform array if this comes from the parent
    if (t && (t === binding.parentBinding._transform)) {
      t = binding._transforms = t.slice() ;
    }
    
    // create the transform array if needed.
    if (!t) t = binding._transforms = [] ;
    
    // add the transform function
    t.push(transformFunc) ;
    return binding;
  },
  
  /**
    Resets the transforms for the binding.  After calling this method the 
    binding will no longer transform values.  You can then add new transforms
    as needed.
  
    @returns {SC.Binding} this
  */
  resetTransforms: function() {
    var binding = (this === SC.Binding) ? this.beget() : this ;
    binding._transforms = null ; return binding ;
  },
  
  /**
    Specifies that the binding should not return error objects.  If the value
    of a binding is an Error object, it will be transformed to a null value
    instead.
    
    Note that this is not a transform function since it will be called at the
    end of the transform chain.
    
    @param fromPath {String} optional from path to connect.
    @param aFlag {Boolean} optionally pass NO to allow error objects again.
    @returns {SC.Binding} this
  */
  noError: function(fromPath, aFlag) {
    // If fromPath is a bool but aFlag is undefined, swap.
    if ((aFlag === undefined) && (SC.typeOf(fromPath) === SC.T_BOOL)) {
      aFlag = fromPath; fromPath = null ;
    }
    
    // beget if needed.
    var binding = this.from(fromPath) ;
    if (binding === SC.Binding) binding = binding.beget() ;
    binding._noError = (aFlag === undefined) ? YES : aFlag ;
    return binding ;
  },
  
  /**
    Adds a transform to the chain that will allow only single values to pass.
    This will allow single values, nulls, and error values to pass through.  If
    you pass an array, it will be mapped as so:
    
    {{{
      [] => null
      [a] => a
      [a,b,c] => Multiple Placeholder
    }}}
    
    You can pass in an optional multiple placeholder or it will use the 
    default.
    
    Note that this transform will only happen on forwarded valued.  Reverse
    values are send unchanged.
    
    @param fromPath {String} from path or null
    @param placeholder {Object} optional placeholder value.
    @returns {SC.Binding} this
  */
  single: function(fromPath, placeholder) {
    if (placeholder === undefined) {
      placeholder = SC.MULTIPLE_PLACEHOLDER ;
    }
    return this.from(fromPath).transform(function(value, isForward) {
      if (value && value.isEnumerable) {
        var len = value.get('length');
        value = (len>1) ? placeholder : (len<=0) ? null : value.firstObject();
      }
      return value ;
    }) ;
  },
  
  /** 
    Adds a transform that will return the placeholder value if the value is 
    null, undefined, an empty array or an empty string.  See also notNull().
    
    @param fromPath {String} from path or null
    @param placeholder {Object} optional placeholder.
    @returns {SC.Binding} this
  */
  notEmpty: function(fromPath, placeholder) {
    if (placeholder === undefined) placeholder = SC.EMPTY_PLACEHOLDER ;
    return this.from(fromPath).transform(function(value, isForward) {
      if (SC.none(value) || (value === '') || (SC.isArray(value) && value.length === 0)) {
        value = placeholder ;
      }
      return value ;
    }) ;
  },
  
  /**
    Adds a transform that will return the placeholder value if the value is
    null.  Otherwise it will passthrough untouched.  See also notEmpty().
    
    @param fromPath {String} from path or null
    @param placeholder {Object} optional placeholder;
    @returns {SC.Binding} this
  */
  notNull: function(fromPath, placeholder) {
    if (placeholder === undefined) placeholder = SC.EMPTY_PLACEHOLDER ;
    return this.from(fromPath).transform(function(value, isForward) {
      if (SC.none(value)) value = placeholder ;
      return value ;
    }) ;
  },

  /** 
    Adds a transform that will convert the passed value to an array.  If 
    the value is null or undefined, it will be converted to an empty array.

    @param fromPath {String} optional from path
    @returns {SC.Binding} this
  */
  multiple: function(fromPath) {
    return this.from(fromPath).transform(function(value) {
      if (!SC.isArray(value)) value = (value == null) ? [] : [value] ;
      return value ;
    }) ;
  },
  
  /**
    Adds a transform to convert the value to a bool value.  If the value is
    an array it will return YES if array is not empty.  If the value is a string
    it will return YES if the string is not empty.
  
    @param fromPath {String} optional from path
    @returns {SC.Binding} this
  */
  bool: function(fromPath) {
    return this.from(fromPath).transform(function(v) {
      var t = SC.typeOf(v) ;
      if (t === SC.T_ERROR) return v ;
      return (t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v ;
    }) ;
  },
  
  /**
    Adds a transform to convert the value to the inverse of a bool value.  This
    uses the same transform as bool() but inverts it.
    
    @param fromPath {String} optional from path
    @returns {SC.Binding} this
  */
  not: function(fromPath) {
    return this.from(fromPath).transform(function(v) {
      var t = SC.typeOf(v) ;
      if (t === SC.T_ERROR) return v ;
      return !((t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v) ;
    }) ;
  },
  
  /**
    Adds a transform that will return YES if the value is null, NO otherwise.
    
    @returns {SC.Binding} this
  */
  isNull: function(fromPath) {
    return this.from(fromPath).transform(function(v) { 
      var t = SC.typeOf(v) ;
      return (t === SC.T_ERROR) ? v : SC.none(v) ;
    });
  },
  
  toString: function() {
    var from = this._fromRoot ? "<%@>:%@".fmt(this._fromRoot,this._fromPropertyPath) : this._fromPropertyPath;

    var to = this._toRoot ? "<%@>:%@".fmt(this._toRoot,this._toPropertyPath) : this._toPropertyPath;
    
    var oneWay = this._oneWay ? '[oneWay]' : '';
    return "SC.Binding%@(%@ -> %@)%@".fmt(SC.guidFor(this), from, to, oneWay);
  }  
} ;

/** 
  Shorthand method to define a binding.  This is the same as calling:
  
  {{{
    SC.binding(path) = SC.Binding.from(path)
  }}}
*/
SC.binding = function(path, root) { return SC.Binding.from(path,root); } ;

;});
/* >>>>>>>>>> BEGIN source/system/cookie.js */
tiki.module('sproutcore/runtime:system/cookie',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:system/object');// ==========================================================================
// SC.Cookie
// Copyright: ©2009 Sprout Systems, Inc. and contributors.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import system/object";
"export package";

/** @class
  
  Allows for easier handling of the document.cookie object. To create a cookie,
  simply call SC.Cookie.create. To retrieve a cookie, use SC.Cookie.find.
  Cookies are not added to document.cookie, which SC.Cookie.find uses, until you
  have called SC.Cookie#write.
  
  Heavy inspiration from the
  {@link <a href="http://plugins.jquery.com/project/cookie">jQuery cookie plugin</a>}.
  
  @extends SC.Object
  @since Sproutcore 1.0
  @author Colin Campbell
*/

SC.Cookie = SC.Object.extend({
  
  // ..........................................................
  // PROPERTIES
  //   
  
  /**
    The name of the cookie
    
    @property {String}
  */
  name: null,
  
  /**
    The value of the cookie
    
    @property {String}
  */
  value: '',
  
  /**
    Amount of time until the cookie expires. Set to -1 in order to delete the cookie.
    
    @property {Integer|SC.DateTime|Date}
  */
  expires: null,
  
  /**
    The value of the path atribute of the cookie (default: path of page that created the cookie).
    
    @property {String}
  */
  path: null,
  
  /**
    The value of the domain attribute of the cookie (default: domain of page that created the cookie).
    
    @property {String}
  */
  domain: null,
  
  /**
    If true, the secure attribute of the cookie will be set and the cookie transmission will
    require a secure protocol (like HTTPS).
    
    @property {Boolean}
  */
  secure: NO,
  
  /**
    Walk like a duck
    
    @property {Boolean}
    @isReadOnly
  */
  isCookie: YES,
  
  // ..........................................................
  // METHODS
  // 
  
  /**
    Sets SC.Cookie#expires to -1, which destroys the cookie.
  */
  destroy: function() {
    this.set('expires', -1);
    this.write();
    
    arguments.callee.base.apply(this,arguments);
  },
  
  /**
    Writes this SC.Cookie to document.cookie and adds it to SC.Cookie collection. To find this
    cookie later, or on reload, use SC.Cookie.find.
    
    @see SC.Cookie.find
  */
  write: function() {
    var name = this.get('name'),
        value = this.get('value'),
        expires = this.get('expires'),
        path = this.get('path'),
        domain = this.get('domain'),
        secure = this.get('secure');
    
    var expiresOutput = '';
    if (expires && (SC.typeOf(expires) === SC.T_NUMBER || (SC.DateTime && expires.get && expires.get('milliseconds')) || SC.typeOf(expires.toUTCString) === SC.T_FUNCTION)) {
      var date;
      if (SC.typeOf(expires) === SC.T_NUMBER) {
        date = new Date();
        date.setTime(date.getTime()+(expires*24*60*60*1000));
      }
      else if (SC.DateTime && expires.get && expires.get('milliseconds')) {
        date = new Date(expires.get('milliseconds'));
      }
      else if (SC.typeOf(expires.toUTCString) === SC.T_FUNCTION) {
        date = expires;
      }
      
      if (date) {
        expiresOutput = '; expires=' + date.toUTCString();
      }
    }
    
    var pathOutput = path ? '; path=' + path : '';
    var domainOutput = domain ? '; domain=' + domain : '';
    var secureOutput = secure ? '; secure' : '';
    
    document.cookie = [name, '=', encodeURIComponent(value), expiresOutput, pathOutput, domainOutput, secureOutput].join('');
    
    return this;
  }
  
});

SC.Cookie.mixin(
  /** @scope SC.Cookie */ {
  
  /**
    Finds a cookie that has been stored
    
    @param {String} name The name of the cookie
    @returns SC.Cookie object containing name and value of cookie
  */
  find: function(name) {
    if (document.cookie && document.cookie != '') {
      var cookies = document.cookie.split(';');
      for (var i = 0; i < cookies.length; i++) {
        var cookie = String(cookies[i]).trim();
        if (cookie.substring(0, name.length + 1) === (name + "=")) {
          return SC.Cookie.create({
            name: name,
            value: decodeURIComponent(cookie.substring(name.length + 1))
          });
        }
      }
    }
    return null;
  }
  
});;});
/* >>>>>>>>>> BEGIN source/system/enumerator.js */
tiki.module('sproutcore/runtime:system/enumerator',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"export package";

/**
  @class
  
  An object that iterates over all of the values in an object.  
  
  An instance of this object is returned everytime you call the 
  enumerator() method on an object that implements the SC.Enumerable mixin.
  
  Once you create an enumerator instance, you can call nextObject() on it
  until you can iterated through the entire collection.  Once you have
  exhausted the enumerator, you can reuse it if you want by calling reset().
  
  @since SproutCore 1.0
*/
SC.Enumerator = function(enumerableObject) {
  this.enumerable = enumerableObject ;
  this.reset() ;
  return this ;
} ;

SC.Enumerator.prototype = {
  
  /** 
    Returns the next object in the enumeration or undefined when complete.
    
    @returns {Object} the next object or undefined
  */
  nextObject: function() {
    var index = this._index ;
    var len = this._length;
    if (index >= len) return undefined ; // nothing to do
    
    // get the value
    var ret = this.enumerable.nextObject(index, this._previousObject, this._context) ;
    this._previousObject = ret ;
    this._index = index + 1 ;
    
    if (index >= len) {
      this._context = SC.Enumerator._pushContext(this._context); 
    }
    
    return ret ;
  },
  
  /**
    Resets the enumerator to the beginning.  This is a nice way to reuse
    an existing enumerator. 
    
    @returns {Object} this
  */
  reset: function() {
    var e = this.enumerable ;
    if (!e) throw SC.$error("Enumerator has been destroyed");
    this._length = e.get ? e.get('length') : e.length ;
    var len = this._length;
    this._index = 0;
    this._previousObject = null ;
    this._context = (len > 0) ? SC.Enumerator._popContext() : null;
  },
  
  /**
    Releases the enumerators enumerable object.  You cannot use this object
    anymore.  This is not often needed but it is useful when you need to 
    make sure memory gets cleared.
    
    @returns {Object} null 
  */
  destroy: function() {
    this.enumerable = this._length = this._index = this._previousObject = this._context = null;
  }
  
} ;

/**
  Use this method to manually create a new Enumerator object.  Usually you
  will not access this method directly but instead call enumerator() on the
  item you want to enumerate.

  @param {SC.Enumerable}  enumerableObject enumerable object.
  @returns {SC.Enumerator} the enumerator
*/
SC.Enumerator.create = function(enumerableObject) {
  return new SC.Enumerator(enumerableObject) ;
};

// Private context caching methods.  This avoids recreating lots of context
// objects.

SC.Enumerator._popContext = function() {
  var ret = this._contextCache ? this._contextCache.pop() : null ;
  return ret || {} ;
} ;

SC.Enumerator._pushContext = function(context) {
  this._contextCache = this._contextCache || [] ;
  var cache = this._contextCache;
  cache.push(context);
  return null ;
}; 

;});
/* >>>>>>>>>> BEGIN source/system/error.js */
tiki.module('sproutcore/runtime:system/error',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"export package";

/**
  @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).
  
  h2. Faking Error Objects
  
  You can actually make any object you want to be treated like an Error object
  by simply implementing two properties: isError and errorValue.  If you 
  set isError to YES, then calling SC.ok(obj) on your object will return NO.
  If isError is YES, then SC.val(obj) will return your errorValue property 
  instead of the receiver.
  
  @extends SC.Object
  @since SproutCore 1.0
*/
SC.Error = SC.Object.extend(
/** @scope SC.Error.prototype */ {
  
  /**
    error code.  Used to designate the error type.
    
    @property {Number}
  */
  code: -1,
  
  /**
    Human readable description of the error.  This can also be a non-localized
    key.
    
    @property {String}
  */
  message: '',
  
  /**
    The value the error represents.  This is used when wrapping a value inside
    of an error to represent the validation failure.
    
    @property {Object}
  */
  errorValue: null,
  
  /**
    The original error object.  Normally this will return the receiver.  
    However, sometimes another object will masquarade as an error; this gives
    you a way to get at the underyling error.
    
    @property {SC.Error}
  */
  errorObject: function() {
    return this;
  }.property().cacheable(),
  
  /**
    Human readable name of the item with the error.
    
    @property {String}
  */
  label: null,

  /** @private */
  toString: function() {
    return "SC.Error:%@:%@ (%@)".fmt(SC.guidFor(this), this.get('message'), this.get('code'));
  },
  
  /**
    Walk like a duck.
    
    @property {Boolean}
  */
  isError: YES
}) ;

/**
  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, value, code) {
  var opts = { message: description } ;
  if (label !== undefined) opts.label = label ;
  if (code !== undefined) opts.code = code ;
  if (value !== undefined) opts.errorValue = value ;
  return this.create(opts) ;
} ;

/**
  Shorthand form of the SC.Error.desc method.

  @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 = function(description, label, value, c) { 
  return SC.Error.desc(description,label, value, c); 
} ;

/**
  Returns YES if the passed value is an error object or false.
  
  @param {Object} ret object value
  @returns {Boolean}
*/
SC.ok = function(ret) {
  return (ret !== false) && !(ret && ret.isError);
};

/** @private */
SC.$ok = SC.ok;

/**
  Returns the value of an object.  If the passed object is an error, returns
  the value associated with the error; otherwise returns the receiver itself.
  
  @param {Object} obj the object
  @returns {Object} value 
*/
SC.val = function(obj) {
  if (obj && obj.isError) {
    return obj.get ? obj.get('errorValue') : null ; // Error has no value
  } else return obj ;
};

/** @private */
SC.$val = SC.val;

// STANDARD ERROR OBJECTS

/**
  Standard error code for errors that do not support multiple values.
  
  @property {Number}
*/
SC.Error.HAS_MULTIPLE_VALUES = -100 ;
;});
/* >>>>>>>>>> BEGIN source/system/index_set.js */
tiki.module('sproutcore/runtime:system/index_set',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:mixins/enumerable');require('sproutcore/runtime:mixins/observable');require('sproutcore/runtime:mixins/freezable');require('sproutcore/runtime:mixins/copyable');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import mixins/enumerable";
"import mixins/observable";
"import mixins/freezable";
"import mixins/copyable";
"export package";

/**
  @class 

  A collection of ranges.  You can use an IndexSet to keep track of non-
  continuous ranges of items in a parent array.  IndexSet's are used for 
  selection, for managing invalidation ranges and other data-propogation.

  h2. Examples
  
  {{{
    var set = SC.IndexSet.create(ranges) ;
    set.contains(index);
    set.add(index, length);
    set.remove(index, length);
    
    // uses a backing SC.Array object to return each index
    set.forEach(function(object) { .. })
    
    // returns the index
    set.forEachIndex(function(index) { ... });
    
    // returns ranges
    set.forEachRange(function(start, length) { .. });
  }}}

  h2. Implementation Notes

  An IndexSet stores indices on the object.  A positive value great than the
  index tells you the end of an occupied range.  A negative values tells you
  the end of an empty range.  A value less than the index is a search 
  accelerator.  It tells you the start of the nearest range.

  @extends SC.Enumerable 
  @extends SC.Observable
  @extends SC.Copyable
  @extends SC.Freezable
  @since SproutCore 1.0
*/
SC.IndexSet = SC.mixin({}, 
  SC.Enumerable, SC.Observable, SC.Freezable, SC.Copyable,
/** @scope SC.IndexSet.prototype */ {

  /** @private
    Walks a content array and copies its contents to a new array.  For large
    content arrays this is faster than using slice()
  */
  _sc_sliceContent: function(c) {
    if (c.length < 1000) return c.slice(); // use native when faster
    var cur = 0, ret = [], next = c[0];
    while(next !== 0) {
      ret[cur] = next ;
      cur = (next<0) ? (0-next) : next ;
      next = c[cur];
    }
    ret[cur] = 0;
    this._hint(0, cur, ret); // hints are not copied manually - add them
    return ret ;
  },
  
  /**
    To create a set, pass either a start and index or another IndexSet.
    
    @returns {SC.IndexSet}
  */
  create: function(start, length) { 
    var ret = SC.beget(this);
    ret.initObservable();
    
    // optimized method to clone an index set.
    if (start && start.isIndexSet) {
      ret._content = this._sc_sliceContent(start._content);
      ret.max = start.max;
      ret.length = start.length; 
      ret.source = start.source ;
      
    // otherwise just do a regular add
    } else {
      ret._content = [0];
      if (start !== undefined) ret.add(start, length);
    }
    return ret ;
  },

  /**
    Walk like a duck.
    
    @property {Boolean}
  */
  isIndexSet: YES,

  /**  @private 
    Internal setting determines the preferred skip size for hinting sets.
    
    @property {Number}
  */
  HINT_SIZE: 256,
  
  /**
    Total number of indexes contained in the set

    @property {Number}
  */
  length: 0,
  
  /**
    One greater than the largest index currently stored in the set.  This 
    is sometimes useful when determining the total range of items covering
    the index set.
    
    @property {Number}
  */
  max: 0,
  
  /**
    The first index included in the set or -1.
    
    @property {Number}
  */
  min: function() {  
    var content = this._content, 
        cur = content[0];
    return (cur === 0) ? -1 : (cur>0) ? 0 : Math.abs(cur);
    
  }.property('[]').cacheable(),
  
  /**
    Returns the first index in the set .
    
    @property {Number}
  */
  firstObject: function() {
    return (this.get('length')>0) ? this.get('min') : undefined;  
  }.property(),
  
  /** 
    Returns the starting index of the nearest range for the specified 
    index.
    
    @param {Number} index
    @returns {Number} starting index
  */
  rangeStartForIndex: function(index) {    
    var content = this._content,
        max     = this.get('max'),
        ret, next, accel;
    
    // fast cases
    if (index >= max) return max ;
    if (Math.abs(content[index]) > index) return index ; // we hit a border
    
    // use accelerator to find nearest content range
    accel = index - (index % SC.IndexSet.HINT_SIZE);
    ret = content[accel];
    if (ret<0 || ret>index) ret = accel;
    next = Math.abs(content[ret]);

    // now step forward through ranges until we find one that includes the
    // index.
    while (next < index) {
      ret = next ;
      next = Math.abs(content[ret]);
    }
    return ret ;
  },
    
  /**
    Returns YES if the passed index set contains the exact same indexes as 
    the receiver.  If you pass any object other than an index set, returns NO.
    
    @param {Object} obj another object.
    @returns {Boolean}
  */
  isEqual: function(obj) {
    
    // optimize for some special cases
    if (obj === this) return YES ;
    if (!obj || !obj.isIndexSet || (obj.max !== this.max) || (obj.length !== this.length)) return NO;

    // ok, now we need to actually compare the ranges of the two.
    var lcontent = this._content,
        rcontent = obj._content,
        cur      = 0,
        next     = lcontent[cur];
    
    do {
      if (rcontent[cur] !== next) return NO ;
      cur = Math.abs(next) ;
      next = lcontent[cur];
    } while (cur !== 0);
    return YES ;
  },
  
  /**
    Returns the first index in the set before the passed index or null if 
    there are no previous indexes in the set.
    
    @param {Number} index index to check
    @returns {Number} index or -1
  */
  indexBefore: function(index) {
    
    if (index===0) return -1; // fast path
    index--; // start with previous index
    
    var content = this._content, 
        max     = this.get('max'),
        start   = this.rangeStartForIndex(index);
    if (!content) return null;

    // loop backwards until we find a range that is in the set.
    while((start===max) || (content[start]<0)) {
      if (start === 0) return -1 ; // nothing before; just quit
      index = start -1 ;
      start = this.rangeStartForIndex(index);
    }
    
    return index;
  },

  /**
    Returns the first index in the set after the passed index or null if 
    there are no additional indexes in the set.
    
    @param {Number} index index to check
    @returns {Number} index or -1
  */
  indexAfter: function(index) {
    var content = this._content,
        max     = this.get('max'),
        start, next ;
    if (!content || (index>=max)) return -1; // fast path
    index++; // start with next index
    

    // loop forwards until we find a range that is in the set.
    start = this.rangeStartForIndex(index);
    next  = content[start];
    while(next<0) {
      if (next === 0) return -1 ; //nothing after; just quit
      index = start = Math.abs(next);
      next  = content[start];
    }
    
    return index;
  },
  
  /**
    Returns YES if the index set contains the named index
    
    @param {Number} start index or range
    @param {Number} length optional range length
    @returns {Boolean}
  */
  contains: function(start, length) {
    var content, cur, next, rstart, rnext;
    
    // normalize input
    if (length === undefined) { 
      if (start === null || start === undefined) return NO ;
      
      if (typeof start === SC.T_NUMBER) {
        length = 1 ;
        
      // if passed an index set, check each receiver range
      } else if (start && start.isIndexSet) {
        if (start === this) return YES ; // optimization

        content = start._content ;
        cur = 0 ;
        next = content[cur];
        while (next !== 0) {
          if ((next>0) && !this.contains(cur, next-cur)) return NO ;
          cur = Math.abs(next);
          next = content[cur];
        }
        return YES ;
        
      } else {
        length = start.length; 
        start = start.start;
      }
    }
    
    rstart = this.rangeStartForIndex(start);
    rnext  = this._content[rstart];
    
    return (rnext>0) && (rstart <= start) && (rnext >= (start+length));
  },

  /**
    Returns YES if the index set contains any of the passed indexes.  You
    can pass a single index, a range or an index set.
    
    @param {Number} start index, range, or IndexSet
    @param {Number} length optional range length
    @returns {Boolean}
  */
  intersects: function(start, length) {
    var content, cur, next, lim;
    
    // normalize input
    if (length === undefined) { 
      if (typeof start === SC.T_NUMBER) {
        length = 1 ;
        
      // if passed an index set, check each receiver range
      } else if (start && start.isIndexSet) {
        if (start === this) return YES ; // optimization

        content = start._content ;
        cur = 0 ;
        next = content[cur];
        while (next !== 0) {
          if ((next>0) && this.intersects(cur, next-cur)) return YES ;
          cur = Math.abs(next);
          next = content[cur];
        }
        return NO ;
        
      } else {
        length = start.length; 
        start = start.start;
      }
    }
    
    cur     = this.rangeStartForIndex(start);
    content = this._content;
    next    = content[cur];
    lim     = start + length;
    while (cur < lim) {
      if (next === 0) return NO; // no match and at end!
      if ((next > 0) && (next > start)) return YES ; // found a match
      cur = Math.abs(next);
      next = content[cur];
    }
    return NO ; // no match
  },

  /**
    Returns a new IndexSet without the passed range or indexes.   This is a
    convenience over simply cloning and removing.  Does some optimizations.
    
    @param {Number} start index, range, or IndexSet
    @param {Number} length optional range length
    @returns {SC.IndexSet} new index set
  */
  without: function(start, length) {
    if (start === this) return SC.IndexSet.create(); // just need empty set
    return this.clone().remove(start, length);
  },
  
  /**
    Replace the index set's current content with the passed index set.  This
    is faster than clearing the index set adding the values again.
    
    @param {Number} start index, Range, or another IndexSet
    @param {Number} length optional length of range. 
    @returns {SC.IndexSet} receiver
  */
  replace: function(start, length) {
    
    if (length === undefined) {
      if (typeof start === SC.T_NUMBER) {
        length = 1 ;
      } else if (start && start.isIndexSet) {
        this._content = this._sc_sliceContent(start._content);
        this.beginPropertyChanges()
          .set('max', start.max)
          .set('length', start.length)
          .set('source', start.source)
          .enumerableContentDidChange()
        .endPropertyChanges();
        return this ;
        
      } else {
        length = start.length;
        start  = start.start;
      }
    }
    
    var oldlen = this.length;
    this._content.length=1;
    this._content[0] = 0;
    this.length = this.max = 0 ; // reset without notifying since add()
    return this.add(start, length);
  },
  
  /**
    Adds the specified range of indexes to the set.  You can also pass another
    IndexSet to union the contents of the index set with the receiver.
    
    @param {Number} start index, Range, or another IndexSet
    @param {Number} length optional length of range. 
    @returns {SC.IndexSet} receiver
  */
  add: function(start, length) {
    
    if (this.isFrozen) throw SC.FROZEN_ERROR;
    
    var content, cur, next;

    // normalize IndexSet input
    if (start && start.isIndexSet) {
      
      content = start._content;
      
      if (!content) return this; // nothing to do

      cur = 0 ;
      next = content[0];
      while(next !== 0) {
        if (next>0) this.add(cur, next-cur);
        cur = next<0 ? 0-next : next;
        next = content[cur];
      }
      return this ;
      
    } else if (length === undefined) { 
      
      if (start === null || start === undefined) {
        return this; // nothing to do
      } else if (typeof start === SC.T_NUMBER) {
        length = 1 ;
      } else {
        length = start.length; 
        start = start.start;
      }
    } else if (length === null) length = 1 ;

    // if no length - do nothing.
    if (length <= 0) return this;
    
    // special case - appending to end of set
    var max     = this.get('max'),
        oldmax  = max,
        delta, value ;

    content = this._content ;
    
    if (start === max) {

      // if adding to the end and the end is in set, merge.
      if (start > 0) {
        cur = this.rangeStartForIndex(start-1);
        next = content[cur];
        
        // just extend range at end
        if (next > 0) { 
          delete content[max]; // no 0
          content[cur] = max = start + length ;
          start = cur ;
          
        // previous range was not in set, just tack onto the end
        } else {
          content[max] = max = start + length;
        }
      } else {
        content[start] = max = length;
      }
      
      content[max] = 0 ;
      this.set('max', max);
      this.set('length', this.length + length) ;
      length = max - start ;
      
    } else if (start > max) {
      content[max] = 0-start; // empty!
      content[start] = start+length ;
      content[start+length] = 0; // set end
      this.set('max', start + length) ;
      this.set('length', this.length + length) ;
      
      // affected range goes from starting range to end of content.
      length = start + length - max ;
      start = max ;
      
    // otherwise, merge into existing range
    } else {

      // find nearest starting range.  split or join that range
      cur   = this.rangeStartForIndex(start);
      next  = content[cur];
      max   = start + length ;
      delta = 0 ;

      // we are right on a boundary and we had a range or were the end, then
      // go back one more.
      if ((start>0) && (cur === start) && (next <= 0)) {
        cur = this.rangeStartForIndex(start-1);
        next = content[cur] ;
      }
      
      // previous range is not in set.  splice it here
      if (next < 0) { 
        content[cur] = 0-start ;
        
        // if previous range extends beyond this range, splice afterwards also
        if (Math.abs(next) > max) {
          content[start] = 0-max;
          content[max] = next ;
        } else content[start] = next;
        
      // previous range is in set.  merge the ranges
      } else {
        start = cur ;
        if (next > max) {
          // delta -= next - max ;
          max = next ;
        }
      }
      
      // at this point there should be clean starting point for the range.
      // just walk the ranges, adding up the length delta and then removing
      // the range until we find a range that passes last
      cur = start;
      while (cur < max) {
        // get next boundary.  splice if needed - if value is 0, we are at end
        // just skip to last
        value = content[cur];
        if (value === 0) {
          content[max] = 0;
          next = max ;
          delta += max - cur ;
        } else {
          next  = Math.abs(value);
          if (next > max) {
            content[max] = value ;
            next = max ;
          }

          // ok, cur range is entirely inside top range.  
          // add to delta if needed
          if (value < 0) delta += next - cur ;
        }

        delete content[cur] ; // and remove range
        cur = next;
      }
      
      // cur should always === last now.  if the following range is in set,
      // merge in also - don't adjust delta because these aren't new indexes
      if ((cur = content[max]) > 0) {
        delete content[max];     
        max = cur ;
      }

      // finally set my own range.
      content[start] = max ;
      if (max > oldmax) this.set('max', max) ;

      // adjust length
      this.set('length', this.get('length') + delta);
      
      // compute hint range
      length = max - start ;
    }
    
    this._hint(start, length);
    if (delta !== 0) this.enumerableContentDidChange();
    return this;
  },

  /**
    Removes the specified range of indexes from the set
    
    @param {Number} start index, Range, or IndexSet
    @param {Number} length optional length of range. 
    @returns {SC.IndexSet} receiver
  */
  remove: function(start, length) {

    if (this.isFrozen) throw SC.FROZEN_ERROR;
    
    // normalize input
    if (length === undefined) { 
      if (start === null || start === undefined) {
        return this; // nothing to do

      } else if (typeof start === SC.T_NUMBER) {
        length = 1 ;
      
      // if passed an index set, just add each range in the index set.
      } else if (start.isIndexSet) {
        start.forEachRange(this.remove, this);
        return this;

      } else {
        length = start.length; 
        start = start.start;
      }
    }

    if (length <= 0) return this; // nothing to do
    
    // special case - appending to end of set
    var max     = this.get('max'),
        oldmax  = max,
        content = this._content,
        cur, next, delta, value, last ;

    // if we're past the end, do nothing.
    if (start >= max) return this;

    // find nearest starting range.  split or join that range
    cur   = this.rangeStartForIndex(start);
    next  = content[cur];
    last  = start + length ;
    delta = 0 ;

    // we are right on a boundary and we had a range or were the end, then
    // go back one more.
    if ((start>0) && (cur === start) && (next > 0)) {
      cur = this.rangeStartForIndex(start-1);
      next = content[cur] ;
    }
    
    // previous range is in set.  splice it here
    if (next > 0) { 
      content[cur] = start ;
      
      // if previous range extends beyond this range, splice afterwards also
      if (next > last) {
        content[start] = last;
        content[last] = next ;
      } else content[start] = next;
      
    // previous range is not in set.  merge the ranges
    } else {
      start = cur ;
      next  = Math.abs(next);
      if (next > last) {
        last = next ;
      }
    }
    
    // at this point there should be clean starting point for the range.
    // just walk the ranges, adding up the length delta and then removing
    // the range until we find a range that passes last
    cur = start;
    while (cur < last) {
      // get next boundary.  splice if needed - if value is 0, we are at end
      // just skip to last
      value = content[cur];
      if (value === 0) {
        content[last] = 0;
        next = last ;

      } else {
        next  = Math.abs(value);
        if (next > last) {
          content[last] = value ;
          next = last ;
        }

        // ok, cur range is entirely inside top range.  
        // add to delta if needed
        if (value > 0) delta += next - cur ;
      }

      delete content[cur] ; // and remove range
      cur = next;
    }
    
    // cur should always === last now.  if the following range is not in set,
    // merge in also - don't adjust delta because these aren't new indexes
    if ((cur = content[last]) < 0) {
      delete content[last];     
      last = Math.abs(cur) ;
    }

    // set my own range - if the next item is 0, then clear it.
    if (content[last] === 0) {
      delete content[last];
      content[start] = 0 ;
      this.set('max', start); //max has changed
      
    } else {
      content[start] = 0-last ;
    }

    // adjust length
    this.set('length', this.get('length') - delta);
    
    // compute hint range
    length = last - start ;
    
    this._hint(start, length);
    if (delta !== 0) this.enumerableContentDidChange();
    return this;
  },
    
  /** @private 
    iterates through a named range, setting hints every HINT_SIZE indexes 
    pointing to the nearest range start.  The passed range must start on a
    range boundary.  It can end anywhere.
  */
  _hint: function(start, length, content) {
    if (content === undefined) content = this._content;
    
    var skip    = SC.IndexSet.HINT_SIZE,
        next    = Math.abs(content[start]), // start of next range
        loc     = start - (start % skip) + skip, // next hint loc
        lim     = start + length ; // stop
        
    while (loc < lim) {
      // make sure we are in current rnage
      while ((next !== 0) && (next <= loc)) {
        start = next ; 
        next  = Math.abs(content[start]) ;
      }
      
      // past end
      if (next === 0) {
        delete content[loc];

      // do not change if on actual boundary
      } else if (loc !== start) {
        content[loc] = start ;  // set hint
      }
      
      loc += skip;
    }
  },

  /**
    Clears the set 
  */
  clear: function() {
    if (this.isFrozen) throw SC.FROZEN_ERROR;
    
    var oldlen = this.length;
    this._content.length=1;
    this._content[0] = 0;
    this.set('length', 0).set('max', 0);
    if (oldlen > 0) this.enumerableContentDidChange();
  },
  
  /**
    Add all the ranges in the passed array.
  */
  addEach: function(objects) {
    if (this.isFrozen) throw SC.FROZEN_ERROR;

    this.beginPropertyChanges();
    var idx = objects.get('length') ;
    if (objects.isSCArray) {
      while(--idx >= 0) this.add(objects.objectAt(idx)) ;
    } else if (objects.isEnumerable) {
      objects.forEach(function(idx) { this.add(idx); }, this);
    }
    this.endPropertyChanges();
    
    return this ;
  },  

  /**
    Removes all the ranges in the passed array.
  */
  removeEach: function(objects) {
    if (this.isFrozen) throw SC.FROZEN_ERROR;

    this.beginPropertyChanges();
    
    var idx = objects.get('length') ;
    if (objects.isSCArray) {
      while(--idx >= 0) this.remove(objects.objectAt(idx)) ;
    } else if (objects.isEnumerable) {
      objects.forEach(function(idx) { this.remove(idx); }, this); 
    }
    
    this.endPropertyChanges();
    
    return this ;
  },  

  /**
   Clones the set into a new set.  
  */
  clone: function() {
    return SC.IndexSet.create(this);    
  },
  
  /**
    Returns a string describing the internal range structure.  Useful for
    debugging.
    
    @returns {String}
  */
  inspect: function() {
    var content = this._content,
        len     = content.length,
        idx     = 0,
        ret     = [],
        item;
    
    for(idx=0;idx<len;idx++) {
      item = content[idx];
      if (item !== undefined) ret.push("%@:%@".fmt(idx,item));      
    }
    return "SC.IndexSet<%@>".fmt(ret.join(' , '));
  },
  
  /** 
    Invoke the callback, passing each occuppied range instead of each 
    index.  This can be a more efficient way to iterate in some cases.  The
    callback should have the signature:
    
    {{{
      callback(start, length, indexSet, source) { ... }
    }}}
    
    If you pass a target as a second option, the callback will be called in
    the target context.
    
    @param {Function} callback the iterator callback
    @param {Object} target the target
    @returns {SC.IndexSet} receiver
  */
  forEachRange: function(callback, target) {
    var content = this._content,
        cur     = 0,
        next    = content[cur],
        source  = this.source;

    if (target === undefined) target = null ;
    while (next !== 0) {
      if (next > 0) callback.call(target, cur, next - cur, this, source);
      cur  = Math.abs(next);
      next = content[cur];
    }
    
    return this ;
  },    
  
  /**
    Invokes the callback for each index within the passed start/length range.
    Otherwise works just like regular forEach().
    
    @param {Number} start starting index
    @param {Number} length length of range
    @param {Function} callback
    @param {Object} target
    @returns {SC.IndexSet} receiver
  */
  forEachIn: function(start, length, callback, target) {
    var content = this._content,
        cur     = 0,
        idx     = 0,
        lim     = start + length,
        source  = this.source,
        next    = content[cur];

    if (target === undefined) target = null ;
    while (next !== 0) {
      if (cur < start) cur = start ; // skip forward 
      while((cur < next) && (cur < lim)) { 
        callback.call(target, cur++, idx++, this, source); 
      }
      
      if (cur >= lim) {
        cur = next = 0 ;
      } else {
        cur  = Math.abs(next);
        next = content[cur];
      }
    }
    return this ;
  },

  /**
    Total number of indexes within the specified range.
    
    @param {Number|SC.IndexSet} start index, range object or IndexSet
    @param {Number} length optional range length
    @returns {Number} count of indexes
  */
  lengthIn: function(start, length) {

    var ret = 0 ;
    
    // normalize input
    if (length === undefined) { 
      if (start === null || start === undefined) {
        return 0; // nothing to do

      } else if (typeof start === SC.T_NUMBER) {
        length = 1 ;
        
      // if passed an index set, just add each range in the index set.
      } else if (start.isIndexSet) {
        start.forEachRange(function(start, length) { 
          ret += this.lengthIn(start, length);
        }, this);
        return ret;
        
      } else {
        length = start.length; 
        start = start.start;
      }
    }

    // fast path
    if (this.get('length') === 0) return 0;
    
    var content = this._content,
        cur     = 0,
        next    = content[cur],
        lim     = start + length ;

    while (cur<lim && next !== 0) {
      if (next>0) {
        ret += (next>lim) ? lim-cur : next-cur;
      }
      cur  = Math.abs(next);
      next = content[cur];
    }
    
    return ret ;
  },
  
  // ..........................................................
  // OBJECT API
  // 
  
  /**
    Optionally set the source property on an index set and then you can 
    iterate over the actual object values referenced by the index set.  See
    indexOf(), lastIndexOf(), forEachObject(), addObject() and removeObject().
  */
  source: null,
  
  /**
    Returns the first index in the set that matches the passed object.  You
    must have a source property on the set for this to work.
    
    @param {Object} object the object to check 
    @param {Number} startAt optional starting point
    @returns {Number} found index or -1 if not in set
  */
  indexOf: function(object, startAt) {
    var source  = this.source;
    if (!source) throw "%@.indexOf() requires source".fmt(this);
    
    var len     = source.get('length'),
        
        // start with the first index in the set
        content = this._content,
        cur     = content[0]<0 ? Math.abs(content[0]) : 0,
        idx ;
        
    while(cur>=0 && cur<len) {
      idx = source.indexOf(object, cur);
      if (idx<0) return -1 ; // not found in source
      if (this.contains(idx)) return idx; // found in source and in set.
      cur = idx+1;
    } 
    
    return -1; // not found
  },

  /**
    Returns the last index in the set that matches the passed object.  You
    must have a source property on the set for this to work.
    
    @param {Object} object the object to check 
    @param {Number} startAt optional starting point
    @returns {Number} found index or -1 if not in set
  */
  lastIndexOf: function(object, startAt) {
    var source  = this.source;
    if (!source) throw "%@.lastIndexOf() requires source".fmt(this);
    
    // start with the last index in the set
    var len     = source.get('length'),
        cur     = this.max-1,
        idx ;

    if (cur >= len) cur = len-1;
    while (cur>=0) {
      idx = source.lastIndexOf(object, cur);
      if (idx<0) return -1 ; // not found in source
      if (this.contains(idx)) return idx; // found in source and in set.
      cur = idx+1;
    } 
    
    return -1; // not found
  },
  
  /**
    Iterates through the objects at each index location in the set.  You must
    have a source property on the set for this to work.  The callback you pass
    will be invoked for each object in the set with the following signature:
    
    {{{
      function callback(object, index, source, indexSet) { ... }
    }}}
    
    If you pass a target, it will be used when the callback is called.
    
    @param {Function} callback function to invoke.  
    @param {Object} target optional content. otherwise uses window
    @returns {SC.IndexSet} receiver
  */ 
  forEachObject: function(callback, target) {
    var source  = this.source;
    if (!source) throw "%@.forEachObject() requires source".fmt(this);

    var content = this._content,
        cur     = 0,
        idx     = 0,
        next    = content[cur];
        
    if (target === undefined) target = null ;
    while (next !== 0) {
      
      while(cur < next) { 
        callback.call(target, source.objectAt(cur), cur, source, this); 
        cur++;
      }
      
      cur  = Math.abs(next);
      next = content[cur];
    }
    return this ;
  },
  
  /**
    Adds all indexes where the object appears to the set.  If firstOnly is 
    passed, then it will find only the first index and add it.  If  you know
    the object only appears in the source array one time, firstOnly may make
    this method faster.
    
    Requires source to work.
    
    @param {Object} object the object to add
    @returns {SC.IndexSet} receiver
  */
  addObject: function(object, firstOnly) {
    var source  = this.source;
    if (!source) throw "%@.addObject() requires source".fmt(this);

    var len = source.get('length'),
        cur = 0, idx;
        
    while(cur>=0 && cur<len) {
      idx = source.indexOf(object, cur);
      if (idx >= 0) { 
        this.add(idx);
        if (firstOnly) return this ;
        cur = idx++;
      } else return this ;
    }
    return this ;    
  },

  /**
    Adds any indexes matching the passed objects.  If firstOnly is passed, 
    then only finds the first index for each object.
    
    @param {SC.Enumerable} objects the objects to add
    @returns {SC.IndexSet} receiver
  */
  addObjects: function(objects, firstOnly) {
    objects.forEach(function(object) {
      this.addObject(object, firstOnly);
    }, this);
    return this;
  },
  
  /**
    Removes all indexes where the object appears to the set.  If firstOnly is 
    passed, then it will find only the first index and add it.  If  you know
    the object only appears in the source array one time, firstOnly may make
    this method faster.
    
    Requires source to work.
    
    @param {Object} object the object to add
    @returns {SC.IndexSet} receiver
  */
  removeObject: function(object, firstOnly) {
    var source  = this.source;
    if (!source) throw "%@.removeObject() requires source".fmt(this);

    var len = source.get('length'),
        cur = 0, idx;
        
    while(cur>=0 && cur<len) {
      idx = source.indexOf(object, cur);
      if (idx >= 0) { 
        this.remove(idx);
        if (firstOnly) return this ;
        cur = idx+1;
      } else return this ;
    }
    return this ;    
  },

  /**
    Removes any indexes matching the passed objects.  If firstOnly is passed, 
    then only finds the first index for each object.
    
    @param {SC.Enumerable} objects the objects to add
    @returns {SC.IndexSet} receiver
  */
  removeObjects: function(objects, firstOnly) {
    objects.forEach(function(object) {
      this.removeObject(object, firstOnly);
    }, this);
    return this;
  },
  
  
  // .......................................
  // PRIVATE 
  //

  /** 
    Usually observing notifications from IndexSet are not useful, so 
    supress them by default.
    
    @property {Boolean}
  */
  LOG_OBSERVING: NO,
  
  /** @private - optimized call to forEach() */
  forEach: function(callback, target) {
    var content = this._content,
        cur     = 0,
        idx     = 0,
        source  = this.source,
        next    = content[cur];

    if (target === undefined) target = null ;
    while (next !== 0) {
      while(cur < next) { 
        callback.call(target, cur++, idx++, this, source); 
      }
      cur  = Math.abs(next);
      next = content[cur];
    }
    return this ;
  },
  
  /** @private - support iterators */
  nextObject: function(ignore, idx, context) {
    var content = this._content,
        next    = context.next,
        max     = this.get('max'); // next boundary
    
    // seed.
    if (idx === null) {
      idx = next = 0 ;

    } else if (idx >= max) {
      delete context.next; // cleanup context
      return null ; // nothing left to do

    } else idx++; // look on next index
    
    // look for next non-empty range if needed.
    if (idx === next) {
      do { 
        idx = Math.abs(next);
        next = content[idx];
      } while(next < 0);
      context.next = next;
    }
    
    return idx;
  },
  
  toString: function() {
    var str = [];
    this.forEachRange(function(start, length) {
      str.push(length === 1 ? start : "%@..%@".fmt(start, start + length - 1));
    }, this);
    return "SC.IndexSet<%@>".fmt(str.join(',')) ;
  },
  
  max: 0

}) ;

SC.IndexSet.slice = SC.IndexSet.copy = SC.IndexSet.clone ;
SC.IndexSet.EMPTY = SC.IndexSet.create().freeze();
;});
/* >>>>>>>>>> BEGIN source/system/logger.js */
tiki.module('sproutcore/runtime:system/logger',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:system/object');// ==========================================================================
// SC.Logger
// ==========================================================================

"import core";
"import system/object";
"export package";

/**
  If {@link SC.Logger.format} is true, this delimiter will be put between arguments.
  
  @property {String}
*/
SC.LOGGER_LOG_DELIMITER = ", ";

/**
  If {@link SC.Logger.error} falls back onto {@link SC.Logger.log}, this will be
  prepended to the output.
  
  @property {String}
*/
SC.LOGGER_LOG_ERROR = "ERROR: ";

/**
  If {@link SC.Logger.info} falls back onto {@link SC.Logger.log}, this will be
  prepended to the output.
  
  @property {String}
*/
SC.LOGGER_LOG_INFO = "INFO: ";

/**
  If {@link SC.Logger.warn} falls back onto {@link SC.Logger.log}, this will be
  prepended to the output.
  
  @property {String}
*/
SC.LOGGER_LOG_WARN = "WARNING: ";

/** @class
  
  Object to allow for safe logging actions, such as using the browser console.
  
  The FireFox plugin Firebug was used as a function reference. Please see
  {@link <a href="http://getfirebug.com/logging.html">Firebug Logging Reference</a>}
  for further information.
  
  @author Colin Campbell
  @extends SC.Object
  @since Sproutcore 1.0
  @see <a href="http://getfirebug.com/logging.html">Firebug Logging Reference</a>
*/
SC.Logger = SC.Object.create({ 
  
  // ..........................................................
  // PROPERTIES
  //   
  
  /**
    Computed property that checks for the existence of the reporter object.
    
    @property {Boolean}
  */
  exists: function() {
    return typeof(this.get('reporter')) !== 'undefined' && this.get('reporter') != null;
  }.property('reporter').cacheable(),
  
  /**
    If console.log does not exist, SC.Logger will use window.alert instead.
    
    This property is only used inside {@link SC.Logger.log}. If fallBackOnLog is
    false and you call a different function, an alert will not be opened.
    
    @property {Boolean}
  */
  fallBackOnAlert: NO,
  
  /**
    If some function, such as console.dir, does not exist,
    SC.Logger will try console.log if this is true.
    
    @property {Boolean}
  */
  fallBackOnLog: YES,
  
  /**
    Whether or not to format multiple arguments together
    or let the browser deal with that.
    
    @property {Boolean}
  */
  format: YES,
  
  /**
    The reporter is the object which implements the actual logging functions.
    
    @default The browser's console
    @property {Object}
  */
  reporter: console,
  
  // ..........................................................
  // METHODS
  // 

  /**
    Log output to the console, but only if it exists.
    
    @param {String|Array|Function|Object}
    @returns {Boolean} true if reporter.log exists, false otherwise
  */
  log: function() {
    var reporter = this.get('reporter');
    
    // log through the reporter
    if (this.get('exists') && typeof(reporter.log) === "function") {
      if (this.get('format')) {
        reporter.log(this._argumentsToString.apply(this, arguments));
      }
      else {
        reporter.log.apply(reporter, arguments);
      }
      return true;
    }
    
    // log through alert
    else if (this.fallBackOnAlert) {
      var s = this.get('format') ? this._argumentsToString.apply(this, arguments) : arguments;
      // include support for overriding the alert through the reporter
      // if it has come this far, it's likely this will fail
      if (this.get('exists') && typeof(reporter.alert) === "function") {
        reporter.alert(s);
      }
      else {
        alert(s);
      }
      return true;
    }
    return false;
  },
  
  /**
    Prints the properties of an object.
    
    Logs the object using {@link SC.Logger.log} if the reporter.dir function does not exist and
    {@link SC.Logger.fallBackOnLog} is true.
    
    @param {Object}
    @returns {Boolean} true if logged to console, false if not
  */
  dir: function() {
    var reporter = this.get('reporter');
    
  	if (this.get('exists') && typeof(reporter.dir) === "function") {
      // Firebug's console.dir doesn't support multiple objects here
      // but maybe custom reporters will
  	  reporter.dir.apply(reporter, arguments);
  	  return true;
	  }
  	return (this.fallBackOnLog) ? this.log.apply(this, arguments) : false;
  },
  
  /**
    Prints an XML outline for any HTML or XML object.
    
    Logs the object using {@link SC.Logger.log} if reporter.dirxml function does not exist and
    {@lnk SC.Logger.fallBackOnLog} is true.
    
    @param {Object}
    @returns {Boolean} true if logged to reporter, false if not
  */
  dirxml: function() {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.dirxml) === "function") {
      // Firebug's console.dirxml doesn't support multiple objects here
      // but maybe custom reporters will
      reporter.dirxml.apply(reporter, arguments);
      return true;
    }
    return (this.fallBackOnLog) ? this.log.apply(this, arguments) : false;
  },
  
  /**
    Log an error to the console
    
    Logs the error using {@link SC.Logger.log} if reporter.error does not exist and
    {@link SC.Logger.fallBackOnLog} is true.
    
    @param {String|Array|Function|Object}
    @returns {Boolean} true if logged to reporter, false if not
  */
  error: function() {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.error) === "function") {
      reporter.error.apply(reporter, arguments);
      return true;
    }
    else if (this.fallBackOnLog) {
      var a = this._argumentsToArray(arguments);
      if (typeof(a.unshift) === "function") a.unshift(SC.LOGGER_LOG_ERROR);
      return this.log.apply(this, a);
    }
    return false;
  },
  
  /**
    Every log after this call until {@link SC.Logger.groupEnd} is called
    will be indented for readability. You can create as many levels
    as you want.
    
    @param {String} [title] An optional title to display above the group
    @returns {Boolean} true if reporter.group exists, false otherwise
  */
  group: function(s) {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.group) === "function") {
      reporter.group(s);
      return true;
    }
    return false;
  },
  
  /**
    Ends a group declared with {@link SC.Logger.group}.
    
    @returns {Boolean} true if the reporter.groupEnd exists, false otherwise
    @see SC.Logger.group
  */
  groupEnd: function() {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.groupEnd) === "function") {
      reporter.groupEnd();
      return true;
    }
    return false;
  },
  
  /**
    Log an information response to the reporter.
    
    Logs the response using {@link SC.Logger.log} if reporter.info does not exist and
    {@link SC.Logger.fallBackOnLog} is true.
    
    @param {String|Array|Function|Object}
    @returns {Boolean} true if logged to reporter, false if not
  */
  info: function() {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.info) === "function") {
      reporter.info.apply(reporter, arguments);
      return true;
    }
    else if (this.fallBackOnLog) {
      var a = this._argumentsToArray(arguments);
      if (typeof(a.unshift) === "function") a.unshift(SC.LOGGER_LOG_INFO);
      return this.log.apply(this, a);
    }
    return false;
  },
  
  /**
    Begins the JavaScript profiler, if it exists. Call {@link SC.Logger.profileEnd}
    to end the profiling process and receive a report.
    
    @returns {Boolean} true if reporter.profile exists, false otherwise
  */
  profile: function() {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.profile) === "function") {
      reporter.profile();
      return true;
    }
    return false;
  },
  
  /**
    Ends the JavaScript profiler, if it exists.
    
    @returns {Boolean} true if reporter.profileEnd exists, false otherwise
    @see SC.Logger.profile
  */
  profileEnd: function() {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.profileEnd) === "function") {
      reporter.profileEnd();
      return true;
    }
    return false;
  },
  
  /**
    Measure the time between when this function is called and
    {@link SC.Logger.timeEnd} is called.
    
    @param {String} name The name of the profile to begin
    @returns {Boolean} true if reporter.time exists, false otherwise
    @see SC.Logger.timeEnd
  */
  time: function(name) {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.time) === "function") {
      reporter.time(name);
      return true;
    }
    return false;
  },
  
  /**
    Ends the profile specified.
    
    @param {String} name The name of the profile to end
    @returns {Boolean} true if reporter.timeEnd exists, false otherwise
    @see SC.Logger.time
  */
  timeEnd: function(name) {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.timeEnd) === "function") {
      reporter.timeEnd(name);
      return true;
    }
    return false;
  },
  
  /**
    Prints a stack-trace.
    
    @returns {Boolean} true if reporter.trace exists, false otherwise
  */
  trace: function() {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.trace) === "function") {
      reporter.trace();
      return true;
    }
    return false;
  },
  
  /**
    Log a warning to the console.
    
    Logs the warning using {@link SC.Logger.log} if reporter.warning does not exist and
    {@link SC.Logger.fallBackOnLog} is true.
    
    @param {String|Array|Function|Object}
    @returns {Boolean} true if logged to reporter, false if not
  */
  warn: function() {
    var reporter = this.get('reporter');
    
    if (this.get('exists') && typeof(reporter.warn) === "function") {
      reporter.warn.apply(reporter, arguments);
      return true;
    }
    else if (this.fallBackOnLog) {
      var a = this._argumentsToArray(arguments);
      if (typeof(a.unshift) === "function") a.unshift(SC.LOGGER_LOG_WARN);
      return this.log.apply(this, a);
    }
    return false;
  },
  
  // ..........................................................
  // INTERNAL SUPPORT
  // 
  
  /**
    @private
    
    The arguments function property doesn't support Array#unshift. This helper
    copies the elements of arguments to a blank array.
    
    @param {Array} arguments The arguments property of a function
    @returns {Array} An array containing the elements of arguments parameter
  */
  _argumentsToArray: function(arguments) {
    if (!arguments) return [];
    var a = [];
    for (var i = 0; i < arguments.length; i++) {
      a[i] = arguments[i];
    }
    return a;
  },
  
  /**
    @private
    
    Formats the arguments array of a function by creating a string
    with SC.LOGGER_LOG_DELIMITER between the elements.
    
    @returns {String} A string of formatted arguments
  */
  _argumentsToString: function() {
  	var s = "";
  	for (var i = 0; i<arguments.length - 1; i++) {
  		s += arguments[i] + SC.LOGGER_LOG_DELIMITER;
  	}
  	s += arguments[arguments.length-1];
  	return s;
  }
  
});;});
/* >>>>>>>>>> BEGIN source/system/object.js */
tiki.module('sproutcore/runtime:system/object',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:mixins/observable');require('sproutcore/runtime:system/set');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import mixins/observable";
"import system/set";
"export package";

SC.BENCHMARK_OBJECTS = NO;

// ..........................................................
// PRIVATE HELPER METHODS
// 
// Private helper methods.  These are not kept as part of the class
// definition because SC.Object is copied frequently and we want to keep the
// number of class methods to a minimum.

/** @private
  Augments a base object by copying the properties from the extended hash.
  In addition to simply copying properties, this method also performs a 
  number of optimizations that can make init'ing a new object much faster
  including:
  
  - concating concatenatedProperties
  - prepping a list of bindings, observers, and dependent keys
  - caching local observers so they don't need to be manually constructed.

  @param {Hash} base hash
  @param {Hash} extension
  @returns {Hash} base hash
*/
SC._object_extend = function _object_extend(base, ext) {
  if (!ext) throw "SC.Object.extend expects a non-null value.  Did you forget to 'sc_require' something?  Or were you passing a Protocol to extend() as if it were a mixin?";
  
  // set _kvo_cloned for later use
  base._kvo_cloned = null;

  // get some common vars
  var key, idx, len, cur, cprops = base.concatenatedProperties, K = SC.K ;
  var p1,p2;

  // first, save any concat props.  use old or new array or concat
  idx = (cprops) ? cprops.length : 0 ;
  var concats = (idx>0) ? {} : null;
  while(--idx>=0) {
    key = cprops[idx]; p1 = base[key]; p2 = ext[key];

    if (p1) {
      if (!(p1 instanceof Array)) p1 = SC.$A(p1);
      concats[key] = (p2) ? p1.concat(p2) : p2 ;
    } else {
      if (!(p2 instanceof Array)) p2 = SC.$A(p2);
      concats[key] = p2 ;
    }
  }

  // setup arrays for bindings, observers, and properties.  Normally, just
  // save the arrays from the base.  If these need to be changed during 
  // processing, then they will be cloned first.
  var bindings = base._bindings, clonedBindings = NO;
  var observers = base._observers, clonedObservers = NO;
  var properties = base._properties, clonedProperties = NO;
  var paths, pathLoc, local ;

  // outlets are treated a little differently because you can manually 
  // name outlets in the passed in hash. If this is the case, then clone
  // the array first.
  var outlets = base.outlets, clonedOutlets = NO ;
  if (ext.outlets) { 
    outlets = (outlets || SC.EMPTY_ARRAY).concat(ext.outlets);
    clonedOutlets = YES ;
  }

  // now copy properties, add superclass to func.
  for(key in ext) {

    if (key === '_kvo_cloned') continue; // do not copy

    // avoid copying builtin methods
    if (!ext.hasOwnProperty(key)) continue ; 

    // get the value.  use concats if defined
    var value = (concats.hasOwnProperty(key) ? concats[key] : null) || ext[key] ;

    // Possibly add to a bindings.
    if (key.slice(-7) === "Binding") {
      if (!clonedBindings) {
        bindings = (bindings || SC.EMPTY_ARRAY).slice() ;
        clonedBindings = YES ;
      }

      if (bindings === null) bindings = (base._bindings || SC.EMPTY_ARRAY).slice();
      bindings[bindings.length] = key ;

    // Also add observers, outlets, and properties for functions...
    } else if (value && (value instanceof Function)) {

      // add super to funcs.  Be sure not to set the base of a func to 
      // itself to avoid infinite loops.
      if (!value.superclass && (value !== (cur=base[key]))) {
        value.superclass = value.base = cur || K;
      }

      // handle regular observers
      if (value.propertyPaths) {
        if (!clonedObservers) {
          observers = (observers || SC.EMPTY_ARRAY).slice() ;
          clonedObservers = YES ;
        }
        observers[observers.length] = key ;

      // handle local properties
      } else if (paths = value.localPropertyPaths) {
        pathLoc = paths.length;
        while(--pathLoc >= 0) {
          local = base._kvo_for(SC.keyFor('_kvo_local', paths[pathLoc]), SC.Set);
          local.add(key);
          base._kvo_for('_kvo_observed_keys', SC.CoreSet).add(paths[pathLoc]);
        }

      // handle computed properties
      } else if (value.dependentKeys) {
        if (!clonedProperties) {
          properties = (properties || SC.EMPTY_ARRAY).slice() ;
          clonedProperties = YES ;
        }
        properties[properties.length] = key ;

      // handle outlets
      } else if (value.autoconfiguredOutlet) {
        if (!clonedOutlets) {
          outlets = (outlets || SC.EMPTY_ARRAY).slice();
          clonedOutlets = YES ;
        }
        outlets[outlets.length] = key ;          
      }
    }

    // copy property
    base[key] = value ;
  }
  
  // Manually set base on toString() because some JS engines (such as IE8) do
  // not enumerate it
  if (ext.hasOwnProperty('toString')) {
    key = 'toString';
    // get the value.  use concats if defined
    value = (concats.hasOwnProperty(key) ? concats[key] : null) || ext[key] ;
    if (!value.superclass && (value !== (cur=base[key]))) {
      value.superclass = value.base = cur || K ;
    }
    // copy property
    base[key] = value ;
  }


  // copy bindings, observers, and properties 
  base._bindings = bindings || [];
  base._observers = observers || [] ;
  base._properties = properties || [] ;
  base.outlets = outlets || [];

  return base ;
} ;

/** @class

  Root object for the SproutCore framework.  SC.Object is the root class for
  most classes defined by SproutCore.  It builds on top of the native object
  support provided by JavaScript to provide support for class-like 
  inheritance, automatic bindings, properties observers, and more.  
  
  Most of the classes you define in your application should inherit from 
  SC.Object or one of its subclasses.  If you are writing objects of your
  own, you should read this documentation to learn some of the details of 
  how SC.Object's behave and how they differ from other frameworks.
  
  h2. About SproutCore Classes
  
  JavaScript is not a class-based language.  Instead it uses a type of 
  inheritence inspired by self called "prototypical" inheritance. 
  ...

  h2. Using SproutCore objects with other JavaScript object.
  
  You can create a SproutCore object just like any other object...
  obj = new SC.Object() ;
  
  @extends SC.Observable 
  @since SproutCore 1.0
*/
SC.Object = function(props) { return this._object_init(props); };

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

  /**
    Adds the passed properties to the object's class definition.  You can 
    pass as many hashes as you want, including Mixins, and they will be 
    added in the order they are passed.

    This is a shorthand for calling SC.mixin(MyClass, props...);
    
    @params {Hash} props the properties you want to add.
    @returns {Object} receiver
  */
  mixin: function(props) {
    var len = arguments.length, loc ;
    for(loc =0;loc<len;loc++) SC.mixin(this, arguments[loc]);
    return this ;
  },

  // ..........................................
  // CREATING CLASSES AND INSTANCES
  //

  /**
    Points to the superclass for this class.  You can use this to trace a
    class hierarchy.
    
    @property {SC.Object}
  */
  superclass: null,
  
  /**
    Creates a new subclass of the receiver, adding any passed properties to
    the instance definition of the new class.  You should use this method
    when you plan to create several objects based on a class with similar 
    properties.

    h2. Init

    If you define an init() method, it will be called when you create 
    instances of your new class.  Since SproutCore uses the init() method to
    do important setup, you must be sure to always call arguments.callee.base.apply(this,arguments) somewhere
    in your init() to allow the normal setup to proceed.

    @params {Hash} props the methods of properties you want to add
    @returns {Class} A new object class
  */
  extend: function(props) {   
    var bench = SC.BENCHMARK_OBJECTS ;
    if (bench) SC.Benchmark.start('SC.Object.extend') ;

    // build a new constructor and copy class methods.  Do this before 
    // adding any other properties so they are not overwritten by the copy.
    var prop, ret = function(props) { return this._object_init(props); } ;
    for(prop in this) {
      if (!this.hasOwnProperty(prop)) continue ;
      ret[prop] = this[prop];
    }
    
    // manually copy toString() because some JS engines do not enumerate it
    if (this.hasOwnProperty('toString')) ret.toString = this.toString;

    // now setup superclass, guid
    ret.superclass = this ;
    SC.generateGuid(ret); // setup guid

    ret.subclasses = SC.Set.create();
    this.subclasses.add(ret); // now we can walk a class hierarchy

    // setup new prototype and add properties to it
    var base = (ret.prototype = SC.beget(this.prototype));
    var idx, len = arguments.length;
    for(idx=0;idx<len;idx++) SC._object_extend(base, arguments[idx]) ;
    base.constructor = ret; // save constructor

    if (bench) 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.

    You can pass any hash of properties to this method, including mixins.
    
    @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 C=this; return new C(arguments); },

  /**
    Walk like a duck.  You can use this to quickly test classes.
    
    @property {Boolean}
  */
  isClass: YES,

  /**
    Set of subclasses that extend from this class.  You can observe this 
    array if you want to be notified when the object is extended.
    
    @property {SC.Set}
  */
  subclasses: SC.Set.create(),
  
  /** @private */
  toString: function() { return SC._object_className(this); },

  // ..........................................
  // PROPERTY SUPPORT METHODS
  //

  /** 
    Returns YES if the receiver is a subclass of the named class.  If the 
    receiver is the class passed, this will return NO since the class is not
    a subclass of itself.  See also kindOf().

    h2. Example
    
    {{{
      ClassA = SC.Object.extend();
      ClassB = ClassA.extend();

      ClassB.subclassOf(ClassA) => YES
      ClassA.subclassOf(ClassA) => NO
    }}}
    
    @param {Class} scClass class to compare
    @returns {Boolean} 
  */
  subclassOf: function(scClass) {
    if (this === scClass) return NO ;
    var t = this ;
    while(t = t.superclass) if (t === scClass) return YES ;
    return NO ;
  },
  
  /**
    Returns YES if the passed object is a subclass of the receiver.  This is 
    the inverse of subclassOf() which you call on the class you want to test.
    
    @param {Class} scClass class to compare
    @returns {Boolean}
  */
  hasSubclass: function(scClass) {
    return (scClass && scClass.subclassOf) ? scClass.subclassOf(this) : NO;
  },

  /**
    Returns YES if the receiver is the passed class or is a subclass of the 
    passed class.  Unlike subclassOf(), this method will return YES if you
    pass the receiver itself, since class is a kind of itself.  See also 
    subclassOf().

    h2. Example

    {{{
      ClassA = SC.Object.extend();
      ClassB = ClassA.extend();

      ClassB.kindOf(ClassA) => YES
      ClassA.kindOf(ClassA) => YES
    }}}
    
    @param {Class} scClass class to compare
    @returns {Boolean} 
  */
  kindOf: function(scClass) { 
    return (this === scClass) || this.subclassOf(scClass) ;
  }  
  
}) ;

// ..........................................
// DEFAULT OBJECT INSTANCE
// 
SC.Object.prototype = {
  
  _kvo_enabled: YES,
  
  /** @private
    This is the first method invoked on a new instance.  It will first apply
    any added properties to the new instance and then calls the real init()
    method.
    
    @param {Array} extensions an array-like object with hashes to apply.
    @returns {Object} receiver
  */
  _object_init: function(extensions) {
    // apply any new properties
    var idx, len = (extensions) ? extensions.length : 0;
    for(idx=0;idx<len;idx++) SC._object_extend(this, extensions[idx]) ;
    SC.generateGuid(this) ; // add guid
    this.init() ; // call real init
    
    // Call 'initMixin' methods to automatically setup modules.
    var inits = this.initMixin; len = (inits) ? inits.length : 0 ;
    for(idx=0;idx < len; idx++) inits[idx].call(this);
    
    return this ; // done!
  },
  
  /**
    You can call this method on an object to mixin one or more hashes of 
    properties on the receiver object.  In addition to simply copying 
    properties, this method will also prepare the properties for use in 
    bindings, computed properties, etc.
    
    If you plan to use this method, you should call it before you call
    the inherited init method from SC.Object or else your instance may not 
    function properly.
    
    h2. Example
    
    {{{
      // dynamically apply a mixin specified in an object property
      var MyClass = SC.Object.extend({
         extraMixin: null,
         
         init: function() {
           this.mixin(this.extraMixin);
           arguments.callee.base.apply(this,arguments);
         }
      });
      
      var ExampleMixin = { foo: "bar" };
      
      var instance = MyClass.create({ extraMixin: ExampleMixin }) ;
      
      instance.get('foo') => "bar"
    }}}

    @param {Hash} ext a hash to copy.  Only one.
    @returns {Object} receiver
  */
  mixin: function() {
    var idx, len = arguments.length;
    for(idx=0;idx<len;idx++) SC.mixin(this, arguments[idx]) ;

    // call initMixin
    for(idx=0;idx<len;idx++) {
      var init = arguments[idx].initMixin ;
      if (init) init.call(this) ;
    }
    return this ;
  },

  /**
    This method is invoked automatically whenever a new object is 
    instantiated.  You can override this method as you like to setup your
    new object.  

    Within your object, be sure to call arguments.callee.base.apply(this,arguments) to ensure that the 
    built-in init method is also called or your observers and computed 
    properties may not be configured.

    Although the default init() method returns the receiver, the return 
    value is ignored.

    @returns {void}
  */
  init: function() {
    this.initObservable();
    return this ;
  },

  /**
    Set to NO once this object has been destroyed. 
    
    @property {Boolean}
  */
  isDestroyed: NO,

  /**
    Call this method when you are finished with an object to teardown its
    contents.  Because JavaScript is garbage collected, you do not usually 
    need to call this method.  However, you may choose to do so for certain
    objects, especially views, in order to let them reclaim memory they 
    consume immediately.

    If you would like to perform additional cleanup when an object is
    finished, you may override this method.  Be sure to call arguments.callee.base.apply(this,arguments).
    
    @returns {SC.Object} receiver
  */
  destroy: function() {
    if (this.get('isDestroyed')) return this; // nothing to do
    this.set('isDestroyed', YES);

    // destroy any mixins
    var idx, inits = this.destroyMixin, len = (inits) ? inits.length : 0 ;
    for(idx=0;idx < len; idx++) inits[idx].call(this);

    return this ;
  },

  /**
    Walk like a duck. Always YES since this is an object and not a class.
    
    @property {Boolean}
  */
  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 !!(SC.typeOf(this[methodName]) === SC.T_FUNCTION);
  },
  
  /**
    Attemps to invoked the named method, passing the included two arguments.  
    Returns NO if the method is either not implemented or if the handler 
    returns NO (indicating that it did not handle the event).  This method 
    is invoked to deliver actions from menu items and to deliver events.  
    You can override this method to provide additional handling if you 
    prefer.

    @param {String} methodName
    @param {Object} arg1
    @param {Object} arg2
    @returns {Boolean} YES if handled, NO if not handled
  */
  tryToPerform: function(methodName, arg1, arg2) {
    return this.respondsTo(methodName) && (this[methodName](arg1, arg2) !== NO);
  },

  /**  
    EXPERIMENTAL:  You can use this to invoke a superclass implementation in
    any method.  This does not work in Safari 2 or earlier.  If you need to
    target these methods, you should use one of the alternatives below:

    - *With Build Tools:* arguments.callee.base.apply(this,arguments);
    - *Without Build Tools:* arguments.callee.base.apply(this, arguments);
    
    h2. Example
    
    All of the following methods will call the superclass implementation of
    your method:
    
    {{{
      SC.Object.create({
        
        // DOES NOT WORK IN SAFARI 2 OR EARLIER
        method1: function() {
          this.superclass();
        },
        
        // REQUIRES SC-BUILD TOOLS
        method2: function() {
          arguments.callee.base.apply(this,arguments);
        },
        
        // WORKS ANYTIME
        method3: function() {
          arguments.callee.base.apply(this, arguments);
        }
      });
    }}}

    @params args {*args} any arguments you want to pass along.
    @returns {Object} return value from super
  */
  superclass: function(args) {
    var caller = arguments.callee.caller; 
    if (!caller) throw "superclass cannot determine the caller method" ;
    return caller.superclass ? caller.superclass.apply(this, arguments) : null;
  },

  /**  
    returns YES if the receiver is an instance of the named class.  See also
    kindOf().

    h2. Example
    
    {{{
      var ClassA = SC.Object.extend();
      var ClassB = SC.Object.extend();
      
      var instA = ClassA.create();
      var instB = ClassB.create();
      
      instA.instanceOf(ClassA) => YES
      instB.instanceOf(ClassA) => NO
    }}}
    
    @param {Class} scClass the class
    @returns {Boolean}
  */
  instanceOf: function(scClass) {
    return this.constructor === scClass ;  
  },

  /**  
    Returns true if the receiver is an instance of the named class or any 
    subclass of the named class.  See also instanceOf().

    h2. Example
    
    {{{
      var ClassA = SC.Object.extend();
      var ClassB = SC.Object.extend();
      
      var instA = ClassA.create();
      var instB = ClassB.create();
      
      instA.kindOf(ClassA) => YES
      instB.kindOf(ClassA) => YES
    }}}

    @param scClass {Class} the class
    @returns {Boolean}
  */
  kindOf: function(scClass) { return this.constructor.kindOf(scClass); },

  /** @private */
  toString: function() {
    if (!this._object_toString) {
      // only cache the string if the klass name is available
      var klassName = SC._object_className(this.constructor) ;
      var string = "%@:%@".fmt(klassName, SC.guidFor(this));
      if (klassName) this._object_toString = string ;
      else return string ;
    } 
    return this._object_toString ;
  },

  /**  
    Activates any outlet connections in object and syncs any bindings.  This
    method is called automatically for view classes but may be used for any
    object.
    
    @returns {void}
  */
  awake: function(key) { 
    this.outlets.forEach(function(key) { this.get(key); },this) ;
    this.bindings.invoke('sync'); 
  },

  /**
    Invokes the passed method or method name one time during the runloop.  You
    can use this method to schedule methods that need to execute but may be 
    too expensive to execute more than once, such as methods that update the
    DOM.
    
    @param {Funciton|String} method method or method name
    @returns {SC.Object} receiver
  */
  invokeOnce: function(method) {
    SC.RunLoop.currentRunLoop.invokeOnce(this, method) ;
    return this ;
  },
  
  /**
    Invokes the passed method once at the beginning of the next runloop, 
    before any other methods (including events) are processed. This is useful
    for situations where you know you need to update something, but due to
    the way the run loop works, you can't actually do the update until the
    run loop has completed.
    
    A simple example is setting the selection on a collection controller to a 
    newly created object. Because the collection controller won't have its
    content collection updated until later in the run loop, setting the 
    selection immediately will have no affect. In this situation, you could do
    this instead:
    
    {{{
      // Creates a new MyRecord object and sets the selection of the
      // myRecord collection controller to the new object.
      createObjectAction: function(sender, evt) {
        // create a new record and add it to the store
        var obj = MyRecord.newRecord() ;
        
        // update the collection controller's selection
        MyApp.myRecordCollectionController.invokeLast( function() {
          this.set('selection', [obj]) ;
        });
      }
    }}}
    
    You can call invokeLast as many times as you like and the method will
    only be invoked once.
    
    @param {Funciton|String} method method or method name
    @returns {SC.Object} receiver
  */
  invokeLast: function(method) {
    SC.RunLoop.currentRunLoop.invokeLast(this, method) ;
    return this ;
  },
  
  /**
    The properties named in this array will be concatenated in subclasses
    instead of replaced.  This allows you to name special properties that
    should contain any values you specify plus values specified by parents.

    It is used by SproutCore and is available for your use, though you 
    should limit the number of properties you include in this list as it 
    adds a slight overhead to new class and instance creation.

    @property {Array}
  */
  concatenatedProperties: ['concatenatedProperties', 'initMixin', 'destroyMixin']  

} ;

// bootstrap the constructor for SC.Object.
SC.Object.prototype.constructor = SC.Object;

// Add observable to mixin
SC.mixin(SC.Object.prototype, SC.Observable) ;

// ..........................................................
// CLASS NAME SUPPORT
// 

/** @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.
*/
function findClassNames() {

  if (SC._object_foundObjectClassNames) return ;
  SC._object_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 == 'superclass') continue ;
      if (!key.match(/^[A-Z0-9]/)) continue ;

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


      switch(SC.typeOf(value)) {
      case SC.T_CLASS:
        if (!value._object_className) value._object_className = path;
        if (levels>=0) searchObject(path, value, levels) ;
        break ;

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

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

      default:
        break;
      }
    }
  } ;

  searchObject(null, require('system:index').global, 2) ;

  // Internet Explorer doesn's loop over global variables...
  /*if ( SC.browser.isIE ) {
    searchObject('SC', SC, 2) ; // get names for the SC classes

    // get names for the model classes, including nested namespaces (untested)
    for ( var i = 0; i < SC.Server.servers.length; i++ ) {
      var server = SC.Server.servers[i];
      if (server.prefix) {
        for (var prefixLoc = 0; prefixLoc < server.prefix.length; prefixLoc++) {
          var prefixParts = server.prefix[prefixLoc].split('.');
          var namespace = window;
          var namespaceName;
          for (var prefixPartsLoc = 0; prefixPartsLoc < prefixParts.length; prefixPartsLoc++) {
            namespace = namespace[prefixParts[prefixPartsLoc]] ;
            namespaceName = prefixParts[prefixPartsLoc];
          }
          searchObject(namespaceName, namespace, 2) ;
        }
      }
    }
  }*/
}

/**  
  Same as the instance method, but lets you check instanceOf without
  having to first check if instanceOf exists as a method.
  
  @param {Object} scObject the object to check instance of
  @param {Class} scClass the class
  @returns {Boolean} if object1 is instance of class
*/
SC.instanceOf = function(scObject, scClass) {
  return !!(scObject && scObject.constructor === scClass) ;  
} ; 

/**
  Same as the instance method, but lets you check kindOf without having to 
  first check if kindOf exists as a method.
  
  @param {Object} scObject object to check kind of
  @param {Class} scClass the class to check
  @returns {Boolean} if object is an instance of class or subclass
*/
SC.kindOf = function(scObject, scClass) {
  if (scObject && !scObject.isClass) scObject = scObject.constructor;
  return !!(scObject && scObject.kindOf && scObject.kindOf(scClass));
};

/** @private
  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.
  
  This method is used to allow classes to determine their own name.
*/
SC._object_className = function(obj) {
  if (!SC.isReady) return ''; // class names are not available until ready
  if (!obj._object_className) findClassNames() ;
  if (obj._object_className) return obj._object_className ;

  // if no direct classname was found, walk up class chain looking for a 
  // match.
  var ret = obj ;
  while(ret && !ret._object_className) ret = ret.superclass; 
  return (ret && ret._object_className) ? ret._object_className : 'Anonymous';
} ;

;});
/* >>>>>>>>>> BEGIN source/system/range_observer.js */
tiki.module('sproutcore/runtime:system/range_observer',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:private/observer_queue');require('sproutcore/runtime:system/index_set');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import private/observer_queue";
"import system/index_set";
"export package";

/** @class

  A RangeObserver is used by Arrays to automatically observe all of the
  objects in a particular range on the array.  Whenever any property on one 
  of those objects changes, it will notify its delegate.  Likewise, whenever
  the contents of the array itself changes, it will notify its delegate and
  possibly update its own registration.

  This implementation uses only SC.Array methods.  It can be used on any 
  object that complies with SC.Array.  You may, however, choose to subclass
  this object in a way that is more optimized for your particular design.
  
  @since SproutCore 1.0
*/
SC.RangeObserver = {

  /** 
    Walk like a duck.
    
    @property {Boolean}
  */
  isRangeObserver: YES,
  
  /** @private */
  toString: function() { 
    var base = this.indexes ? this.indexes.toString() : "SC.IndexSet<..>";
    return base.replace('IndexSet', 'RangeObserver(%@)'.fmt(SC.guidFor(this)));
  },
  
  /**
    Creates a new range observer owned by the source.  The indexSet you pass
    must identify the indexes you are interested in observing.  The passed
    target/method will be invoked whenever the observed range changes.
    
    Note that changes to a range are buffered until the end of a run loop
    unless a property on the record itself changes.
  
    @param {SC.Array} source the source array
    @param {SC.IndexSet} indexSet set of indexes to observer
    @param {Object} target the target
    @param {Function|String} method the method to invoke
    @param {Object} context optional context to include in callback
    @param {Boolean} isDeep if YES, observe property changes as well
    @returns {SC.RangeObserver} instance
  */
  create: function(source, indexSet, target, method, context, isDeep) {
    var ret = SC.beget(this);
    ret.source = source;
    ret.indexes = indexSet ? indexSet.frozenCopy() : null;
    ret.target = target;
    ret.method = method;
    ret.context = context ;
    ret.isDeep  = isDeep || NO ;
    ret.beginObserving();
    return ret ;
  },

  /**
    Create subclasses for the RangeObserver.  Pass one or more attribute
    hashes.  Use this to create customized RangeObservers if needed for your 
    classes.
    
    @param {Hash} attrs one or more attribute hashes
    @returns {SC.RangeObserver} extended range observer class
  */
  extend: function(attrs) {
    var ret = SC.beget(this), args = arguments, len = args.length, idx;
    for(idx=0;idx<len;idx++) SC.mixin(ret, args[idx]);
    return ret ;
  },

  /**
    Destroys an active ranger observer, cleaning up first.
    
    @param {SC.Array} source the source array
    @returns {SC.RangeObserver} receiver
  */
  destroy: function(source) { 
    this.endObserving(); 
    return this; 
  },

  /**
    Updates the set of indexes the range observer applies to.  This will 
    stop observing the old objects for changes and start observing the 
    new objects instead.
    
    @param {SC.Array} source the source array
    @returns {SC.RangeObserver} receiver
  */
  update: function(source, indexSet) {
    if (this.indexes && this.indexes.isEqual(indexSet)) return this ;
    
    this.indexes = indexSet ? indexSet.frozenCopy() : null ;
    this.endObserving().beginObserving();
    return this;
  },
  
  /**
    Configures observing for each item in the current range.  Should update
    the observing array with the list of observed objects so they can be
    torn down later
    
    @returns {SC.RangeObserver} receiver
  */
  beginObserving: function() {
    if (!this.isDeep) return this; // nothing to do
    
    var observing = this.observing;
    if (!observing) observing = this.observing = SC.CoreSet.create();
    
    // cache iterator function to keep things fast
    var func = this._beginObservingForEach;
    if (!func) {
      func = this._beginObservingForEach = function(idx) {
        var obj = this.source.objectAt(idx);
        if (obj && obj.addObserver) {
          observing.push(obj);
          obj._kvo_needsRangeObserver = YES ;
        }
      };
    }
    this.indexes.forEach(func,this);

    // add to pending range observers queue so that if any of these objects
    // change we will have a chance to setup observing on them.
    this.isObserving = NO ;
    SC.Observers.addPendingRangeObserver(this);

    return this;
  },
  
  /** @private
    Called when an object that appears to need range observers has changed.
    Check to see if the range observer contains this object in its list.  If
    it does, go ahead and setup observers on all objects and remove ourself
    from the queue.
  */
  setupPending: function(object) {
    var observing = this.observing ;

    if (this.isObserving || !observing || (observing.get('length')===0)) {
      return YES ;
    } 
    
    if (observing.contains(object)) {
      this.isObserving = YES ;

      // cache iterator function to keep things fast
      var func = this._setupPendingForEach;
      if (!func) {
        var source = this.source,
            method = this.objectPropertyDidChange;

        func = this._setupPendingForEach = function(idx) {
          var obj = this.source.objectAt(idx),
              guid = SC.guidFor(obj),
              key ;
              
          if (obj && obj.addObserver) {
            observing.push(obj);
            obj.addObserver('*', this, method);
            
            // also save idx of object on range observer itself.  If there is
            // more than one idx, convert to IndexSet.
            key = this[guid];
            if (key === undefined || key === null) {
              this[guid] = idx ;
            } else if (key.isIndexSet) {
              key.add(idx);
            } else {
              key = this[guid] = SC.IndexSet.create(key).add(idx);
            }
            
          }
        };
      }
      this.indexes.forEach(func,this);
      return YES ;
      
    } else return NO ;
  },
  
  /**
    Remove observers for any objects currently begin observed.  This is 
    called whenever the observed range changes due to an array change or 
    due to destroying the observer.
    
    @returns {SC.RangeObserver} receiver
  */
  endObserving: function() {
    if (!this.isDeep) return this; // nothing to do
    
    var observing = this.observing;
    
    if (this.isObserving) {
      var meth      = this.objectPropertyDidChange,
          source    = this.source,
          idx, lim, obj;

      if (observing) {
        lim = observing.length;
        for(idx=0;idx<lim;idx++) {
          obj = observing[idx];
          obj.removeObserver('*', this, meth);
          this[SC.guidFor(obj)] = null;
        }
        observing.length = 0 ; // reset
      } 
      
      this.isObserving = NO ;
    }
    
    if (observing) observing.clear(); // empty set.
    return this ;
  },
  
  /**
    Whenever the actual objects in the range changes, notify the delegate
    then begin observing again.  Usually this method will be passed an 
    IndexSet with the changed indexes.  The range observer will only notify
    its delegate if the changed indexes include some of all of the indexes
    this range observer is monitoring.
    
    @param {SC.IndexSet} changes optional set of changed indexes
    @returns {SC.RangeObserver} receiver
  */
  rangeDidChange: function(changes) {
    var indexes = this.indexes;
    if (!changes || !indexes || indexes.intersects(changes)) {
      this.endObserving(); // remove old observers
      this.method.call(this.target, this.source, null, '[]', changes, this.context);
      this.beginObserving(); // setup new ones
    }
    return this ;
  },

  /**
    Whenever an object changes, notify the delegate
    
    @param {Object} the object that changed
    @param {String} key the property that changed
    @returns {SC.RangeObserver} receiver
  */
  objectPropertyDidChange: function(object, key, value, rev) {
    var context = this.context,
        method  = this.method, 
        guid    = SC.guidFor(object),
        index   = this[guid];
      
    // lazily convert index to IndexSet.  
    if (index && !index.isIndexSet) {
      index = this[guid] = SC.IndexSet.create(index).freeze();
    }
    
    if (context) {
      method.call(this.target, this.source, object, key, index, context, rev);
    } else {
      method.call(this.target, this.source, object, key, index, rev);
    }
  }
  
};
;});
/* >>>>>>>>>> BEGIN source/system/run_loop.js */
tiki.module('sproutcore/runtime:system/run_loop',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:private/observer_set');require('sproutcore/runtime:system/object');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import private/observer_set";
"import system/object";
"export package";

/**
  @class
  
  The run loop provides a universal system for coordinating events within
  your application.  The run loop processes timers as well as pending 
  observer notifications within your application.
  
  To use a RunLoop within your application, you should make sure your event
  handlers always begin and end with SC.RunLoop.begin() and SC.RunLoop.end()
  
  The RunLoop is important because bindings do not fire until the end of 
  your run loop is reached.  This improves the performance of your
  application.
  
  h2. Example
  
  This is how you could write your mouseup handler in jQuery:
  
  {{{
    $('#okButton').on('click', function() {
      SC.RunLoop.begin();
      
      // handle click event...
      
      SC.RunLoop.end(); // allows bindings to trigger...
    });
  }}}
  
  @extends SC.Object
  @since SproutCore 1.0
*/
SC.RunLoop = SC.Object.extend(/** @scope SC.RunLoop.prototype */ {
  
  /**
    Call this method whenver you begin executing code.  
    
    This is typically invoked automatically for you from event handlers and 
    the timeout handler.  If you call setTimeout() or setInterval() yourself, 
    you may need to invoke this yourself.
    
    @returns {SC.RunLoop} receiver
  */
  beginRunLoop: function() {
    this._start = new Date().getTime() ; // can't use Date.now() in runtime
    if (SC.LOG_BINDINGS || SC.LOG_OBSERVERS) {
      console.log("-- SC.RunLoop.beginRunLoop at %@".fmt(this._start));
    } 
    return this ; 
  },
  
  /**
    Call this method whenever you are done executing code.
    
    This is typically invoked automatically for you from event handlers and
    the timeout handler.  If you call setTimeout() or setInterval() yourself
    you may need to invoke this yourself.
    
    @returns {SC.RunLoop} receiver
  */
  endRunLoop: function() {
    // at the end of a runloop, flush all the delayed actions we may have 
    // stored up.  Note that if any of these queues actually run, we will 
    // step through all of them again.  This way any changes get flushed
    // out completely.
    var didChange ;

    if (SC.LOG_BINDINGS || SC.LOG_OBSERVERS) {
      console.log("-- SC.RunLoop.endRunLoop ~ flushing application queues");
    } 
    
    do {
      didChange = this.flushApplicationQueues() ;
      if (!didChange) didChange = this._flushinvokeLastQueue() ; 
    } while(didChange) ;
    this._start = null ;

    if (SC.LOG_BINDINGS || SC.LOG_OBSERVERS) {
      console.log("-- SC.RunLoop.endRunLoop ~ End");
    } 
    
    return this ; 
  },
  
  /**
    Invokes the passed target/method pair once at the end of the runloop.
    You can call this method as many times as you like and the method will
    only be invoked once.  
    
    Usually you will not call this method directly but use invokeOnce() 
    defined on SC.Object.
    
    @param {Object} target
    @param {Function} method
    @returns {SC.RunLoop} receiver
  */
  invokeOnce: function(target, method) {
    // normalize
    if (method === undefined) { 
      method = target; target = this ;
    }
    if (SC.typeOf(method) === SC.T_STRING) method = target[method];
    if (!this._invokeQueue) this._invokeQueue = SC.ObserverSet.create();
    this._invokeQueue.add(target, method);
    return this ;
  },
  
  /**
    Invokes the passed target/method pair at the very end of the run loop,
    once all other delayed invoke queues have been flushed.  Use this to 
    schedule cleanup methods at the end of the run loop once all other work
    (including rendering) has finished.

    If you call this with the same target/method pair multiple times it will
    only invoke the pair only once at the end of the runloop.
    
    Usually you will not call this method directly but use invokeLast() 
    defined on SC.Object.
    
    @param {Object} target
    @param {Function} method
    @returns {SC.RunLoop} receiver
  */
  invokeLast: function(target, method) {
    // normalize
    if (method === undefined) { 
      method = target; target = this ;
    }
    if (SC.typeOf(method) === SC.T_STRING) method = target[method];
    if (!this._invokeLastQueue) this._invokeLastQueue = SC.ObserverSet.create();
    this._invokeLastQueue.add(target, method);
    return this ;
  },
  
  /**
    Executes any pending events at the end of the run loop.  This method is 
    called automatically at the end of a run loop to flush any pending 
    queue changes.
    
    The default method will invoke any one time methods and then sync any 
    bindings that might have changed.  You can override this method in a 
    subclass if you like to handle additional cleanup. 
    
    This method must return YES if it found any items pending in its queues
    to take action on.  endRunLoop will invoke this method repeatedly until
    the method returns NO.  This way if any if your final executing code
    causes additional queues to trigger, then can be flushed again.
    
    @returns {Boolean} YES if items were found in any queue, NO otherwise
  */
  flushApplicationQueues: function() {
    var hadContent = NO ;
    
    // execute any methods in the invokeQueue.
    var queue = this._invokeQueue;
    if (queue && queue.targets > 0) {
      this._invokeQueue = null; // reset so that a new queue will be created
      hadContent = YES ; // needs to execute again
      queue.invokeMethods();
    }
    
    // flush any pending changed bindings.  This could actually trigger a 
    // lot of code to execute.
    return SC.Binding.flushPendingChanges() || hadContent ;
  },
  
  _flushinvokeLastQueue: function() {
    var queue = this._invokeLastQueue, hadContent = NO ;
    if (queue && queue.targets > 0) {
      this._invokeLastQueue = null; // reset queue.
      hadContent = YES; // has targets!
      if (hadContent) queue.invokeMethods();
    }
    return hadContent ;
  }
  
});

/** 
  The current run loop.  This is created automatically the first time you
  call begin(). 
  
  @property {SC.RunLoop}
*/
SC.RunLoop.currentRunLoop = null;

/**
  The default RunLoop class.  If you choose to extend the RunLoop, you can
  set this property to make sure your class is used instead.
  
  @property {Class}
*/
SC.RunLoop.runLoopClass = SC.RunLoop;

/** 
  Begins a new run loop on the currentRunLoop.  If you are already in a 
  runloop, this method has no effect.
  
  @returns {SC.RunLoop} receiver
*/
SC.RunLoop.begin = function() {    
  var runLoop = this.currentRunLoop;
  if (!runLoop) runLoop = this.currentRunLoop = this.runLoopClass.create();
  runLoop.beginRunLoop();
  return this ;
};

/**
  Ends the run loop on the currentRunLoop.  This will deliver any final 
  pending notifications and schedule any additional necessary cleanup.
  
  @returns {SC.RunLoop} receiver
*/
SC.RunLoop.end = function() {
  var runLoop = this.currentRunLoop;
  if (!runLoop) {
    throw "SC.RunLoop.end() called outside of a runloop!";
  }
  runLoop.endRunLoop();
  return this ;
} ;

/**
  Helper method executes the passed function inside of a runloop.  Normally
  not needed but useful for testing.
  
  @param {Function} callback callback to execute
  @param {Object} target context for callback
  @returns {SC} receiver
*/
SC.run = function(callback, target) {
  SC.RunLoop.begin();
  callback.call(target);
  SC.RunLoop.end();
};


;});
/* >>>>>>>>>> BEGIN source/system/selection_set.js */
tiki.module('sproutcore/runtime:system/selection_set',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:system/object');require('sproutcore/runtime:mixins/enumerable');require('sproutcore/runtime:mixins/copyable');require('sproutcore/runtime:mixins/freezable');require('sproutcore/runtime:system/set');require('sproutcore/runtime:system/index_set');// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import system/object";
"import mixins/enumerable";
"import mixins/copyable";
"import mixins/freezable";
"import system/set";
"import system/index_set";
"export package";

/** @class

  A SelectionSet contains a set of objects that represent the current 
  selection.  You can select objects by either adding them to the set directly
  or indirectly by selecting a range of indexes on a source object.
  
  @extends SC.Object
  @extends SC.Enumerable
  @extends SC.Freezable
  @extends SC.Copyable
  @since SproutCore 1.0
*/
SC.SelectionSet = SC.Object.extend(SC.Enumerable, SC.Freezable, SC.Copyable, 
  /** @scope SC.SelectionSet.prototype */ {
  
  /**
    Walk like a duck.
    
    @property {Boolean}
  */
  isSelectionSet: YES,
  
  /**
    Total number of indexes in the selection set
    
    @property {Number}
  */
  length: function() {
    var ret     = 0,
        sets    = this._sets,
        objects = this._objects;
    if (objects) ret += objects.get('length');
    if (sets) sets.forEach(function(s) { ret += s.get('length'); });
    return ret ;
  }.property().cacheable(),

  // ..........................................................
  // INDEX-BASED SELECTION
  // 

  /**
    A set of all the source objects used in the selection set.  This 
    property changes automatically as you add or remove index sets.
    
    @property {SC.Array}
  */
  sources: function() {
    var ret  = [],
        sets = this._sets,
        len  = sets ? sets.length : 0,
        idx, set, source;
        
    for(idx=0;idx<len;idx++) {
      set = sets[idx];
      if (set && set.get('length')>0 && set.source) ret.push(set.source);
    }
    return ret ;
  }.property().cacheable(),
  
  /**
    Returns the index set for the passed source object or null if no items are
    seleted in the source.
    
    @param {SC.Array} source the source object
    @returns {SC.IndexSet} index set or null
  */
  indexSetForSource: function(source) {
    if (!source || !source.isSCArray) return null; // nothing to do
    
    var cache   = this._indexSetCache,
        objects = this._objects,
        ret, idx;

    // try to find in cache
    if (!cache) cache = this._indexSetCache = {};
    ret = cache[SC.guidFor(source)];
    if (ret && ret._sourceRevision && (ret._sourceRevision !== source.propertyRevision)) {
      ret = null;
    }

    // not in cache.  generate from index sets and any saved objects
    if (!ret) {
      ret = this._indexSetForSource(source, NO);
      if (ret && ret.get('length')===0) ret = null;
    
      if (objects) {
        if (ret) ret = ret.copy();
        objects.forEach(function(o) {
          if ((idx = source.indexOf(o)) >= 0) {
            if (!ret) ret = SC.IndexSet.create();
            ret.add(idx);
          }
        }, this);
      }
      
      if (ret) {
        ret = cache[SC.guidFor(source)] = ret.frozenCopy();
        ret._sourceRevision = source.propertyRevision;
      }
    }
    
    return ret;
  },
    
  /** 
    @private
  
    Internal method gets the index set for the source, ignoring objects
    that have been added directly.
  */
  _indexSetForSource: function(source, canCreate) {
    if (canCreate === undefined) canCreate = YES;

    var guid  = SC.guidFor(source),
        index = this[guid],
        sets  = this._sets,
        len   = sets ? sets.length : 0,
        ret   = null;
                
    if (index >= len) index = null;
    if (SC.none(index)) {
      if (canCreate && !this.isFrozen) {
        this.propertyWillChange('sources');
        if (!sets) sets = this._sets = [];
        ret = sets[len] = SC.IndexSet.create();
        ret.source = source ;
        this[guid] = len;
        this.propertyDidChange('sources');
      }
      
    } else ret = sets ? sets[index] : null;
    return ret ;
  },
  
  /**
    Add the passed index, range of indexSet belonging to the passed source
    object to the selection set.
    
    The first parameter you pass must be the source array you are selecting
    from.  The following parameters may be one of a start/length pair, a 
    single index, a range object or an IndexSet.  If some or all of the range
    you are selecting is already in the set, it will not be selected again.
    
    You can also pass an SC.SelectionSet to this method and all the selected
    sets will be added from their instead.
    
    @param {SC.Array} source source object or object to add.
    @param {Number} start index, start of range, range or IndexSet
    @param {Number} length length if passing start/length pair.
    @returns {SC.SelectionSet} receiver
  */
  add: function(source, start, length) {
    
    if (this.isFrozen) throw SC.FROZEN_ERROR ;

    var sets, len, idx, set, oldlen, newlen, setlen, objects;
    
    // normalize
    if (start === undefined && length === undefined) {
      if (!source) throw "Must pass params to SC.SelectionSet.add()";
      if (source.isIndexSet) return this.add(source.source, source);
      if (source.isSelectionSet) {
        sets = source._sets;
        objects = source._objects;
        len  = sets ? sets.length : 0;

        this.beginPropertyChanges();
        for(idx=0;idx<len;idx++) {
          set = sets[idx];
          if (set && set.get('length')>0) this.add(set.source, set);
        }
        if (objects) this.addObjects(objects);
        this.endPropertyChanges();
        return this ;
        
      }
    }

    set    = this._indexSetForSource(source, YES);
    oldlen = this.get('length');
    setlen = set.get('length');
    newlen = oldlen - setlen;
        
    set.add(start, length);

    this._indexSetCache = null;

    newlen += set.get('length');
    if (newlen !== oldlen) {
      this.propertyDidChange('length');
      this.enumerableContentDidChange();
      if (setlen === 0) this.notifyPropertyChange('sources');
    }

    return this ;
  },

  /**
    Removes the passed index, range of indexSet belonging to the passed source
    object from the selection set.
    
    The first parameter you pass must be the source array you are selecting
    from.  The following parameters may be one of a start/length pair, a 
    single index, a range object or an IndexSet.  If some or all of the range
    you are selecting is already in the set, it will not be selected again.
    
    @param {SC.Array} source source object. must not be null
    @param {Number} start index, start of range, range or IndexSet
    @param {Number} length length if passing start/length pair.
    @returns {SC.SelectionSet} receiver
  */
  remove: function(source, start, length) {
    
    if (this.isFrozen) throw SC.FROZEN_ERROR ;
    
    var sets, len, idx, set, oldlen, newlen, setlen, objects;
    
    // normalize
    if (start === undefined && length === undefined) {
      if (!source) throw "Must pass params to SC.SelectionSet.remove()";
      if (source.isIndexSet) return this.remove(source.source, source);
      if (source.isSelectionSet) {
        sets = source._sets;
        objects = source._objects;
        len  = sets ? sets.length : 0;
            
        this.beginPropertyChanges();
        for(idx=0;idx<len;idx++) {
          set = sets[idx];
          if (set && set.get('length')>0) this.remove(set.source, set);
        }
        if (objects) this.removeObjects(objects);
        this.endPropertyChanges();
        return this ;
      }
    }
    
    // save starter info
    set    = this._indexSetForSource(source, YES);
    oldlen = this.get('length');
    newlen = oldlen - set.get('length');

    // if we have objects selected, determine if they are in the index 
    // set and remove them as well.
    if (set && (objects = this._objects)) {
      
      // convert start/length to index set so the iterator below will work...
      if (length !== undefined) {
        start = SC.IndexSet.create(start, length);
        length = undefined;
      }
      
      objects.forEach(function(object) {
        idx = source.indexOf(object);
        if (start.contains(idx)) {
          objects.remove(object);
          newlen--;
        }
      }, this);
    }
    
    // remove indexes from source index set
    set.remove(start, length);
    setlen = set.get('length');
    newlen += setlen;

    // update caches; change enumerable...
    this._indexSetCache = null;
    if (newlen !== oldlen) {
      this.propertyDidChange('length');
      this.enumerableContentDidChange();
      if (setlen === 0) this.notifyPropertyChange('sources');
    }

    return this ;
  },

  
  /**
    Returns YES if the selection contains the named index, range of indexes.
    
    @param {Object} source source object for range
    @param {Number} start index, start of range, range object, or indexSet
    @param {Number} length optional range length
    @returns {Boolean}
  */
  contains: function(source, start, length) {
    if (start === undefined && length === undefined) {
      return this.containsObject(source);
    }
    
    var set = this.indexSetForSource(source);
    if (!set) return NO ;
    return set.contains(start, length);
  },

  /**
    Returns YES if the index set contains any of the passed indexes.  You
    can pass a single index, a range or an index set.
    
    @param {Object} source source object for range
    @param {Number} start index, range, or IndexSet
    @param {Number} length optional range length
    @returns {Boolean}
  */
  intersects: function(source, start, length) {
    var set = this.indexSetForSource(source, NO);
    if (!set) return NO ;
    return set.intersects(start, length);
  },
  
  
  // ..........................................................
  // OBJECT-BASED API
  // 

  _TMP_ARY: [],
  
  /**
    Adds the object to the selection set.  Unlike adding an index set, the 
    selection will actually track the object independent of its location in 
    the array.
    
    @param {Object} object 
    @returns {SC.SelectionSet} receiver
  */
  addObject: function(object) {  
    var ary = this._TMP_ARY, ret;
    ary[0] = object;
    
    ret = this.addObjects(ary);
    ary.length = 0;
    
    return ret;
  },
  
  /**
    Adds objects in the passed enumerable to the selection set.  Unlike adding
    an index set, the seleciton will actually track the object independent of
    its location the array.
    
    @param {SC.Enumerable} objects
    @returns {SC.SelectionSet} receiver
  */
  addObjects: function(objects) {
    var cur = this._objects,
        oldlen, newlen;
    if (!cur) cur = this._objects = SC.CoreSet.create();
    oldlen = cur.get('length');

    cur.addEach(objects);
    newlen = cur.get('length');
    
    this._indexSetCache = null;
    if (newlen !== oldlen) {
      this.propertyDidChange('length');
      this.enumerableContentDidChange();
    }
    return this;
  },

  /**
    Removes the object from the selection set.  Note that if the selection
    set also selects a range of indexes that includes this object, it may 
    still be in the selection set.
    
    @param {Object} object 
    @returns {SC.SelectionSet} receiver
  */
  removeObject: function(object) {  
    var ary = this._TMP_ARY, ret;
    ary[0] = object;
    
    ret = this.removeObjects(ary);
    ary.length = 0;
    
    return ret;
  },
  
  /**
    Removes the objects from the selection set.  Note that if the selection
    set also selects a range of indexes that includes this object, it may 
    still be in the selection set.
    
    @param {Object} object 
    @returns {SC.SelectionSet} receiver
  */
  removeObjects: function(objects) {
    var cur = this._objects,
        oldlen, newlen, sets;
        
    if (!cur) return this;

    oldlen = cur.get('length');

    cur.removeEach(objects);
    newlen = cur.get('length');
    
    // also remove from index sets, if present
    if (sets = this._sets) {
      sets.forEach(function(set) {
        oldlen += set.get('length');
        set.removeObjects(objects);
        newlen += set.get('length');
      }, this);
    }
    
    this._indexSetCache = null;
    if (newlen !== oldlen) {
      this.propertyDidChange('length');
      this.enumerableContentDidChange();
    }
    return this;
  },

  /**
    Returns YES if the selection contains the passed object.  This will search
    selected ranges in all source objects.
    
    @param {Object} object the object to search for
    @returns {Boolean}
  */
  containsObject: function(object) {
    // fast path
    var objects = this._objects ;
    if (objects && objects.contains(object)) return YES ;
    
    var sets = this._sets,
        len  = sets ? sets.length : 0,
        idx, set;
    for(idx=0;idx<len;idx++) {
      set = sets[idx];
      if (set && set.indexOf(object)>=0) return YES;
    }
    
    return NO ;
  },
  
  
  // ..........................................................
  // GENERIC HELPER METHODS
  // 
  
  /**
    Constrains the selection set to only objects found in the passed source
    object.  This will remove any indexes selected in other sources, any 
    indexes beyond the length of the content, and any objects not found in the
    set.
    
    @param {Object} source the source to limit
    @returns {SC.SelectionSet} receiver
  */
  constrain: function(source) {
    var set, len, max, objects;
    
    this.beginPropertyChanges();
    
    // remove sources other than this one
    this.get('sources').forEach(function(cur) {
      if (cur === source) return; //skip
      var set = this._indexSetForSource(source, NO);
      if (set) this.remove(source, set);
    },this); 
    
    // remove indexes beyond end of source length
    set = this._indexSetForSource(source, NO);
    if (set && ((max=set.get('max'))>(len=source.get('length')))) {
      this.remove(source, len, max-len);
    }
    
    // remove objects not in source
    if (objects = this._objects) {
      objects.forEach(function(cur) {
        if (source.indexOf(cur)<0) this.removeObject(cur);
      },this);
    }
    
    this.endPropertyChanges();
    return this ;
  },
  
  /**
    Returns YES if the passed index set or selection set contains the exact 
    same source objects and indexes as  the receiver.  If you pass any object 
    other than an IndexSet or SelectionSet, returns NO.
    
    @param {Object} obj another object.
    @returns {Boolean}
  */
  isEqual: function(obj) {
    var left, right, idx, len, sources, source;
    
    // fast paths
    if (!obj || !obj.isSelectionSet) return NO ;
    if (obj === this) return YES;
    if ((this._sets === obj._sets) && (this._objects === obj._objects)) return YES;
    if (this.get('length') !== obj.get('length')) return NO;
    
    // check objects
    left = this._objects;
    right = obj._objects;
    if (left || right) {
      if ((left ? left.get('length'):0) !== (right ? right.get('length'):0)) {
        return NO;
      }
      if (left && !left.isEqual(right)) return NO ;
    }

    // now go through the sets
    sources = this.get('sources');
    len     = sources.get('length');
    for(idx=0;idx<len;idx++) {
      source = sources.objectAt(idx);
      left = this._indexSetForSource(source, NO);
      right = this._indexSetForSource(source, NO);
      if (!!right !== !!left) return NO ;
      if (left && !left.isEqual(right)) return NO ;
    }
    
    return YES ;
  },

  /**
    Clears the set.  Removes all IndexSets from the object
    
    @returns {SC.SelectionSet}
  */
  clear: function() {
    if (this.isFrozen) throw SC.FROZEN_ERROR;
    if (this._sets) this._sets.length = 0 ; // truncate
    if (this._objects) this._objects = null;
    
    this._indexSetCache = null;
    this.propertyDidChange('length');
    this.enumerableContentDidChange();
    this.notifyPropertyChange('sources');
    
    return this ;
  },
  
  /**
   Clones the set into a new set.  
   
   @returns {SC.SelectionSet}
  */
  copy: function() {
    var ret  = this.constructor.create(),
        sets = this._sets,
        len  = sets ? sets.length : 0 ,
        idx, set;
    
    if (sets && len>0) {
      sets = ret._sets = sets.slice();
      for(idx=0;idx<len;idx++) {
        if (!(set = sets[idx])) continue ;
        set = sets[idx] = set.copy();
        ret[SC.guidFor(set.source)] = idx;
      }
    }
    
    if (this._objects) ret._objects = this._objects.copy();
    return ret ;
  },
  
  /**
    @private 
    
    Freezing a SelectionSet also freezes its internal sets.
  */
  freeze: function() {
    if (this.isFrozen) return this ;
    var sets = this._sets,
        loc  = sets ? sets.length : 0,
        set ;
        
    while(--loc >= 0) {
      if (set = sets[loc]) set.freeze();
    }
    
    if (this._objects) this._objects.freeze();
    return arguments.callee.base.apply(this,arguments);
  },
  
  // ..........................................................
  // ITERATORS
  // 
  
  /** @private */
  toString: function() {
    var sets = this._sets || [];
    sets = sets.map(function(set) { 
      return set.toString().replace("SC.IndexSet", SC.guidFor(set.source)); 
    }, this);
    if (this._objects) sets.push(this._objects.toString());
    return "SC.SelectionSet:%@<%@>".fmt(SC.guidFor(this), sets.join(','));  
  },
  
  /** @private */
  firstObject: function() {
    var sets    = this._sets,
        objects = this._objects;
        
    // if we have sets, get the first one
    if (sets && sets.get('length')>0) {
      var set  = sets ? sets[0] : null,
          src  = set ? set.source : null,
          idx  = set ? set.firstObject() : -1;
      if (src && idx>=0) return src.objectAt(idx);
    }
    
    // otherwise if we have objects, get the first one
    return objects ? objects.firstObject() : undefined;
    
  }.property(),
  
  /** @private
    Implement primitive enumerable support.  Returns each object in the 
    selection.
  */
  nextObject: function(count, lastObject, context) { 
    var objects, ret;
    
    // TODO: Make this more efficient.  Right now it collects all objects
    // first.  
    
    if (count === 0) {
      objects = context.objects = [];
      this.forEach(function(o) { objects.push(o); }, this);
      context.max = objects.length;
    }

    objects = context.objects ;
    ret = objects[count];
    
    if (count+1 >= context.max) {
      context.objects = context.max = null;
    }
    
    return ret ;
  },
  
  /** 
    Iterates over the selection, invoking your callback with each __object__.
    This will actually find the object referenced by each index in the 
    selection, not just the index.

    The callback must have the following signature:
    
    {{{
      function callback(object, index, source, indexSet) { ... }
    }}}
    
    If you pass a target, it will be used when the callback is called.
    
    @param {Function} callback function to invoke.  
    @param {Object} target optional content. otherwise uses window
    @returns {SC.SelectionSet} receiver
  */
  forEach: function(callback, target) {
    var sets = this._sets,
        objects = this._objects,
        len = sets ? sets.length : 0,
        set, idx;
        
    for(idx=0;idx<len;idx++) {
      set = sets[idx];
      if (set) set.forEachObject(callback, target);
    }
    
    if (objects) objects.forEach(callback, target);
    return this ;
  }  
  
});

/** @private */
SC.SelectionSet.prototype.clone = SC.SelectionSet.prototype.copy;

/** 
  Default frozen empty selection set
  
  @property {SC.SelectionSet}
*/
SC.SelectionSet.EMPTY = SC.SelectionSet.create().freeze();

;});
/* >>>>>>>>>> BEGIN source/system/set.js */
tiki.module('sproutcore/runtime:system/set',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:mixins/enumerable');require('sproutcore/runtime:mixins/copyable');require('sproutcore/runtime:mixins/freezable');require('sproutcore/runtime:mixins/observable');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import mixins/enumerable";
"import mixins/copyable";
"import mixins/freezable";
"import mixins/observable";
"export package";

// IMPORTANT NOTE:  This file actually defines two classes: 
// SC.Set is a fully observable set class documented below. 
// SC._CoreSet is just like SC.Set but is not observable.  This is required
// because SC.Observable is built on using sets and requires sets without 
// observability.
//
// We use pointer swizzling below to swap around the actual definitions so 
// that the documentation will turn out right.  (The docs should only 
// define SC.Set - not SC._CoreSet)

/**
  @class 

  An unordered collection of objects.

  A Set works a bit like an array except that its items are not ordered.  
  You can create a set to efficiently test for membership for an object. You 
  can also iterate through a set just like an array, even accessing objects
  by index, however there is no gaurantee as to their order.

  Note that SC.Set is a primitive object, like an array.  It does implement
  limited key-value observing support but it does not extend from SC.Object
  so you should not subclass it.

  h1. Creating a Set

  You can create a set like you would most objects using SC.Set.create().  
  Most new sets you create will be empty, but you can also initialize the set 
  with some content by passing an array or other enumerable of objects to the 
  constructor.

  Finally, you can pass in an existing set and the set will be copied.  You
  can also create a copy of a set by calling SC.Set#clone().

  {{{
    // creates a new empty set
    var foundNames = SC.Set.create();

    // creates a set with four names in it.
    var names = SC.Set.create(["Charles", "Peter", "Chris", "Erich"]) ;

    // creates a copy of the names set.
    var namesCopy = SC.Set.create(names);

    // same as above.
    var anotherNamesCopy = names.clone();
  }}}

  h1. Adding/Removing Objects

  You generally add or removed objects from a set using add() or remove().
  You can add any type of object including primitives such as numbers,
  strings, and booleans.

  Note that objects can only exist one time in a set.  If you call add() on
  a set with the same object multiple times, the object will only be added 
  once.  Likewise, calling remove() with the same object multiple times will
  remove the object the first time and have no effect on future calls until 
  you add the object to the set again.

  Note that you cannot add/remove null or undefined to a set.  Any attempt to
  do so will be ignored.  

  In addition to add/remove you can also call push()/pop().  Push behaves just
  like add() but pop(), unlike remove() will pick an arbitrary object, remove
  it and return it.  This is a good way to use a set as a job queue when you
  don't care which order the jobs are executed in.

  h1. Testing for an Object

  To test for an object's presence in a set you simply call SC.Set#contains().
  This method tests for the object's hash, which is generally the same as the
  object's _guid but if you implement the hash() method on the object, it will
  use the return value from that method instead.

  @extends SC.Enumerable 
  @extends SC.Observable
  @extends SC.Copyable
  @extends SC.Freezable

  @since SproutCore 1.0
*/
SC.Set = SC.mixin({}, 
  SC.Enumerable, 
  SC.Observable, 
  SC.Freezable, 
/** @scope SC.Set.prototype */ {

  /** 
    Creates a new set, with the optional array of items included in the 
    return set.

    @param {SC.Enumerable} items items to add
    @return {SC.Set}
  */
  create: function(items) {
    var ret, idx, pool = SC.Set._pool, isObservable = this.isObservable;
    if (!isObservable && items===undefined && pool.length>0) ret = pool.pop();
    else {
      ret = SC.beget(this);
      if (isObservable) ret.initObservable();
      
      if (items && items.isEnumerable && items.get('length')>0) {

        ret.isObservable = NO; // suspend change notifications
        
        // arrays and sets get special treatment to make them a bit faster
        if (items.isSCArray) {
          idx = items.get ? items.get('length') : items.length;
          while(--idx>=0) ret.add(items.objectAt(idx));
        
        } else if (items.isSet) {
          idx = items.length;
          while(--idx>=0) ret.add(items[idx]);
          
        // otherwise use standard SC.Enumerable API
        } else items.forEach(function(i) { ret.add(i); }, this);
        
        ret.isObservable = isObservable;
      }
    }
    return ret ;
  },
  
  /**
    Walk like a duck
    
    @property {Boolean}
  */
  isSet: YES,
  
  /**
    This property will change as the number of objects in the set changes.

    @property {Number}
  */
  length: 0,

  /**
    Returns the first object in the set or null if the set is empty
    
    @property {Object}
  */
  firstObject: function() {
    return (this.length>0) ? this[0] : undefined ;
  }.property(),
  
  /**
    Clears the set 
    
    @returns {SC.Set}
  */
  clear: function() { 
    if (this.isFrozen) throw SC.FROZEN_ERROR;
    this.length = 0;
    return this ;
  },

  /**
    Call this method to test for membership.
    
    @returns {Boolean}
  */
  contains: function(obj) {

    // because of the way a set is "reset", the guid for an object may 
    // still be stored as a key, but points to an index that is beyond the
    // length.  Therefore the found idx must both be defined and less than
    // the current length.
    if (obj === null) return NO ;
    var idx = this[SC.hashFor(obj)] ;
    return (!SC.none(idx) && (idx < this.length) && (this[idx]===obj)) ;
  },
  
  /**
    Returns YES if the passed object is also a set that contains the same 
    objects as the receiver.
  
    @param {SC.Set} obj the other object
    @returns {Boolean}
  */
  isEqual: function(obj) {
    // fail fast
    if (!obj || !obj.isSet || (obj.get('length') !== this.get('length'))) {
      return NO ;
    }
    
    var loc = this.get('length');
    while(--loc>=0) {
      if (!obj.contains(this[loc])) return NO ;
    }
    
    return YES;
  },

  /**
    Call this method to add an object. performs a basic add.

    If the object is already in the set it will not be added again.

    @param obj {Object} the object to add
    @returns {SC.Set} receiver
  */
  add: function(obj) {
    if (this.isFrozen) throw SC.FROZEN_ERROR;
    
    // cannot add null to a set.
    if (obj===null || obj===undefined) return this; 

    var guid = SC.hashFor(obj) ;
    var idx = this[guid] ;
    var len = this.length ;
    if ((idx===null || idx===undefined) || (idx >= len) || (this[idx]!==obj)){
      this[len] = obj ;
      this[guid] = len ;
      this.length = len+1;
    }
    
    if (this.isObservable) this.enumerableContentDidChange();
    
    return this ;
  },

  /**
    Add all the items in the passed array or enumerable

    @returns {SC.Set} receiver
  */
  addEach: function(objects) {
    if (this.isFrozen) throw SC.FROZEN_ERROR;
    if (!objects || !objects.isEnumerable) {
      throw "%@.addEach must pass enumerable".fmt(this);
    }

    var idx, isObservable = this.isObservable ;
    
    if (isObservable) this.beginPropertyChanges();
    if (objects.isSCArray) {
      idx = objects.get('length');
      while(--idx >= 0) this.add(objects.objectAt(idx)) ;
    } else if (objects.isSet) {
      idx = objects.length;
      while(--idx>=0) this.add(objects[idx]);
      
    } else objects.forEach(function(i) { this.add(i); }, this);
    if (isObservable) this.endPropertyChanges();
    
    return this ;
  },  

  /**
    Removes the object from the set if it is found.

    If the object is not in the set, nothing will be changed.

    @param obj {Object} the object to remove
    @returns {SC.Set} receiver
  */  
  remove: function(obj) {
    if (this.isFrozen) throw SC.FROZEN_ERROR;

    if (SC.none(obj)) return this ;
    var guid = SC.hashFor(obj);
    var idx = this[guid] ;
    var len = this.length;

    // not in set.
    if (SC.none(idx) || (idx >= len) || (this[idx] !== obj)) return this; 

    // clear the guid key
    delete this[guid] ;

    // to clear the index, we will swap the object stored in the last index.
    // if this is the last object, just reduce the length.
    if (idx < (len-1)) {
      obj = this[idx] = this[len-1];
      this[SC.hashFor(obj)] = idx ;
    }

    // reduce the length
    this.length = len-1;
    if (this.isObservable) this.enumerableContentDidChange();
    return this ;
  },

  /**
    Removes an arbitrary object from the set and returns it.

    @returns {Object} an object from the set or null
  */
  pop: function() {
    if (this.isFrozen) throw SC.FROZEN_ERROR;
    var obj = (this.length > 0) ? this[this.length-1] : null ;
    if (obj) this.remove(obj) ;
    return obj ;
  },

  /**
    Removes all the items in the passed array.

    @returns {SC.Set} receiver
  */
  removeEach: function(objects) {
    if (this.isFrozen) throw SC.FROZEN_ERROR;
    if (!objects || !objects.isEnumerable) {
      throw "%@.addEach must pass enumerable".fmt(this);
    }

    var idx, isObservable = this.isObservable ;
    
    if (isObservable) this.beginPropertyChanges();
    if (objects.isSCArray) {
      idx = objects.get('length');
      while(--idx >= 0) this.remove(objects.objectAt(idx)) ;
    } else if (objects.isSet) {
      idx = objects.length;
      while(--idx>=0) this.remove(objects[idx]);
    } else objects.forEach(function(i) { this.remove(i); }, this);
    if (isObservable) this.endPropertyChanges();
    
    return this ;
  },  

  /**
   Clones the set into a new set.  

    @returns {SC.Set} new copy
  */
  copy: function() {
    return this.constructor.create(this);    
  },

  /**
    Return a set to the pool for reallocation.

    @returns {SC.Set} receiver
  */
  destroy: function() {
    this.isFrozen = NO ; // unfreeze to return to pool
    if (!this.isObservable) SC.Set._pool.push(this.clear());
    return this;
  },
  
  // .......................................
  // PRIVATE 
  //

  /** @private - optimized */
  forEach: function(iterator, target) {
    var len = this.length;
    if (!target) target = this ;
    for(var idx=0;idx<len;idx++) iterator.call(target, this[idx], idx, this);
    return this ;
  },

  /** @private */
  toString: function() {
    var len = this.length, idx, ary = [];
    for(idx=0;idx<len;idx++) ary[idx] = this[idx];
    return "SC.Set<%@>".fmt(ary.join(',')) ;
  },
  
  // the pool used for non-observable sets
  _pool: [],

  /** @private */
  isObservable: YES

}) ;

SC.Set.constructor = SC.Set;

// Make SC.Set look a bit more like other enumerables

/** @private */
SC.Set.clone = SC.Set.copy ;

/** @private */
SC.Set.push = SC.Set.unshift = SC.Set.add ;

/** @private */
SC.Set.shift = SC.Set.pop ;

// add generic add/remove enumerable support

/** @private */
SC.Set.addObject = SC.Set.add ;

/** @private */
SC.Set.removeObject = SC.Set.remove;

SC.Set._pool = [];

// ..........................................................
// CORE SET
// 

/** @class

  CoreSet is just like set but not observable.  If you want to use the set 
  as a simple data structure with no observing, CoreSet is slightly faster
  and more memory efficient.
  
  @extends SC.Set
  @since SproutCore 1.0
*/
SC.CoreSet = SC.beget(SC.Set);

/** @private */
SC.CoreSet.isObservable = NO ;

/** @private */
SC.CoreSet.constructor = SC.CoreSet;
;});
/* >>>>>>>>>> BEGIN source/system/sparse_array.js */
tiki.module('sproutcore/runtime:system/sparse_array',function(require,exports,module){var $m__ = require('sproutcore/runtime:core'), SC=$m__.SC,SproutCore=$m__.SproutCore,YES=$m__.YES,NO=$m__.NO;require('sproutcore/runtime:system/index_set');require('sproutcore/runtime:system/object');require('sproutcore/runtime:mixins/enumerable');require('sproutcore/runtime:mixins/array');require('sproutcore/runtime:mixins/delegate_support');// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

"import core";
"import system/index_set";
"import system/object";
"import mixins/enumerable";
"import mixins/array";
"import mixins/delegate_support";
"export package";

/**
  @class

  A dynamically filled array.  A SparseArray makes it easy for you to create 
  very large arrays of data but then to defer actually populating that array
  until it is actually needed.  This is often much faster than generating an
  array up front and paying the cost to load your data then.
  
  Although technically all arrays in JavaScript are "sparse" (in the sense 
  that you can read and write properties are arbitrary indexes), this array
  keeps track of which elements in the array have been populated already 
  and which ones have not.  If you try to get a value at an index that has 
  not yet been populated, the SparseArray will notify a delegate object first
  giving the delegate a chance to populate the component.
  
  Most of the time, you will use a SparseArray to incrementally load data 
  from the server.  For example, if you have a contact list with 3,000
  contacts in it, you may create a SparseArray with a length of 3,000 and set
  that as the content for a ListView.  As the ListView tries to display the
  visible contacts, it will request them from the SparseArray, which will in
  turn notify your delegate, giving you a chance to load the contact data from
  the server.
  
  @extends SC.Enumerable
  @extends SC.Array
  @extends SC.Observable
  @extends SC.DelegateSupport
  @since SproutCore 1.0
*/

SC.SparseArray = SC.Object.extend(SC.Enumerable, SC.Array, 
  SC.DelegateSupport, /** @scope SC.SparseArray.prototype */ {  

  // ..........................................................
  // LENGTH SUPPORT
  // 

  _requestingLength: 0,  
  _requestingIndex: 0,
   
  /**
    The length of the sparse array.  The delegate for the array should set 
    this length.
    
    @property {Number}
  */
  length: function() {
    var del = this.delegate ;
    if (del && SC.none(this._length) && del.sparseArrayDidRequestLength) {
      this._requestingLength++ ;
      del.sparseArrayDidRequestLength(this);
      this._requestingLength-- ;
    }
    return this._length || 0 ;
  }.property().cacheable(),

  /**
    Call this method from a delegate to provide a length for the sparse array.
    If you pass null for this property, it will essentially "reset" the array
    causing your delegate to be called again the next time another object 
    requests the array length.
  
    @param {Number} length the length or null
    @returns {SC.SparseArray} receiver
  */
  provideLength: function(length) {
    if (SC.none(length)) this._sa_content = null ;
    if (length !== this._length) {
      this._length = length ;
      if (this._requestingLength <= 0) this.enumerableContentDidChange() ;
    }
    return this ;
  },

  // ..........................................................
  // READING CONTENT 
  // 

  /** 
    The minimum range of elements that should be requested from the delegate.
    If this value is set to larger than 1, then the sparse array will always
    fit a requested index into a range of this size and request it.
    
    @property {Number}
  */
  rangeWindowSize: 1,
  
  /*
    This array contains all the start_indexes of ranges requested. This is to 
    avoid calling sparseArrayDidRequestRange to often. Indexes are removed and 
    added as range requests are completed.
  */
  requestedRangeIndex: [],
  
  /** 
    Returns the object at the specified index.  If the value for the index
    is currently undefined, invokes the didRequestIndex() method to notify
    the delegate.
    
    @param  {Number} idx the index to get
    @return {Object} the object
  */
  objectAt: function(idx) {
    var content = this._sa_content, ret ;
    if (!content) content = this._sa_content = [] ;
    if ((ret = content[idx]) === undefined) {
      this.requestIndex(idx);
      ret = content[idx]; // just in case the delegate provided immediately
    }
    return ret ;
  },

  /**
    Returns the set of indexes that are currently defined on the sparse array.
    If you pass an optional index set, the search will be limited to only 
    those indexes.  Otherwise this method will return an index set containing
    all of the defined indexes.  Currently this can be quite expensive if 
    you have a lot of indexes defined.
    
    @param {SC.IndexSet} indexes optional from indexes
    @returns {SC.IndexSet} defined indexes
  */
  definedIndexes: function(indexes) {
    var ret = SC.IndexSet.create(),
        content = this._sa_content,
        idx, len;
        
    if (!content) return ret.freeze(); // nothing to do
    
    if (indexes) {
      indexes.forEach(function(idx) { 
        if (content[idx] !== undefined) ret.add(idx);
      });
    } else {      
      len = content.length;
      for(idx=0;idx<len;idx++) {
        if (content[idx] !== undefined) ret.add(idx);
      }
    }
    
    return ret.freeze();
  },
  
  _TMP_RANGE: {},
  
  /**
    Called by objectAt() whenever you request an index that has not yet been
    loaded.  This will possibly expand the index into a range and then invoke
    an appropriate method on the delegate to request the data.
    
    It will check if the range has been already requested.
    
    @param {Number} idx the index to retrieve
    @returns {SC.SparseArray} receiver
  */
  requestIndex: function(idx) {
    var del = this.delegate;
    if (!del) return this; // nothing to do
    
    // adjust window
    var len = this.get('rangeWindowSize'), start = idx;
    if (len > 1) start = start - Math.floor(start % len);
    if (len < 1) len = 1 ;
    
    // invoke appropriate callback
    this._requestingIndex++;
    if (del.sparseArrayDidRequestRange) {
      var range = this._TMP_RANGE;
      if(this.wasRangeRequested(start)===-1){
        range.start = start;
        range.length = len;
        del.sparseArrayDidRequestRange(this, range);
        this.requestedRangeIndex.push(start);
      }
    } else if (del.sparseArrayDidRequestIndex) {
      while(--len >= 0) del.sparseArrayDidRequestIndex(this, start + len);
    }
    this._requestingIndex--;

    return this ;
  },
  
  /*
    This method is called by requestIndex to check if the range has already 
    been requested. We assume that rangeWindowSize is not changed often.
    
     @param {Number} startIndex
     @return {Number} index in requestRangeIndex
  */
  wasRangeRequested: function(rangeStart) {
    var i, ilen;
    for(i=0, ilen=this.requestedRangeIndex.length; i<ilen; i++){
      if(this.requestedRangeIndex[i]===rangeStart) return i;
    }
    return -1;
  },
  
  /*
    This method has to be called after a request for a range has completed.
    To remove the index from the sparseArray to allow future updates on the 
    range.
    
     @param {Number} startIndex
     @return {Number} index in requestRangeIndex
  */
  rangeRequestCompleted: function(start) { 
    var i = this.wasRangeRequested(start);
    if(i>=0) { 
      this.requestedRangeIndex.removeAt(i,1);
      return YES;
    }
    return NO;
  },
  
  /**
    This method sets the content for the specified to the objects in the 
    passed array.  If you change the way SparseArray implements its internal
    tracking of objects, you should override this method along with 
    objectAt().
    
    @param {Range} range the range to apply to
    @param {Array} array the array of objects to insert
    @returns {SC.SparseArray} reciever
  */
  provideObjectsInRange: function(range, array) {
    var content = this._sa_content ;
    if (!content) content = this._sa_content = [] ;
    var start = range.start, len = range.length;
    while(--len >= 0) content[start+len] = array[len];
    if (this._requestingIndex <= 0) this.enumerableContentDidChange() ;
    return this ;
  },

  _TMP_PROVIDE_ARRAY: [],
  _TMP_PROVIDE_RANGE: { length: 1 },
  
  /**
    Convenience method to provide a single object at a specified index.  Under
    the covers this calls provideObjectsInRange() so you can override only 
    that method and this one will still work.
    
    @param {Number} index the index to insert
    @param {Object} the object to insert
    @return {SC.SparseArray} receiver
  */
  provideObjectAtIndex: function(index, object) {
    var array = this._TMP_PROVIDE_ARRAY, range = this._TMP_PROVIDE_RANGE;
    array[0] = object;
    range.start = index;
    return this.provideObjectsInRange(range, array);
  },

  /**
    Invalidates the array content in the specified range.  This is not the 
    same as editing an array.  Rather it will cause the array to reload the
    content from the delegate again when it is requested.
    
    @param {Range} the range
    @returns {SC.SparseArray} receiver
  */
  objectsDidChangeInRange: function(range) {

    // delete cached content
    var content = this._sa_content ;
    if (content) {
      // if range covers entire length of cached content, just reset array
      if (range.start === 0 && SC.maxRange(range)>=content.length) {
        this._sa_content = null ;
        
      // otherwise, step through the changed parts and delete them.
      } else {
        var start = range.start, loc = Math.min(start + range.length, content.length);
        while (--loc>=start) content[loc] = undefined;
      }
    }    
    this.enumerableContentDidChange(range) ; // notify
    return this ;
  },
  
  /**
    Optimized version of indexOf().  Asks the delegate to provide the index 
    of the specified object.  If the delegate does not implement this method
    then it will search the internal array directly.
    
    @param {Object} obj the object to search for
    @returns {Number} the discovered index or -1 if not found
  */
  indexOf: function(obj) {
    var del = this.delegate ;
    if (del && del.sparseArrayDidRequestIndexOf) {
      return del.sparseArrayDidRequestIndexOf(this, obj);
    } else {
      var content = this._sa_content ;
      if (!content) content = this._sa_content = [] ;
      return content.indexOf(obj) ;
    }
  },  
  
  // ..........................................................
  // EDITING
  // 

  /**
    Array primitive edits the objects at the specified index unless the 
    delegate rejects the change.
    
    @param {Number} idx the index to begin to replace
    @param {Number} amt the number of items to replace
    @param {Array} objects the new objects to set instead
    @returns {SC.SparseArray} receiver
  */
  replace: function(idx, amt, objects) {
    objects = objects || [] ;

    // if we have a delegate, get permission to make the replacement.
    var del = this.delegate ;
    if (del) {
      if (!del.sparseArrayShouldReplace || 
          !del.sparseArrayShouldReplace(this, idx, amt, objects)) {
            return this;
      }
    }

    // go ahead and apply to local content.
    var content = this._sa_content ;
    if (!content) content = this._sa_content = [] ;
    content.replace(idx, amt, objects) ;
    
    // update length
    var len = objects ? (objects.get ? objects.get('length') : objects.length) : 0;
    var delta = len - amt ;
    if (!SC.none(this._length)) {
      this.propertyWillChange('length');
      this._length += delta;
      this.propertyDidChange('length');
    }

    this.enumerableContentDidChange(idx, amt, delta) ;
    return this ;
  },

  /** 
    Resets the SparseArray, causing it to reload its content from the 
    delegate again.
    
    @returns {SC.SparseArray} receiver
  */
  reset: function() {
    this._sa_content = null ;
    this._length = null ;
    this.enumerableContentDidChange() ;
    this.invokeDelegateMethod(this.delegate, 'sparseArrayDidReset', this);
    return this ;
  }
      
}) ;

/** 
  Convenience metohd returns a new sparse array with a default length already 
  provided.
  
  @param {Number} len the length of the array
  @returns {SC.SparseArray}
*/
SC.SparseArray.array = function(len) {
  return this.create({ _length: len||0 });
};
;});
/* >>>>>>>>>> BEGIN package_exports.js */
tiki.module('sproutcore/runtime:index', function(require, exports, module) {
var m;
m = require('sproutcore/runtime:core');
exports.SC = m.SC;
exports.SproutCore = m.SproutCore;
exports.YES = m.YES;
exports.NO = m.NO;
require('sproutcore/runtime:mixins/array');
require('sproutcore/runtime:mixins/comparable');
require('sproutcore/runtime:mixins/copyable');
require('sproutcore/runtime:mixins/delegate_support');
require('sproutcore/runtime:mixins/enumerable');
require('sproutcore/runtime:mixins/freezable');
require('sproutcore/runtime:mixins/observable');
require('sproutcore/runtime:system/binding');
require('sproutcore/runtime:system/cookie');
require('sproutcore/runtime:system/enumerator');
require('sproutcore/runtime:system/error');
require('sproutcore/runtime:system/index_set');
require('sproutcore/runtime:system/logger');
require('sproutcore/runtime:system/object');
require('sproutcore/runtime:system/range_observer');
require('sproutcore/runtime:system/run_loop');
require('sproutcore/runtime:system/selection_set');
require('sproutcore/runtime:system/set');
require('sproutcore/runtime:system/sparse_array');
});

; tiki.script('sproutcore/runtime:en/cfe6f8a7557f916515999a065d70bfa71bd13986/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/datastore', {"packages":{"sproutcore\/runtime":{"scripts":[{"url":"\/static\/sproutcore\/runtime\/en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js","id":"sproutcore\/runtime:en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js"}]}},"depends":["sproutcore\/runtime"],"scripts":[{"url":"\/static\/sproutcore\/datastore\/en\/334c3630f54926a13d0810d33b3686af21530ee7\/javascript.js","id":"sproutcore\/datastore:en\/334c3630f54926a13d0810d33b3686af21530ee7\/javascript.js"}]});

/* >>>>>>>>>> BEGIN package_exports.js */
tiki.module('sproutcore/datastore:index', function(require, exports, module) {
var m;
});

; tiki.script('sproutcore/datastore:en/334c3630f54926a13d0810d33b3686af21530ee7/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/foundation', {"packages":{"sproutcore\/runtime":{"scripts":[{"url":"\/static\/sproutcore\/runtime\/en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js","id":"sproutcore\/runtime:en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js"}]}},"stylesheets":[{"url":"\/static\/sproutcore\/foundation\/en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/stylesheet.css","id":"sproutcore\/foundation:en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/stylesheet.css"}],"depends":["sproutcore\/runtime"],"scripts":[{"url":"\/static\/sproutcore\/foundation\/en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/javascript.js","id":"sproutcore\/foundation:en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/javascript.js"}]});
tiki.global('sproutcore/foundation');

/* >>>>>>>>>> BEGIN source/system/locale.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  The Locale defined information about a specific locale, including date and
  number formatting conventions, and localization strings.  You can define
  various locales by adding them to the SC.locales hash, keyed by language
  and/or country code.
  
  On page load, the default locale will be chosen based on the current 
  languages and saved at SC.Locale.current.  This locale is used for 
  localization, etc.
  
  h2. Creating a new locale
  
  You can create a locale by simply extending the SC.Locale class and adding
  it to the locales hash:
  
  {{{
    SC.Locale.locales['en'] = SC.Locale.extend({ .. config .. }) ;
  }}}
  
  Alternatively, you could choose to base your locale on another locale by
  extending that locale:
  
  {{{
    SC.Locale.locales['en-US'] = SC.Locale.locales['en'].extend({ ... }) ;
  }}}
  
  Note that if you do not define your own strings property, then your locale
  will inherit any strings added to the parent locale.  Otherwise you must
  implement your own strings instead.
  
  @extends SC.Object
  @since SproutCore 1.0
*/
SC.Locale = SC.Object.extend({
  
  init: function() {
    // make sure we know the name of our own locale.
    if (!this.language) SC.Locale._assignLocales();
    
    // Make sure we have strings that were set using the new API.  To do this
    // we check to a bool that is set by one of the string helpers.  This 
    // indicates that the new API was used. If the new API was not used, we
    // check to see if the old API was used (which places strings on the 
    // String class). 
    if (!this.hasStrings) {
      var langs = this._deprecatedLanguageCodes || [] ;
      langs.push(this.language);
      var idx = langs.length ;
      var strings = null ;
      while(!strings && --idx >= 0) {
        strings = String[langs[idx]];
      }
      if (strings) {
        this.hasStrings = YES; 
        this.strings = strings ;
      }
    }
  },
  
  /** Set to YES when strings have been added to this locale. */
  hasStrings: NO,
  
  /** The strings hash for this locale. */
  strings: {},
  
  toString: function() {
    if (!this.language) SC.Locale._assignLocales() ;
    return "SC.Locale["+this.language+"]"+SC.guidFor(this) ;
  },
  
  /** 
    Returns the localized version of the string or the string if no match
    was found.
    
    @param {String} string
    @param {String} optional default string to return instead
    @returns {String}
  */
  locWithDefault: function(string, def) {
    return this.strings[string] || def || string ;
  }
  
  
}) ;

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

  /**
    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,
  
  /** 
    Invoked at the start of SproutCore's document onready handler to setup 
    the currentLocale.  This will use the language properties you have set on
    the locale to make a decision.
  */
  createCurrentLocale: function() {

    // get values from String if defined for compatibility with < 1.0 build 
    // tools.
    var autodetect = (String.useAutodetectedLanguage !== undefined) ? String.useAutodetectedLanguage : this.useAutodetectedLanguage; 
    var preferred = (String.preferredLanguage !== undefined) ? String.preferredLanguage : this.preferredLanguage ;

    // determine the language
    var lang = ((autodetect) ? SC.browser.language : null) || preferred || SC.browser.language || 'en';
    lang = SC.Locale.normalizeLanguage(lang) ;

    // get the locale class.  If a class cannot be found, fall back to generic
    // language then to english.
    var klass = this.localeClassFor(lang) ;

    // if the detected language does not match the current language (or there
    // is none) then set it up.
    if (lang != this.currentLanguage) {
      this.currentLanguage = lang ; // save language
      this.currentLocale = klass.create(); // setup locale
    }
    return this.currentLocale ;
  },

  /**
    Finds the locale class for the names language code or creates on based on
    its most likely parent.
  */
  localeClassFor: function(lang) {
    lang = SC.Locale.normalizeLanguage(lang) ;
    var parent, klass = this.locales[lang];
    
    // if locale class was not found and there is a broader-based locale
    // present, create a new locale based on that.
    if (!klass && ((parent = lang.split('-')[0]) !== lang) && (klass = this.locales[parent])) {
      klass = this.locales[lang] = klass.extend() ;      
    }
    
    // otherwise, try to create a new locale based on english.
    if (!klass) klass = this.locales[lang] = this.locales.en.extend();
    
    return klass;
  },

  /** 
    Shorthand method to define the settings for a particular locale.
    The settings you pass here will be applied directly to the locale you
    designate.  

    If you are already holding a reference to a locale definition, you can
    also use this method to operate on the receiver.
    
    If the locale you name does not exist yet, this method will create the 
    locale for you, based on the most closely related locale or english.  For 
    example, if you name the locale 'fr-CA', you will be creating a locale for
    French as it is used in Canada.  This will be based on the general French
    locale (fr), since that is more generic.  On the other hand, if you create
    a locale for manadarin (cn), it will be based on generic english (en) 
    since there is no broader language code to match against.

    @param {String} localeName
    @param {Hash} options
    @returns {SC.Locale} the defined locale
  */
  define: function(localeName, options) {
    var locale ;
    if (options===undefined && (SC.typeOf(localeName) !== SC.T_STRING)) {
      locale = this; options = localeName ;
    } else locale = SC.Locale.localeClassFor(localeName) ;
    SC.mixin(locale.prototype, options) ;
    return locale ;
  },
  
  /**
    Gets the current options for the receiver locale.  This is useful for 
    inspecting registered locales that have not been instantiated.
    
    @returns {Hash} options + instance methods
  */
  options: function() { return this.prototype; },
  
  /**
    Adds the passed hash of strings to the locale's strings table.  Note that
    if the receiver locale inherits its strings from its parent, then the 
    strings table will be cloned first.
    
    @returns {Object} receiver
  */
  addStrings: function(stringsHash) {
    // make sure the target strings hash exists and belongs to the locale
    var strings = this.prototype.strings ;
    if (strings) {
      if (!this.prototype.hasOwnProperty('strings')) {
        this.prototype.strings = SC.clone(strings) ;
      }
    } else strings = this.prototype.strings = {} ;
    
    // add strings hash
    if (stringsHash)  this.prototype.strings = SC.mixin(strings, stringsHash) ;
    this.prototype.hasStrings = YES ;
    return this;
  },
  
  _map: { english: 'en', french: 'fr', german: 'de', japanese: 'ja', jp: 'ja', spanish: 'es' },
  
  /**
    Normalizes the passed language into a two-character language code.
    This method allows you to specify common languages in their full english
    name (i.e. English, French, etc). and it will be treated like their two
    letter code equivalent.
    
    @param {String} languageCode
    @returns {String} normalized code
  */
  normalizeLanguage: function(languageCode) {
    if (!languageCode) return 'en' ;
    return SC.Locale._map[languageCode.toLowerCase()] || languageCode ;
  },
  
  // this method is called once during init to walk the installed locales 
  // and make sure they know their own names.
  _assignLocales: function() {
    for(var key in this.locales) this.locales[key].prototype.language = key;
  },
  
  toString: function() {
    if (!this.prototype.language) SC.Locale._assignLocales() ;
    return "SC.Locale["+this.prototype.language+"]" ;
  },
  
  // make sure important properties are copied to new class. 
  extend: function() {
    var ret= SC.Object.extend.apply(this, arguments) ;
    ret.addStrings= SC.Locale.addStrings;
    ret.define = SC.Locale.define ;
    ret.options = SC.Locale.options ;
    ret.toString = SC.Locale.toString ;
    return ret ;
  }
    
}) ;

/** 
  This locales hash contains all of the locales defined by SproutCore and
  by your own application.  See the SC.Locale class definition for the
  various properties you can set on your own locales.
  
  @property {Hash}
*/
SC.Locale.locales = {
  en: SC.Locale.extend({ _deprecatedLanguageCodes: ['English'] }),
  fr: SC.Locale.extend({ _deprecatedLanguageCodes: ['French'] }),
  de: SC.Locale.extend({ _deprecatedLanguageCodes: ['German'] }),
  ja: SC.Locale.extend({ _deprecatedLanguageCodes: ['Japanese', 'jp'] }),
  es: SC.Locale.extend({ _deprecatedLanguageCodes: ['Spanish'] })
} ;




/**
  This special helper will store the strings you pass in the locale matching
  the language code.  If a locale is not defined from the language code you
  specify, then one will be created for you with the english locale as the 
  parent.
  
  @param {String} languageCode
  @param {Hash} strings
  @returns {Object} receiver 
*/
SC.stringsFor = function(languageCode, strings) {
  // get the locale, creating one if needed.
  var locale = SC.Locale.localeClassFor(languageCode);
  locale.addStrings(strings) ;
  return this ;
} ;



/* >>>>>>>>>> BEGIN source/lproj/strings.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/locale');

SC.stringsFor('English', {
  '_SC.DateTime.dayNames': 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday',
  '_SC.DateTime.abbreviatedDayNames': 'Sun Mon Tue Wed Thu Fri Sat',
  '_SC.DateTime.monthNames': 'January February March April May June July August September October November December',
  '_SC.DateTime.abbreviatedMonthNames': 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'
}) ;

/* >>>>>>>>>> BEGIN source/core.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  Indicates that the collection view expects to accept a drop ON the specified
  item.
  
  @property {Number}
*/
SC.DROP_ON = 0x01 ;

/**
  Indicates that the collection view expects to accept a drop BEFORE the 
  specified item.
  
  @property {Number}
*/
SC.DROP_BEFORE = 0x02 ;

/**
  Indicates that the collection view expects to accept a drop AFTER the
  specified item.  This is treated just like SC.DROP_BEFORE is most views
  except for tree lists.
  
  @property {Number}
*/
SC.DROP_AFTER = 0x04 ;

/**
  Indicates that the collection view want's to know which operations would 
  be allowed for either drop operation.
  
  @property {Number}
*/
SC.DROP_ANY = 0x07 ;

SC.mixin(/** @lends SC */ {
  
  /**
    Reads or writes data from a global cache.  You can use this facility to
    store information about an object without actually adding properties to
    the object itself.  This is needed especially when working with DOM,
    which can leak easily in IE.
    
    To read data, simply pass in the reference element (used as a key) and
    the name of the value to read.  To write, also include the data.
    
    You can also just pass an object to retrieve the entire cache.
    
    @param elem {Object} An object or Element to use as scope
    @param name {String} Optional name of the value to read/write
    @param data {Object} Optional data.  If passed, write.
    @returns {Object} the value of the named data
  */
  data: function(elem, name, data) {
    elem = (elem === window) ? "@window" : elem ;
    var hash = SC.hashFor(elem) ; // get the hash key
    
    // Generate the data cache if needed
    var cache = SC._data_cache ;
    if (!cache) SC._data_cache = cache = {} ;
    
    // Now get cache for element
    var elemCache = cache[hash] ;
    if (name && !elemCache) cache[hash] = elemCache = {} ;
    
    // Write data if provided 
    if (elemCache && (data !== undefined)) elemCache[name] = data ;
    
    return (name) ? elemCache[name] : elemCache ;
  },
  
  /**
    Removes data from the global cache.  This is used throughout the
    framework to hold data without creating memory leaks.
    
    You can remove either a single item on the cache or all of the cached 
    data for an object.
    
    @param elem {Object} An object or Element to use as scope
    @param name {String} optional name to remove. 
    @returns {Object} the value or cache that was removed
  */
  removeData: function(elem, name) {
    elem = (elem === window) ? "@window" : elem ;
    var hash = SC.hashFor(elem) ;
    
    // return undefined if no cache is defined
    var cache = SC._data_cache ;
    if (!cache) return undefined ;
    
    // return undefined if the elem cache is undefined
    var elemCache = cache[hash] ;
    if (!elemCache) return undefined;
    
    // get the return value
    var ret = (name) ? elemCache[name] : elemCache ;
    
    // and delete as appropriate
    if (name) {
      delete elemCache[name] ;
    } else {
      delete cache[hash] ;
    }
    
    return ret ;
  }
}) ;

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

});

/* >>>>>>>>>> BEGIN source/controllers/controller.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class
  
  The controller base class provides some common functions you will need
  for controllers in your applications, especially related to maintaining
  an editing context.
  
  In general you will not use this class, but you can use a subclass such
  as ObjectController, TreeController, or ArrayController.
  
  h2. EDITING CONTEXTS
  
  One major function of a controller is to mediate between changes in the
  UI and changes in the model.  In particular, you usually do not want 
  changes you make in the UI to be applied to a model object directly.  
  Instead, you often will want to collect changes to an object and then
  apply them only when the user is ready to commit their changes.
  
  The editing contact support in the controller class will help you
  provide this capability.
  
  @extends SC.Object
  @since SproutCore 1.0
*/
SC.Controller = SC.Object.extend(
/** @scope SC.Controller.prototype */ {
  
  /**
    Makes a controller editable or not editable.  The SC.Controller class 
    itself does not do anything with this property but subclasses will 
    respect it when modifying content.
    
    @property {Boolean}
  */
  isEditable: YES
  
});

/* >>>>>>>>>> BEGIN source/ext/object.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

// Extensions to the core SC.Object class
SC.mixin(SC.Object.prototype, /** @scope SC.Object.prototype */ {
  
  /**
    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 methodName {String} method name to perform.
    @param interval {Number} period from current time to schedule.
    @returns {SC.Timer} scheduled timer.
  */
  invokeLater: function(methodName, interval) {
    if (interval === undefined) interval = 1 ;
    var f = methodName, args, func;
    
    // if extra arguments were passed - build a function binding.
    if (arguments.length > 2) {
      args = SC.$A(arguments).slice(2);
      if (SC.typeOf(f) === SC.T_STRING) f = this[methodName] ;
      func = f ;
      f = function() { return func.apply(this, args); } ;
    }

    // schedule the timer
    return SC.Timer.schedule({ target: this, action: f, interval: interval });
  },
  
  /**
    Lookup the named property path and then invoke the passed function, 
    passing the resulting value to the function.
    
    This method is a useful way to handle deferred loading of properties.  
    If you want to defer loading a property, you can override this method.
    When the method is called, passing a deferred property, you can load the
    property before invoking the callback method.
    
    You can even swap out the receiver object.  
    
    The callback method should have the signature:
    
    function callback(objectAtPath, sourceObject) { ... }
    
    You may pass either a function itself or a target/method pair.
    
    @param {String} pathName
    @param {Object} target target or method
    @param {Function|String} method
    @returns {SC.Object} receiver
  */
  invokeWith: function(pathName, target, method) {
    // normalize target/method
    if (method === undefined) {
      method = target; target = this;
    }
    if (!target) target = this ;
    if (SC.typeOf(method) === SC.T_STRING) method = target[method];
    
    // get value
    var v = this.getPath(pathName);
    
    // invoke method
    method.call(target, v, this);
    return this ;
  }
  
});
/* >>>>>>>>>> BEGIN source/ext/run_loop.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

// Create anonymous subclass of SC.RunLoop to add support for processing 
// view queues and Timers.
SC.RunLoop = SC.RunLoop.extend(
/** @scope SC.RunLoop.prototype */ {

  /**
    The time the current run loop began executing.
    
    All timers scheduled during this run loop will begin executing as if 
    they were scheduled at this time.
  
    @property {Number}
  */
  startTime: function() {
    if (!this._start) this._start = Date.now();
    return this._start ;  
  }.property(),
  
  /* 
  
    Override to fire and reschedule timers once per run loop.
    
    Note that timers should fire only once per run loop to avoid the 
    situation where a timer might cause an infinite loop by constantly 
    rescheduling itself everytime it is fired.
  */
  endRunLoop: function() {
    this.fireExpiredTimers(); // fire them timers!
    var ret = arguments.callee.base.apply(this,arguments); // do everything else
    this.scheduleNextTimeout(); // schedule a timout if timers remain
    return ret; 
  },
  
  // ..........................................................
  // TIMER SUPPORT
  // 
  
  /**
    Schedules a timer to execute at the specified runTime.  You will not 
    usually call this method directly.  Instead you should work with SC.Timer,
    which will manage both creating the timer and scheduling it.
    
    Calling this method on a timer that is already scheduled will remove it 
    from the existing schedule and reschedule it.
    
    @param {SC.Timer} timer the timer to schedule
    @param {Time} runTime the time offset when you want this to run
    @returns {SC.RunLoop} receiver
  */
  scheduleTimer: function(timer, runTime) {
    // if the timer is already in the schedule, remove it.
    this._timerQueue = timer.removeFromTimerQueue(this._timerQueue);
    
    // now, add the timer ot the timeout queue.  This will walk down the 
    // chain of timers to find the right place to insert it.
    this._timerQueue = timer.scheduleInTimerQueue(this._timerQueue, runTime);
    return this ;
  },
  
  /**
    Removes the named timer from the timeout queue.  If the timer is not 
    currently scheduled, this method will have no effect.
    
    @param {SC.Timer} timer the timer to schedule
    @returns {SC.RunLoop} receiver
  */
  cancelTimer: function(timer) {
    this._timerQueue = timer.removeFromTimerQueue(this._timerQueue) ;
    return this ;
  },

  /** @private - shared array used by fireExpiredTimers to avoid memory */
  TIMER_ARRAY: [],
  
  /**
    Invokes any timers that have expired since this method was last called.
    Usually you will not call this method directly, but it will be invoked 
    automatically at the end of the run loop.
    
    @returns {Boolean} YES if timers were fired, NO otherwise
  */
  fireExpiredTimers: function() {
    if (!this._timerQueue || this._firing) return NO; // nothing to do

    // max time we are allowed to run timers
    var now = this.get('startTime') ;
    
    // avoid recursive calls
    this._firing = YES;
    
    // collect timers to fire.  we do this one time up front to avoid infinite 
    // loops where firing a timer causes it to schedule itself again, causing 
    // it to fire again, etc.
    var timers = this.TIMER_ARRAY ;
    this._timerQueue = this._timerQueue.collectExpiredTimers(timers, now);

    // now step through timers and fire them.
    var idx, len = timers.length;
    for(idx=0;idx<len;idx++) timers[idx].fire();
    
    // cleanup
    var didFire = timers.length > 0 ;
    timers.length = 0 ; // reset for later use...
    this._firing = NO ;
    return didFire; 
  },
  
  /** @private
    Invoked at the end of a runloop, if there are pending timers, a timeout
    will be scheduled to fire when the next timer expires.  You will not 
    usually call this method yourself.  It is invoked automatically at the
    end of a run loop.
    
    @returns {Boolean} YES if a timeout was scheduled
  */
  scheduleNextTimeout: function() {
    var timer = this._timerQueue ;
    
    var ret = NO ;
    // if no timer, and there is an existing timeout, cancel it
    if (!timer) {
      if (this._timeout) clearTimeout(this._timeout);
      
    // otherwise, determine if the timeout needs to be rescheduled.
    } else {
      var nextTimeoutAt = timer._timerQueueRunTime ;
      if (this._timeoutAt !== nextTimeoutAt) { // need to reschedule
        if (this._timeout) clearTimeout(this._timeout); // clear existing...
        // reschedule
        var delay = Math.max(0, nextTimeoutAt - Date.now());
        this._timeout = setTimeout(this._timeoutDidFire, delay);
        this._timeoutAt = nextTimeoutAt ;
      }
      ret = YES ;
    }
    
    return ret ;
  },

  /** @private
    Invoked when a timeout actually fires.  Simply cleanup, then begin and end 
    a runloop. This will fire any expired timers and reschedule.  Note that
    this function will be called with 'this' set to the global context, 
    hence the need to lookup the current run loop.
  */
  _timeoutDidFire: function() {
    var rl = SC.RunLoop.currentRunLoop;
    rl._timeout = rl._timeoutAt = null ; // cleanup
    SC.RunLoop.begin().end();  // begin/end runloop to trigger timers.
  }
  
});

SC.RunLoop.currentRunLoop = SC.RunLoop.create();

/* >>>>>>>>>> BEGIN source/license.js */
/*! @license

Portions of this software are copyright Yahoo, Inc, used under the following license:

Software License Agreement (BSD License)
Copyright (c) 2009, Yahoo! Inc.
All rights reserved.
Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the
following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of Yahoo! Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Yahoo! Inc.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Sources of Intellectual Property Included in the YUI Library
Where not otherwise indicated, all YUI content is authored by Yahoo! engineers and consists of Yahoo!-owned intellectual property. YUI is issued by Yahoo! under the BSD license above. In some specific instances, YUI will incorporate work done by developers outside of Yahoo! with their express permission.

*/
/* >>>>>>>>>> BEGIN source/mixins/button.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @namespace

  This mixin implements many of the basic state-handling attributes for 
  button-like views, including an auto-updated title, and mapping the current
  value to an isSelected state.
  
  Usually you will not work with this mixin directly.  Instead, you should use
  a class that incorporates the mixin such as SC.ButtonView, SC.CheckboxView
  or SC.RadioView.
  
  This mixin assumes you have already applied the SC.Control and 
  SC.DelegateSupport mixins as well.
  
  @since SproutCore 1.0  
*/
SC.Button = {
  
  // ..........................................................
  // VALUE 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
    update the isSelected state of the button view.  The value will also
    change as the user selects or deselects.  You can control which values
    the button will treat as isSelected by setting the toggleOnValue and 
    toggleOffValue.  Alternatively, if you leave these properties set to
    YES or NO, the button will do its best to convert a value to an 
    appropriate state:
  
    - null, false, 0  -> isSelected = false
    - any other single value -> isSelected = true
    - array -> if all values are the same state: that state.  otherwise MIXED.
    
    @property {Object}
  */  
  value: null,
  
  /**
    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 YES
    
    @property {Object}
  */
  toggleOnValue: YES,

  /**
    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 NO 
  
    @property {Object}
  */
  toggleOffValue: NO,
  
  // ..........................................................
  // TITLE 
  // 
  
  /**
    If YES, then the title will be localized.
    
    @property {Boolean}
  */
  localize: NO,
  
  /** @private */
  localizeBindingDefault: SC.Binding.bool(),

  /**
    The button title.  If localize is YES, then this should be the localization key to display.  Otherwise, this will be the actual string displayed in the title.  This property is observable and bindable.
    
    @property {String}
  */  
  title: '',

  /**
    If you set this property, the title property will be updated automatically
    from the content using the key you specify.
    
    @property {String}
  */
  contentTitleKey: null,
  
  /**
    The button icon.  Set this to either a URL or a CSS class name (for 
    spriting).  Note that if you pass a URL, it must contain at 
    least one slash to be detected as such.
    
    @property {String}
  */
  icon: null,

  /**
    If you set this property, the icon will be updated automatically from the
    content using the key you specify.
    
    @property {String}
  */
  contentIconKey: null,

  /**
    If YES, button will attempt to display an ellipsis if the title cannot 
    fit inside of the visible area.  This feature is not available on all
    browsers.
    
    @property {Boolean}
  */
  needsEllipsis: YES,
  
  /**
    The computed display title.  This is generated by localizing the title 
    property if necessary.
    
    @property {String}
  */
  displayTitle: function() {
    var ret = this.get('title');
    return (ret && this.get('localize')) ? ret.loc() : (ret || '');
  }.property('title','localize').cacheable(),
  
  /**
    The key equivalent that should trigger this button on the page.
    
    @property {String}
  */
  keyEquivalent: null,
  
  // ..........................................................
  // METHODS
  // 
  
  /**
    Classes that include this mixin can invoke this method from their 
    render method to render the proper title HTML.  This will include an 
    icon if necessary along with any other standard markup.
    
    @param {SC.RenderContext} context the context to render
    @param {Boolean} firstTime YES if first time rendering
    @returns {SC.RenderContext} the context
  */
  renderTitle: function(context, firstTime) {
    var icon = this.get('icon') ;
    var image = '' ;
    var title = this.get('displayTitle') ;
    var needsTitle = (!SC.none(title) && title.length>0);
    var elem, htmlNode;
    // get the icon.  If there is an icon, then get the image and update it.
    // if there is no image element yet, create it and insert it just before
    // title.
    if (icon) {
      var blank = SC.BLANK_IMAGE_URL;

      image = '<img src="%@1" alt="" class="%@2" />' ;
      if (icon.indexOf('/') >= 0) {
        image = image.fmt(icon, 'icon');
      } else {
        image = image.fmt(blank, icon);
      }
      needsTitle = YES ;
    }
    elem = this.$('label');
    if(firstTime){
      if(this.get('needsEllipsis')){
        context.push('<label class="sc-button-label ellipsis">'+image+title+'</label>'); 
      }else{
          context.push('<label class="sc-button-label">'+image+title+'</label>'); 
      }  
    }else if ( (htmlNode = elem[0])){
      if(needsTitle) { 
        if(this.get('needsEllipsis')){
          elem.addClass('ellipsis');
          htmlNode.innerHTML = image + title;
        }else{
          elem.removeClass('ellipsis');
          htmlNode.innerHTML = image + title;
        } 
      }
      else { htmlNode.innerHTML = ''; } 
    }  
    return context ;
  },

  /**
    Updates the value, title, and icon keys based on the content object, if 
    set.
    
    @property {Object} target the target of the object that changed
    @property {String} key name of property that changed
    @returns {SC.Button} receiver
  */
  contentPropertyDidChange: function(target, key) {
    var del = this.get('displayDelegate');
    var content = this.get('content'), value ;

    var valueKey = this.getDelegateProperty('contentValueKey', del) ;
    if (valueKey && (key === valueKey || key === '*')) {
      this.set('value', content ? content.get(valueKey) : null) ;
    }

    var titleKey = this.getDelegateProperty('contentTitleKey', del) ;
    if (titleKey && (key === titleKey || key === '*')) {
      this.set('title', content ? content.get(titleKey) : null) ;
    }

    var iconKey = this.getDelegateProperty('contentIconKey', del);
    if (iconKey && (key === iconKey || key === '*')) {
      this.set('icon', content ? content.get(iconKey) : null) ;
    }
    
    return this ;
  },

  /** @private - when title changes, dirty display. */
  _button_displayObserver: function() {
    this.displayDidChange();
  }.observes('title', 'icon', 'value'),

  /**
    Handle a key equivalent if set.  Trigger the default action for the 
    button.  Depending on the implementation this may vary.
    
    @param {String} keystring
    @param {SC.Event} evt
    @returns {Boolean}  YES if handled, NO otherwise
  */
  performKeyEquivalent: function(keystring, evt) {

    if (!this.get('isEnabled')) return NO;
    var equiv = this.get('keyEquivalent');

    // 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)
    if (equiv) {
      if (equiv === keystring) return this.triggerAction(evt);
    
    // should fire if isDefault OR isCancel.  This way if isDefault AND 
    // isCancel, responds to both return and escape
    } else if ((this.get('isDefault') && (keystring === 'return')) ||
        (this.get('isCancel') && (keystring === 'escape'))) {
          return this.triggerAction(evt);
    }

    return NO; // did not handle it; keep searching
  },

  /**
    Your class should implement this method to perform the default action on
    the button.  This is used to implement keyboard control.  Your button
    may make this change in its own way also.
    
    @property {SC.Event} evt the event
    @returns {void}
  */
  triggerAction: function(evt) {
    throw "SC.Button.triggerAction() is not defined in %@".fmt(this);
  },

  // ..........................................................
  // VALUE <-> isSelected STATE MANAGEMNT
  // 

  /**
    This is the standard logic to compute a proposed isSelected state for a
    new value.  This takes into account the toggleOnValue/toggleOffValue 
    properties, among other things.  It may return YES, NO, or SC.MIXED_STATE.
    
    @param {Object} value
    @returns {Boolean} return state
  */
  computeIsSelectedForValue: function(value) {
    var targetValue = this.get('toggleOnValue') ;
    var state, next ;
    
    if (SC.typeOf(value) === SC.T_ARRAY) {

      // treat a single item array like a single value
      if (value.length === 1) {
        state = (value[0] == targetValue) ;
        
      // for a multiple item array, check the states of all items.
      } else {
        state = null;
        value.find(function(x) {
          next = (x == targetValue) ;
          if (state === null) {
            state = next ;
          } else if (next !== state) state = SC.MIXED_STATE ;
          return state === SC.MIXED_STATE ; // stop when we hit a mixed state.
        });
      }
      
    // for single values, just compare to the toggleOnValue...use truthiness
    } else {
      state = (value == targetValue) ;
    }
    return state ;
  },
  
  /** @ignore */
  initMixin: function() {
    // if value is not null, update isSelected to match value.  If value is
    // null, we assume you may be using isSelected only.  
    if (!SC.none(this.get('value'))) this._button_valueDidChange();  
  },
  
  /** @private
    Whenever the button value changes, update the selected state to match.
  */
  _button_valueDidChange: function() {
    var value = this.get('value');  
    var state = this.computeIsSelectedForValue(value);
    this.set('isSelected', state) ; // set new state...
  }.observes('value'),
  
  /** @private
    Whenever the selected state is changed, make sure the button value is also updated.  Note that this may be called because the value has just changed.  In that case this should do nothing.
  */
  _button_isSelectedDidChange: function() {
    var newState = this.get('isSelected');
    var curState = this.computeIsSelectedForValue(this.get('value'));
    
    // fix up the value, but only if computed state does not match.
    // never fix up value if isSelected is set to MIXED_STATE since this can
    // only come from the value.
    if ((newState !== SC.MIXED_STATE) && (curState !== newState)) {
      var valueKey = (newState) ? 'toggleOnValue' : 'toggleOffValue' ;
      this.set('value', this.get(valueKey));
    }
  }.observes('isSelected')
  
} ;

/* >>>>>>>>>> BEGIN source/mixins/collection_content.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================


/**
  Used for contentIndexDisclosureState().  Indicates open branch node.
  
  @property {Number}
*/
SC.BRANCH_OPEN = 0x0011;

/**
  Used for contentIndexDisclosureState().  Indicates closed branch node.
  
  @property {Number}
*/
SC.BRANCH_CLOSED = 0x0012;

/**
  Used for contentIndexDisclosureState().  Indicates leaf node.
  
  @property {Number}
*/
SC.LEAF_NODE = 0x0020;

/**
  @namespace

  This mixin provides standard methods used by a CollectionView to provide
  additional meta-data about content in a collection view such as selection
  or enabled state.
  
  You can apply this mixin to a class that you set as a delegate or to the
  object you set as content.  SC.ArrayControllers automatically implement
  this mixin.
  
  @since SproutCore 1.0
*/
SC.CollectionContent = {

  /**
    Used to detect the mixin by SC.CollectionView

    @property {Boolean}
  */
  isCollectionContent: YES,
  
  /**
    Return YES if the content index should be selected.  Default behavior 
    looks at the selection property on the view.
    
    @param {SC.CollectionView} view the collection view
    @param {SC.Array} content the content object
    @param {Number} idx the content index
    @returns {Boolean} YES, NO, or SC.MIXED_STATE
  */
  contentIndexIsSelected: function(view, content, idx) {
    var sel = view.get('selection');
    return sel ? sel.contains(content, idx) : NO ;
  },
  
  /**
    Returns YES if the content index should be enabled.  Default looks at the
    isEnabled state of the collection view.
    looks at the selection property on the view.
    
    @param {SC.CollectionView} view the collection view
    @param {SC.Array} content the content object
    @param {Number} idx the content index
    @returns {Boolean} YES, NO, or SC.MIXED_STATE
  */
  contentIndexIsEnabled: function(view, content, idx) {
    return view.get('isEnabled');
  },
  
  // ..........................................................
  // GROUPING
  // 
  
  /**
    Optionally return an index set containing the indexes that may be group
    views.  For each group view, the delegate will actually be asked to 
    confirm the view is a group using the contentIndexIsGroup() method.
    
    If grouping is not enabled, return null.
    
    @param {SC.CollectionView} view the calling view
    @param {SC.Array} content the content object
    @return {SC.IndexSet} 
  */
  contentGroupIndexes: function(view, content) {
    return null;
  },
  
  /**
    Returns YES if the item at the specified content index should be rendered
    using the groupExampleView instead of the regular exampleView.  Note that
    a group view is different from a branch/leaf view.  Group views often 
    appear with different layout and a different look and feel.

    Default always returns NO.
    
    @param {SC.CollectionView} view the collection view
    @param {SC.Array} content the content object
    @param {Number} idx the content index
    @returns {Boolean} YES, NO, or SC.MIXED_STATE
  */
  contentIndexIsGroup: function(view, content, idx) {
    return NO ;
  },
  
  // ..........................................................
  // OUTLINE VIEWS
  // 
  
  /**
    Returns the outline level for the item at the specified index.  Can be 
    used to display hierarchical lists.
    
    Default always returns -1 (no outline).
    
    @param {SC.CollectionView} view the collection view
    @param {SC.Array} content the content object
    @param {Number} idx the content index
    @returns {Boolean} YES, NO, or SC.MIXED_STATE
  */
  contentIndexOutlineLevel: function(view, content, idx) {
    return -1;
  },
  
  /**
    Returns a constant indicating the disclosure state of the item.  Must be
    one of SC.BRANCH_OPEN, SC.BRANCH_CLOSED, SC.LEAF_NODE.  If you return one
    of the BRANCH options then the item may be rendered with a disclosure 
    triangle open or closed.  If you return SC.LEAF_NODe then the item will 
    be rendered as a leaf node.  

    Default returns SC.LEAF_NODE.
    
    @param {SC.CollectionView} view the collection view
    @param {SC.Array} content the content object
    @param {Number} idx the content index
    @returns {Boolean} YES, NO, or SC.MIXED_STATE
  */
  contentIndexDisclosureState: function(view, content, idx) {
    return SC.LEAF_NODE;    
  },
  
  /**
    Called to expand a content index item if it is currently in a closed 
    disclosure state.  The default implementation does nothing.
    
    @param {SC.CollectionView} view the collection view
    @param {SC.Array} content the content object
    @param {Number} idx the content index
    @returns {void}
  */
  contentIndexExpand: function(view, content, idx) {
    console.log('contentIndexExpand(%@, %@, %@)'.fmt(view,content,idx));
  },
  
  /**
    Called to collapse a content index item if it is currently in an open 
    disclosure state.  The default implementation does nothing.  
    
    @param {SC.CollectionView} view the collection view
    @param {SC.Array} content the content object
    @param {Number} idx the content index
    @returns {void}
  */
  contentIndexCollapse: function(view, content, idx) {
    console.log('contentIndexCollapse(%@, %@, %@)'.fmt(view,content,idx));
  }
    
};

/* >>>>>>>>>> BEGIN source/mixins/content_display.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @namespace

  The SC.ContentDisplay mixin makes it easy to automatically update your view
  display whenever relevant properties on a content object change.  To use
  this mixin, include it in your view and then add the names of the 
  properties on the content object you want to trigger a displayDidChange() 
  method on your view. Your updateDisplay() method will then be called at the 
  end of the run loop.
  
  h2. Example
  
  {{{
    MyApp.MyViewClass = SC.View.extend(SC.ContentDisplay, { 
      contentDisplayProperties: 'title isEnabled hasChildren'.w(),
      ...
    });
  }}}
  
  @since SproutCore 1.0
*/
SC.ContentDisplay = {
  
  /** @private */
  concatenatedProperties: 'contentDisplayProperties',

  /** @private */
  displayProperties: ['content'],
  
  /** 
    Add an array with the names of any property on the content object that
    should trigger an update of the display for your view.  Changes to the
    content object will only invoke your display method once per runloop.
    
    @property {Array}
  */
  contentDisplayProperties: [],
  
  /** @private */
  _display_contentDidChange: function(target, key, value) {
    // handle changes to the content...
    if ((value = this.get('content')) != this._display_content) {

      // get the handler method
      var f = this._display_contentPropertyDidChange ;
      
      // stop listening to old content.
      var content = this._display_content;
      if (content) {
        if (SC.isArray(content)) {
          content.invoke('removeObserver', '*', this, f) ;
        } else if (content.removeObserver) {
          content.removeObserver('*', this, f) ;
        }
      }
      
      // start listening for changes on the new content object.
      content = this._display_content = value ; 
      if (content) {
        if (SC.isArray(content)) {
          content.invoke('addObserver', '*', this, f) ;
        } else if (content.addObserver) {
          content.addObserver('*', this, f) ;
        }
      }

      // notify everyone that everything is different now.
      this.allPropertiesDidChange() ;
      this.endPropertyChanges() ;
    }
  }.observes('content'),
  
  /** @private Invoked when properties on the content object change. */
  _display_contentPropertyDidChange: function(target, key, value, propertyRevision) {
    if (key === '*') {
      this.displayDidChange() ;
    } else {
      // only update if a displayProperty actually changed...s
      var ary = this.get('contentDisplayProperties') ;
      if (ary && ary.indexOf(key)>=0) this.displayDidChange();
    }
  }
  
} ;

/* >>>>>>>>>> BEGIN source/mixins/string.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/locale');

// These are basic enhancements to the string class used throughout 
// SproutCore.
/** @private */
SC.STRING_TITLEIZE_REGEXP = (/([\s|\-|\_|\n])([^\s|\-|\_|\n]?)/g);

/**
  @namespace
  
  SproutCore implements a variety of enhancements to the built-in String 
  object that make it easy to perform common substitutions and conversions.
  
  Most of the utility methods defined here mirror those found in Prototype
  1.6.
  
  @since SproutCore 1.0
*/
SC.String = {

  /**
    Localizes the string.  This will look up the reciever string as a key 
    in the current Strings hash.  If the key matches, the loc'd value will be
    used.  The resulting string will also be passed through fmt() to insert
    any variables.
    
    @param args {Object...} optional arguments to interpolate also
    @returns {String} the localized and formatted string.
  */
  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.
    if(!SC.Locale.currentLocale) SC.Locale.createCurrentLocale();
    var str = SC.Locale.currentLocale.locWithDefault(this) || this;
    return str.fmt.apply(str,arguments) ;
  },

  /**
    Works just like loc() except that it will return the passed default 
    string if a matching key is not found.
    
    @param {String} def the default to return
    @param {Object...} args optional formatting arguments
    @returns {String} localized and formatted string
  */
  locWithDefault: function(def) {
    if(!SC.Locale.currentLocale) SC.Locale.createCurrentLocale();
    var str = SC.Locale.currentLocale.locWithDefault(def) || this;
    var args = SC.$A(arguments); args.shift(); // remove def param
    return str.fmt.apply(str,args) ;
  },
  
  /** 
    Capitalizes a string.

    h2. Examples
    
    | *Input String* | *Output String* |
    | my favorite items | My favorite items |
    | css-class-name | Css-class-name |
    | action_name | Action_name |
    | innerHTML | InnerHTML |

    @return {String} capitalized string
  */
  capitalize: function() {
    return this.charAt(0).toUpperCase() + this.slice(1) ;
  },
  
  /**
    Capitalizes every word in a string.  Unlike titleize, spaces or dashes 
    will remain in-tact.
    
    h2. Examples
    
    | *Input String* | *Output String* |
    | my favorite items | My Favorite Items |
    | css-class-name | Css-Class-Name |
    | action_name | Action_Name |
    | innerHTML | InnerHTML |

    @returns {String} capitalized string
  */
  capitalizeEach: function() {
    return this.replace(SC.STRING_TITLEIZE_REGEXP, 
      function(str,sep,character) { 
        return (character) ? (sep + character.toUpperCase()) : sep;
      }).capitalize() ;
  },

  /**
    Converts a string to a title.  This will decamelize the string, convert
    separators to spaces and capitalize every word.

    h2. Examples
    
    | *Input String* | *Output String* |
    | my favorite items | My Favorite Items |
    | css-class-name | Css Class Name |
    | action_name | Action Name |
    | innerHTML | Inner HTML |

    @return {String} titleized string.
  */
  titleize: function() {
    var ret = this.replace(/([a-z])([A-Z])/g,'$1_$2'); // decamelize
    return ret.replace(SC.STRING_TITLEIZE_REGEXP, 
      function(str,separater,character) { 
        return (character) ? (' ' + character.toUpperCase()) : ' ';
      }).capitalize() ;
  },
  
  /**
    Camelizes a string.  This will take any words separated by spaces, dashes
    or underscores and convert them into camelCase.
    
    h2. Examples
    
    | *Input String* | *Output String* |
    | my favorite items | myFavoriteItems |
    | css-class-name | cssClassName |
    | action_name | actionName |
    | innerHTML | innerHTML |

    @returns {String} camelized string
  */
  camelize: function() {
    var ret = this.replace(SC.STRING_TITLEIZE_REGEXP, 
      function(str,separater,character) { 
        return (character) ? character.toUpperCase() : '' ;
      }) ;
    var first = ret.charAt(0), lower = first.toLowerCase() ;
    return (first !== lower) ? (lower + ret.slice(1)) : ret ;
  },
  
  /**
    Converts the string into a class name.  This method will camelize your 
    string and then capitalize the first letter.
    
    h2. Examples
    
    | *Input String* | *Output String* |
    | my favorite items | MyFavoriteItems |
    | css-class-name | CssClassName |
    | action_name | ActionName |
    | innerHTML | InnerHtml |

    @returns {String}
  */
  classify: function() {
    var ret = this.replace(SC.STRING_TITLEIZE_REGEXP, 
      function(str,separater,character) { 
        return (character) ? character.toUpperCase() : '' ;
      }) ;
    var first = ret.charAt(0), upper = first.toUpperCase() ;
    return (first !== upper) ? (upper + ret.slice(1)) : ret ;
  },
  
  /**
    Converts a camelized string into all lower case separated by underscores.
    
    h2. Examples
    
    | *Input String* | *Output String* |
    | my favorite items | my favorite items |
    | css-class-name | css-class-name |
    | action_name | action_name |
    | innerHTML | inner_html |

    @returns {String} the decamelized string.
  */
  decamelize: function() { 
    return this.replace(/([a-z])([A-Z])/g,'$1_$2').toLowerCase();
  },

  /**
    Converts a camelized string or a string with spaces or underscores into
    a string with components separated by dashes.
    
    h2. Examples
    
    | *Input String* | *Output String* |
    | my favorite items | my-favorite-items |
    | css-class-name | css-class-name |
    | action_name | action-name |
    | innerHTML | inner-html |

    @returns {String} the dasherized string.
  */
  dasherize: function() {
    return this.decamelize().replace(/[ _]/g,'-') ;  
  },
  
  /**
    Converts a camelized string or a string with dashes or underscores into
    a string with components separated by spaces.
    
    h2. Examples
    
    | *Input String* | *Output String* |
    | my favorite items | my favorite items |
    | css-class-name | css class name |
    | action_name | action name |
    | innerHTML | inner html |

    @returns {String} the humanized string.
  */
  humanize: function() {
    return this.decamelize().replace(/[\-_]/g,' ') ;
  },
  
  /**
    Removes any standard diacritic characters from the string. So, for
    example, all instances of 'Á' will become 'A'.

    @returns {String} the modified string
  */
  removeDiacritics: function() {
    // Lazily create the SC.diacriticMappingTable object.
    var diacriticMappingTable = SC.diacriticMappingTable;
    if (!diacriticMappingTable) {
      SC.diacriticMappingTable = {
       'À':'A', 'Á':'A', 'Â':'A', 'Ã':'A', 'Ä':'A', 'Å':'A', 'Ā':'A', 'Ă':'A',
       'Ą':'A', 'Ǎ':'A', 'Ǟ':'A', 'Ǡ':'A', 'Ǻ':'A', 'Ȁ':'A', 'Ȃ':'A', 'Ȧ':'A',
       'Ḁ':'A', 'Ạ':'A', 'Ả':'A', 'Ấ':'A', 'Ầ':'A', 'Ẩ':'A', 'Ẫ':'A', 'Ậ':'A',
       'Ắ':'A', 'Ằ':'A', 'Ẳ':'A', 'Ẵ':'A', 'Ặ':'A', 'Å':'A', 'Ḃ':'B', 'Ḅ':'B',
       'Ḇ':'B', 'Ç':'C', 'Ć':'C', 'Ĉ':'C', 'Ċ':'C', 'Č':'C', 'Ḉ':'C', 'Ď':'D',
       'Ḋ':'D', 'Ḍ':'D', 'Ḏ':'D', 'Ḑ':'D', 'Ḓ':'D', 'È':'E', 'É':'E', 'Ê':'E',
       'Ë':'E', 'Ē':'E', 'Ĕ':'E', 'Ė':'E', 'Ę':'E', 'Ě':'E', 'Ȅ':'E', 'Ȇ':'E',
       'Ȩ':'E', 'Ḕ':'E', 'Ḗ':'E', 'Ḙ':'E', 'Ḛ':'E', 'Ḝ':'E', 'Ẹ':'E', 'Ẻ':'E',
       'Ẽ':'E', 'Ế':'E', 'Ề':'E', 'Ể':'E', 'Ễ':'E', 'Ệ':'E', 'Ḟ':'F', 'Ĝ':'G',
       'Ğ':'G', 'Ġ':'G', 'Ģ':'G', 'Ǧ':'G', 'Ǵ':'G', 'Ḡ':'G', 'Ĥ':'H', 'Ȟ':'H',
       'Ḣ':'H', 'Ḥ':'H', 'Ḧ':'H', 'Ḩ':'H', 'Ḫ':'H', 'Ì':'I', 'Í':'I', 'Î':'I',
       'Ï':'I', 'Ĩ':'I', 'Ī':'I', 'Ĭ':'I', 'Į':'I', 'İ':'I', 'Ǐ':'I', 'Ȉ':'I',
       'Ȋ':'I', 'Ḭ':'I', 'Ḯ':'I', 'Ỉ':'I', 'Ị':'I', 'Ĵ':'J', 'Ķ':'K', 'Ǩ':'K',
       'Ḱ':'K', 'Ḳ':'K', 'Ḵ':'K', 'Ĺ':'L', 'Ļ':'L', 'Ľ':'L', 'Ḷ':'L', 'Ḹ':'L',
       'Ḻ':'L', 'Ḽ':'L', 'Ḿ':'M', 'Ṁ':'M', 'Ṃ':'M', 'Ñ':'N', 'Ń':'N', 'Ņ':'N',
       'Ň':'N', 'Ǹ':'N', 'Ṅ':'N', 'Ṇ':'N', 'Ṉ':'N', 'Ṋ':'N', 'Ò':'O', 'Ó':'O',
       'Ô':'O', 'Õ':'O', 'Ö':'O', 'Ō':'O', 'Ŏ':'O', 'Ő':'O', 'Ơ':'O', 'Ǒ':'O',
       'Ǫ':'O', 'Ǭ':'O', 'Ȍ':'O', 'Ȏ':'O', 'Ȫ':'O', 'Ȭ':'O', 'Ȯ':'O', 'Ȱ':'O',
       'Ṍ':'O', 'Ṏ':'O', 'Ṑ':'O', 'Ṓ':'O', 'Ọ':'O', 'Ỏ':'O', 'Ố':'O', 'Ồ':'O',
       'Ổ':'O', 'Ỗ':'O', 'Ộ':'O', 'Ớ':'O', 'Ờ':'O', 'Ở':'O', 'Ỡ':'O', 'Ợ':'O',
       'Ṕ':'P', 'Ṗ':'P', 'Ŕ':'R', 'Ŗ':'R', 'Ř':'R', 'Ȑ':'R', 'Ȓ':'R', 'Ṙ':'R',
       'Ṛ':'R', 'Ṝ':'R', 'Ṟ':'R', 'Ś':'S', 'Ŝ':'S', 'Ş':'S', 'Š':'S', 'Ș':'S',
       'Ṡ':'S', 'Ṣ':'S', 'Ṥ':'S', 'Ṧ':'S', 'Ṩ':'S', 'Ţ':'T', 'Ť':'T', 'Ț':'T',
       'Ṫ':'T', 'Ṭ':'T', 'Ṯ':'T', 'Ṱ':'T', 'Ù':'U', 'Ú':'U', 'Û':'U', 'Ü':'U',
       'Ũ':'U', 'Ū':'U', 'Ŭ':'U', 'Ů':'U', 'Ű':'U', 'Ų':'U', 'Ư':'U', 'Ǔ':'U',
       'Ǖ':'U', 'Ǘ':'U', 'Ǚ':'U', 'Ǜ':'U', 'Ȕ':'U', 'Ȗ':'U', 'Ṳ':'U', 'Ṵ':'U',
       'Ṷ':'U', 'Ṹ':'U', 'Ṻ':'U', 'Ụ':'U', 'Ủ':'U', 'Ứ':'U', 'Ừ':'U', 'Ử':'U',
       'Ữ':'U', 'Ự':'U', 'Ṽ':'V', 'Ṿ':'V', 'Ŵ':'W', 'Ẁ':'W', 'Ẃ':'W', 'Ẅ':'W',
       'Ẇ':'W', 'Ẉ':'W', 'Ẋ':'X', 'Ẍ':'X', 'Ý':'Y', 'Ŷ':'Y', 'Ÿ':'Y', 'Ȳ':'Y',
       'Ẏ':'Y', 'Ỳ':'Y', 'Ỵ':'Y', 'Ỷ':'Y', 'Ỹ':'Y', 'Ź':'Z', 'Ż':'Z', 'Ž':'Z',
       'Ẑ':'Z', 'Ẓ':'Z', 'Ẕ':'Z',
       '`': '`',
       'à':'a', 'á':'a', 'â':'a', 'ã':'a', 'ä':'a', 'å':'a', 'ā':'a', 'ă':'a',
       'ą':'a', 'ǎ':'a', 'ǟ':'a', 'ǡ':'a', 'ǻ':'a', 'ȁ':'a', 'ȃ':'a', 'ȧ':'a',
       'ḁ':'a', 'ạ':'a', 'ả':'a', 'ấ':'a', 'ầ':'a', 'ẩ':'a', 'ẫ':'a', 'ậ':'a',
       'ắ':'a', 'ằ':'a', 'ẳ':'a', 'ẵ':'a', 'ặ':'a', 'ḃ':'b', 'ḅ':'b', 'ḇ':'b',
       'ç':'c', 'ć':'c', 'ĉ':'c', 'ċ':'c', 'č':'c', 'ḉ':'c', 'ď':'d', 'ḋ':'d',
       'ḍ':'d', 'ḏ':'d', 'ḑ':'d', 'ḓ':'d', 'è':'e', 'é':'e', 'ê':'e', 'ë':'e',
       'ē':'e', 'ĕ':'e', 'ė':'e', 'ę':'e', 'ě':'e', 'ȅ':'e', 'ȇ':'e', 'ȩ':'e',
       'ḕ':'e', 'ḗ':'e', 'ḙ':'e', 'ḛ':'e', 'ḝ':'e', 'ẹ':'e', 'ẻ':'e', 'ẽ':'e',
       'ế':'e', 'ề':'e', 'ể':'e', 'ễ':'e', 'ệ':'e', 'ḟ':'f', 'ĝ':'g', 'ğ':'g',
       'ġ':'g', 'ģ':'g', 'ǧ':'g', 'ǵ':'g', 'ḡ':'g', 'ĥ':'h', 'ȟ':'h', 'ḣ':'h',
       'ḥ':'h', 'ḧ':'h', 'ḩ':'h', 'ḫ':'h', 'ẖ':'h', 'ì':'i', 'í':'i', 'î':'i',
       'ï':'i', 'ĩ':'i', 'ī':'i', 'ĭ':'i', 'į':'i', 'ǐ':'i', 'ȉ':'i', 'ȋ':'i',
       'ḭ':'i', 'ḯ':'i', 'ỉ':'i', 'ị':'i', 'ĵ':'j', 'ǰ':'j', 'ķ':'k', 'ǩ':'k',
       'ḱ':'k', 'ḳ':'k', 'ḵ':'k', 'ĺ':'l', 'ļ':'l', 'ľ':'l', 'ḷ':'l', 'ḹ':'l',
       'ḻ':'l', 'ḽ':'l', 'ḿ':'m', 'ṁ':'m', 'ṃ':'m', 'ñ':'n', 'ń':'n', 'ņ':'n',
       'ň':'n', 'ǹ':'n', 'ṅ':'n', 'ṇ':'n', 'ṉ':'n', 'ṋ':'n', 'ò':'o', 'ó':'o',
       'ô':'o', 'õ':'o', 'ö':'o', 'ō':'o', 'ŏ':'o', 'ő':'o', 'ơ':'o', 'ǒ':'o',
       'ǫ':'o', 'ǭ':'o', 'ȍ':'o', 'ȏ':'o', 'ȫ':'o', 'ȭ':'o', 'ȯ':'o', 'ȱ':'o',
       'ṍ':'o', 'ṏ':'o', 'ṑ':'o', 'ṓ':'o', 'ọ':'o', 'ỏ':'o', 'ố':'o', 'ồ':'o',
       'ổ':'o', 'ỗ':'o', 'ộ':'o', 'ớ':'o', 'ờ':'o', 'ở':'o', 'ỡ':'o', 'ợ':'o',
       'ṕ':'p', 'ṗ':'p', 'ŕ':'r', 'ŗ':'r', 'ř':'r', 'ȑ':'r', 'ȓ':'r', 'ṙ':'r',
       'ṛ':'r', 'ṝ':'r', 'ṟ':'r', 'ś':'s', 'ŝ':'s', 'ş':'s', 'š':'s', 'ș':'s',
       'ṡ':'s', 'ṣ':'s', 'ṥ':'s', 'ṧ':'s', 'ṩ':'s', 'ţ':'t', 'ť':'t', 'ț':'t',
       'ṫ':'t', 'ṭ':'t', 'ṯ':'t', 'ṱ':'t', 'ẗ':'t', 'ù':'u', 'ú':'u', 'û':'u',
       'ü':'u', 'ũ':'u', 'ū':'u', 'ŭ':'u', 'ů':'u', 'ű':'u', 'ų':'u', 'ư':'u',
       'ǔ':'u', 'ǖ':'u', 'ǘ':'u', 'ǚ':'u', 'ǜ':'u', 'ȕ':'u', 'ȗ':'u', 'ṳ':'u',
       'ṵ':'u', 'ṷ':'u', 'ṹ':'u', 'ṻ':'u', 'ụ':'u', 'ủ':'u', 'ứ':'u', 'ừ':'u',
       'ử':'u', 'ữ':'u', 'ự':'u', 'ṽ':'v', 'ṿ':'v', 'ŵ':'w', 'ẁ':'w', 'ẃ':'w',
       'ẅ':'w', 'ẇ':'w', 'ẉ':'w', 'ẘ':'w', 'ẋ':'x', 'ẍ':'x', 'ý':'y', 'ÿ':'y',
       'ŷ':'y', 'ȳ':'y', 'ẏ':'y', 'ẙ':'y', 'ỳ':'y', 'ỵ':'y', 'ỷ':'y', 'ỹ':'y',
       'ź':'z', 'ż':'z', 'ž':'z', 'ẑ':'z', 'ẓ':'z', 'ẕ':'z'
      };
      diacriticMappingTable = SC.diacriticMappingTable;
    }
    
    var original, replacement;
    var ret = "";
    var length = this.length;
    for (var i = 0; i <= length; ++i) {
      original = this.charAt(i);
      replacement = diacriticMappingTable[original];
      if (replacement) {
        ret += replacement;
      }
      else {
        ret += original;
      }
    }
    return ret;
  },
  
  /**
    Removes any extra whitespace from the edges of the string. This method is 
    also aliased as strip().
    
    @returns {String} the trimmed string
  */
  trim: function () {
    return this.replace(/^\s+|\s+$/g,"");
  },
  
  /**
    Removes any extra whitespace from the left edge of the string.
    
    @returns {String} the trimmed string
  */
  trimLeft: function () {
    return this.replace(/^\s+/g,"");
  },
  
  /**
    Removes any extra whitespace from the right edge of the string.
    
    @returns {String} the trimmed string
  */
  trimRight: function () {
    return this.replace(/\s+$/g,"");
  }
    
};

/** @private */
SC.String.strip = SC.String.trim; // convenience alias.

// Apply SC.String mixin to built-in String object
SC.supplement(String.prototype, SC.String) ;

/** @private */
String.prototype.loc = SC.String.loc; // Two places define it, and we want the version at SC.String.loc

/** @private */
SC.String.fmt = String.prototype.fmt; // copy from runtime


/* >>>>>>>>>> BEGIN source/mixins/control.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('mixins/string');

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

/** 
  Option for HUGE control size.
  
  @property {String}
*/
SC.HUGE_CONTROL_SIZE = 'sc-huge-size' ;

/** 
  Option for large control size.
  
  @property {String}
*/
SC.LARGE_CONTROL_SIZE = 'sc-large-size' ;

/** 
  Option for standard control size.
  
  @property {String}
*/
SC.REGULAR_CONTROL_SIZE = 'sc-regular-size' ;

/** 
  Option for small control size.
  
  @property {String}
*/
SC.SMALL_CONTROL_SIZE = 'sc-small-size' ;

/** 
  Option for tiny control size
  
  @property {String}
*/
SC.TINY_CONTROL_SIZE = 'sc-tiny-size' ;

/**
  @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.
  
  @since SproutCore 1.0
*/
SC.Control = {
  
  /** @private */
  initMixin: function() {
    this._control_contentDidChange() ; // setup content observing if needed.
  },
  
  /** 
    The selected state of this control.  Possible options are YES, NO or 
    SC.MIXED_STATE.
    
    @property {Boolean}
  */
  isSelected: NO,
  
  /** @private */
  isSelectedBindingDefault: SC.Binding.oneWay().bool(),
  
  /**
    Set to YES when the item is currently active.  Usually this means the 
    mouse is current pressed and hovering over the control, however the 
    specific implementation my vary depending on the control.
    
    Changing this property value by default will cause the Control mixin to
    add/remove an 'active' class name to the root element.
    
    @property {Boolean}
  */
  isActive: NO,
  
  /** @private */
  isActiveBindingDefault: SC.Binding.oneWay().bool(),
  
  /**
    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.

    @property {Object}
  */
  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.
    
    @property {SC.Object}
  */
  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.
    
    @property {String}
  */
  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
    @returns {void}
    @test in content
  */
  contentPropertyDidChange: function(target, key) {
    return this.updatePropertyFromContent('value', key, 'contentValueKey');
  },
  
  /**
    Helper method you can use from your own implementation of 
    contentPropertyDidChange().  This method will look up the content key to
    extract a property and then update the property if needed.  If you do
    not pass the content key or the content object, they will be computed 
    for you.  It is more efficient, however, for you to compute these values
    yourself if you expect this method to be called frequently.
    
    @param {String} prop local property to update
    @param {String} key the contentproperty that changed
    @param {String} contentKey the local property that contains the key
    @param {Object} content
    @returns {SC.Control} receiver
  */
  updatePropertyFromContent: function(prop, key, contentKey, content) {
    var all = key === '*';
    if (contentKey === undefined) {
      contentKey = "content%@Key".fmt(prop.capitalize());
    }
    if (content === undefined) content = this.get('content');
    
    // get actual content key
    contentKey = this[contentKey] ?
      this.get(contentKey) :
      this.getDelegateProperty(contentKey, this.displayDelegate) ;
    
    if (contentKey && (all || key === contentKey)) {
      var v = (content) ?
        (content.get ? content.get(contentKey) : content[contentKey]) :
        null ;
      this.set(prop, v) ;
    }
    return this ;
  },
  
  /**
    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.
    
    @returns {void}
  */
  updateContentWithValueObserver: function() {
    var key = this.contentValueKey ?
      this.get('contentValueKey') :
      this.getDelegateProperty('contentValueKey', this.displayDelegate) ;
    
    var content = this.get('content') ;
    if (!key || !content) return ; // do nothing if disabled
    
    // get value -- set on content if changed
    var value = this.get('value');
    if (typeof content.setIfChanged === SC.T_FUNCTION) {
      content.setIfChanged(key, value);
    } else {
      // avoid re-writing inherited props
      if (content[key] !== value) content[key] = value ;
    }
  }.observes('value'),
  
  /**
    The name of the property this control should display if it is part of an
    SC.FormView.
    
    If you add a control as part of an SC.FormView, then the form view will 
    automatically bind the value to the property key you name here on the 
    content object.
    
    @property {String}
  */
  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.  If you do not set this property, then the 
    fieldKey or the class name will be used to generate a human readable name.
    
    @property {String}
  */
  fieldLabel: null,
  
  /**
    The human readable label for this control for use in error strings.  This
    property is computed dynamically using the following rules:
    
    If the fieldLabel is defined, that property is localized and returned.
    Otherwise, if the keyField is defined, try to localize using the string 
    "ErrorLabel.{fieldKeyName}".  If a localized name cannot be found, use a
    humanized form of the fieldKey.
    
    Try to localize using the string "ErrorLabel.{ClassName}". Return a 
    humanized form of the class name.
    
    @property {String}
  */
  errorLabel: function() {
    var ret, fk, def ;
    if (ret = this.get('fieldLabel')) return ret ;
    
    // if field label is not provided, compute something...
    fk = this.get('fieldKey') || this.constructor.toString() ;
    def = (fk || '').humanize().capitalize() ;
    return "ErrorLabel.%@".fmt(fk)
      .locWithDefault("FieldKey.%@".fmt(fk).locWithDefault(def)) ;
      
  }.property('fieldLabel','fieldKey').cacheable(),

  /**
    The control size.  This will set a CSS style on the element that can be 
    used by the current theme to vary the appearance of the control.
    
    @property {String}
  */
  controlSize: SC.REGULAR_CONTROL_SIZE,
  
  /** @private */
  displayProperties: 'isEnabled isSelected isActive controlSize'.w(),
  
  /** @private */
  _CONTROL_TMP_CLASSNAMES: {},
  
  /** @private
    Invoke this method in your updateDisplay() method to update any basic 
    control CSS classes.
  */
  renderMixin: function(context, firstTime) {
    var sel = this.get('isSelected'), disabled = !this.get('isEnabled');
    
    // update the CSS classes for the control.  note we reuse the same hash
    // to avoid consuming more memory
    var names = this._CONTROL_TMP_CLASSNAMES ; // temporary object
    names.mixed = sel === SC.MIXED_STATE;
    names.sel = sel && (sel !== SC.MIXED_STATE) ;
    names.active = this.get('isActive') ;
    context.setClass(names).addClass(this.get('controlSize'));
    
    // if the control implements the $input() helper, then fixup the input
    // tags
    if (!firstTime && this.$input) {
      this.$input().attr('disabled', disabled);
    }
  },
  
  /** @private
    This should be null so that if content is also null, the
    _contentDidChange won't do anything on init.
  */
  _control_content: null,
  
  /** @private
    Observes when a content object has changed and handles notifying 
    changes to the value of the content object.
  */
  _control_contentDidChange: function() {
    var content = this.get('content') ;
    if (this._control_content === content) return; // nothing changed
    
    var f = this.contentPropertyDidChange ;
    
    // remove an observer from the old content if necessary
    var old = this._control_content ;
    if (old && old.removeObserver) old.removeObserver('*', this, f) ;
    
    // add observer to new content if necessary.
    this._control_content = content ;
    if (content && content.addObserver) content.addObserver('*', this, f) ;
    
    // notify that value did change.
    this.contentPropertyDidChange(content, '*') ;
    
  }.observes('content')
  
};

/* >>>>>>>>>> BEGIN source/mixins/editable.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @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.
    
    @property {Boolean}
  */
  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.
    
    @property {Boolean}
  */
  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 ;
  }

} ;

/* >>>>>>>>>> BEGIN source/system/browser.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** Detects the current browser type. Borrowed from jQuery + prototype */
SC.browser = (function() {
  
  var userAgent = navigator.userAgent.toLowerCase();
  var version = (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1] ;

  var browser = /** @scope SC.browser */ {
    
    /** The current browser version */
    version: version,
    
    /** non-zero if webkit-based browser */
    safari: (/webkit/).test( userAgent ) ? version : 0,
    
    /** non-zero if this is an opera-based browser */
    opera: (/opera/).test( userAgent ) ? version : 0,
    
    /** non-zero if this is IE */
    msie: (/msie/).test( userAgent ) && !(/opera/).test( userAgent ) ? version : 0,
    
    /** non-zero if this is a miozilla based browser */
    mozilla: (/mozilla/).test( userAgent ) && !(/(compatible|webkit)/).test( userAgent ) ? version : 0,
    
    /** non-zero if this is mobile safari */
    mobileSafari: (/apple.*mobile.*safari/).test(userAgent) ? version : 0,
    
    /** non-zero if we are on windows */
    windows: !!(/(windows)/).test(userAgent),
    
    /** non-zero if we are on a mac */
    mac: !!((/(macintosh)/).test(userAgent) || (/(mac os x)/).test(userAgent)),
    
    language: (navigator.language || navigator.browserLanguage).split('-', 1)[0]
  };
  
  // Add more SC-like descriptions...
  SC.extend(browser, /** @scope SC.browser */ {
    
    isOpera: !!browser.opera,
    isIe: !!browser.msie,
    isIE: !!browser.msie,
    isSafari: !!browser.safari,
    isMobileSafari: !!browser.mobileSafari,
    isMozilla: !!browser.mozilla,
    isWindows: !!browser.windows,
    isMac: !!browser.mac,

    /**
      The current browser name.  This is useful for switch statements. */
    current: browser.msie ? 'msie' : browser.mozilla ? 'mozilla' : browser.safari ? 'safari' : browser.opera ? 'opera' : 'unknown'
    
  }) ;
  
  return browser ;

})();


/* >>>>>>>>>> BEGIN source/system/builder.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @class

  The Builder class makes it easy to create new chained-builder API's such as
  those provided by CoreQuery or jQuery.  Usually you will not create a new
  builder yourself, but you will often use instances of the Builder object to
  configure parts of the UI such as menus and views.
  
  h1. Anatomy of a Builder
  
  You can create a new Builder much like you would any other class in 
  SproutCore.  For example, you could create a new CoreQuery-type object with
  the following:
  
  {{{
    SC.$ = SC.Builder.create({
      // methods you can call go here.
    });
  }}}
  
  Unlike most classes in SproutCore, Builder objects are actually functions 
  that you can call to create new instances.  In the example above, to use 
  the builder, you must call it like a function:
  
  {{{
    buildit = SC.$();
  }}}
  
  If you define an init() method on a builder, it will be invoked wheneve the
  builder is called as a function, including any passed params.  Your init()
  method MUST return this, unlike regular SC objects.  i.e.
  
  {{{
    SC.$ = SC.Builder.create({
      init: function(args) { 
        this.args = SC.A(args);
        return this;
      }
    });
    
    buildit = SC.$('a', 'b');
    buildit.args => ['a','b']
  }}}
  
  In addition to defining a function like this, all builder objects also have
  an 'fn' property that contains a hash of all of the helper methods defined
  on the builder function.  Once a builder has been created, you can add 
  addition "plugins" for the builder by simply adding new methods to the
  fn property.
  
  h1. Writing Builder Functions
  
  All builders share a few things in comming:
  
  - when a new builder is created, it's init() method will be called.  The default version of this method simply copies the passed parameters into the builder as content, but you can override this with anything you want.
  
  - the content the builder works on is stored as indexed properties (i.e. 0,1,2,3, like an array).  The builder should also have a length property if you want it treated like an array.
    
  - Builders also maintain a stack of previous builder instances which you can pop off at any time.
    
  To get content back out of a builder once you are ready with it, you can
  call the method done().  This will return an array or a single object, if 
  the builder only works on a single item.
  
  You should write your methods using the getEach() iterator to work on your
  member objects.  All builders implement SC.Enumerable in the fn() method.

  CoreQuery = SC.Builder.create({
    ...
  }) ;
  
  CoreQuery = new SC.Builder(properties) {
    
  } ;

  CoreQuery2 = CoreQuery.extend() {
  }
  
  @constructor
*/
SC.Builder = function (props) { return SC.Builder.create(props); };

/** 
  Create a new builder object, applying the passed properties to the 
  builder's fn property hash.
  
  @param {Hash} properties
  @returns {SC.Builder}
*/
SC.Builder.create = function create(props) { 
  
  // generate new fn with built-in properties and copy props
  var fn = SC.mixin(SC.beget(this.fn), props||{}) ;
  if (props.hasOwnProperty('toString')) fn.toString = props.toString;
  
  // generate new constructor and hook in the fn
  var construct = function() {
    var ret = SC.beget(fn); // NOTE: using closure here...
    
    // the defaultClass is usually this for this constructor. 
    // e.g. SC.View.build() -> this = SC.View
    ret.defaultClass = this ;
    ret.constructor = construct ;

    // now init the builder object.
    return ret.init.apply(ret, arguments) ;
  } ;
  construct.fn = construct.prototype = fn ;

  // the create() method can be used to extend a new builder.
  // eg. SC.View.buildCustom = SC.View.build.extend({ ...props... })
  construct.extend = SC.Builder.create ;
  construct.mixin = SC.Builder.mixin ;
  
  return construct; // return new constructor
} ;

SC.Builder.mixin = function() {
  var len = arguments.length, idx;
  for(idx=0;idx<len;idx++) SC.mixin(this, arguments[idx]);
  return this ;
};

/** This is the default set of helper methods defined for new builders. */
SC.Builder.fn = {

  /** 
    Default init method for builders.  This method accepts either a single
    content object or an array of content objects and copies them onto the 
    receiver.  You can override this to provide any kind of init behavior 
    that you want.  Any parameters passed to the builder method will be 
    forwarded to your init method.
    
    @returns {SC.Builder} receiver
  */
  init: function(content) {
    if (content !== undefined) {
      if (SC.typeOf(content) === SC.T_ARRAY) {
        var loc=content.length;
        while(--loc >= 0) {
          this[loc] = content.objectAt ? content.objectAt(loc) : content[loc];
        }
        this.length = content.length ;
      } else {
        this[0] = content; this.length=1;
      }
    }
    return this ;
  },
  
  /** Return the number of elements in the matched set. */
  size: function() { return this.length; },
  
  /** 
    Take an array of elements and push it onto the stack (making it the
    new matched set.)  The receiver will be saved so it can be popped later.
    
    @param {Object|Array} content
    @returns {SC.Builder} new isntance
  */
  pushStack: function() {
    // Build a new CoreQuery matched element set
    var ret = this.constructor.apply(this,arguments);

    // Add the old object onto the stack (as a reference)
    ret.prevObject = this;

    // Return the newly-formed element set
    return ret;
  },

  /**
    Returns the previous object on the stack so you can continue with that
    transform.  If there is no previous item on the stack, an empty set will
    be returned.
  */
  end: function() { 
    return this.prevObject || this.constructor(); 
  },
  
  // toString describes the builder
  toString: function() { 
    return "%@$(%@)".fmt(this.defaultClass.toString(), 
      SC.A(this).invoke('toString').join(',')); 
  },
  
  /** You can enhance the fn using this mixin method. */
  mixin: SC.Builder.mixin
  
};

// Apply SC.Enumerable.  Whenever possible we want to use the Array version
// because it might be native code.
(function() {
  var enumerable = SC.Enumerable, fn = SC.Builder.fn, key, value ;
  for(key in enumerable) {
    if (!enumerable.hasOwnProperty(key)) continue ;
    value = Array.prototype[key] || enumerable[key];
    fn[key] = value ;
  }
})();




/* >>>>>>>>>> BEGIN source/system/core_query.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================
/*globals CQ*/

sc_require('system/builder') ;

/**
  CoreQuery is a simplified DOM manipulation library used internally by 
  SproutCore to find and edit DOM elements.  Outside of SproutCore, you 
  should generally use a more full-featured DOM library such as Prototype
  or jQuery.
  
  CoreQuery itself is a subset of jQuery with some additional plugins.  If
  you have jQuery already loaded when SproutCore loads, in fact, it will 
  replace CoreQuery with the full jQuery library and install any CoreQuery
  plugins, including support for the SC.Enumerable mixin.
  
  Much of this code is adapted from jQuery 1.2.6, which is available under an
  MIT license just like SproutCore.
  
  h1. Using CoreQuery
  
  You can work with CoreQuery much like you would work with jQuery.  The core
  manipulation object is exposed as SC.$.  To find some elements on the page
  you just pass in a selector like this:
  
  {{{
    var cq = SC.$('p');
  }}}
  
  The object returned from this call is a CoreQuery object that implements 
  SC.Enumerable as well as a number of other useful manipulation methods.  
  Often times we call this object the "matched set", because it usually an
  array of elements matching the selector key you passed.
  
  To work with the matched set, just call the various helper methods on it.
  Here are some of the more useful ones:
  
  {{{
    // change all of the text red
    cq.css('color','red');
    
    // hide/show the set
    cq.hide();  cq.show();
    
    // set the text content of the set
    cq.text("Hello World!");
    
  }}}
  
  Of course, you can also chain these methods, just like jQuery.  Here is 
  how you might find all of the headings in your page, change their text and
  color:
  
  {{{
    SC.$('h1').text('Hello World!').css('color','red');
  }}}
  
  h1. Using CoreQuery with Views
  
  Usually you will not want to just blindly edit the HTML content in your
  application.  Instead, you will use CoreQuery to update the portion of the
  page managed by your SC.View instances.  Every SC.View instance has a $()
  property just like SC.$().  The difference is that this function will start
  searching from the root of the view.  For example, you could use the 
  following code in your updateDisplay method to set your content and color:
  
  {{{
    updateDisplay: function() {
      this.$().text(this.get('value')).css('color','red');
    }
  }}}
  
  You could also work on content within your view, for example this will 
  change the title on your view held in the span.title element:
  
  {{{
    updateDisplay: function() {
      this.$('span.title').text('Hello World');
      this.$().setClassName('sc-enabled', YES) ;
    }
  }}}

  @class
  @extends SC.Builder.fn
*/
SC.CoreQuery = (function() {
  // Define CoreQuery inside of its own scope to support some jQuery idioms.
  
  // A simple way to check for HTML strings or ID strings
  // (both of which we optimize for)
  var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
  // Is it a simple selector
  isSimple = /^.[^:#\[\.]*$/,
  undefined ;

  var styleFloat = SC.browser.msie ? "styleFloat" : "cssFloat";

  // used for the find() method.
  var chars = (SC.browser.safari && parseInt(SC.browser.version,0) < 417) ?
      "(?:[\\w*_-]|\\\\.)" :
      "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)" ;
  var quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)") ;
  var singleClass = new RegExp("^([#.]?)(" + chars + "*)");
  var quickSplit = new RegExp("([#.]?)(" + chars + "*)",'g');

  // Constants used in CQ.css()
  var LEFT_RIGHT = ["Left", "Right"];
  var TOP_BOTTOM = ["Top", "Bottom"];
  var CSS_DISPLAY_PROPS = {  
    position: "absolute", visibility: "hidden", display:"block" 
  } ;

  var getWH = function getWH(elem, name, which) {
    var val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
    var padding = 0, border = 0, loc=which.length, dim;
    while(--loc>=0) {
      dim = which[loc];
      padding += parseFloat(CQ.curCSS( elem, "padding" + dim, true)) || 0;
      border += parseFloat(CQ.curCSS( elem, "border" + dim + "Width", true)) ||0;   
    }
    val -= Math.round(padding + border);
    return val;
  } ;

  var expando = SC.guidKey, uuid = 0, windowData = {},
    // exclude the following css properties to add px
    exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
    // cache defaultView
    defaultView = document.defaultView || {};

  // A helper method for determining if an element's values are broken
  var styleIsBorked = function styleIsBorked( elem ) {
    if ( !SC.browser.safari ) return false;

    // defaultView is cached
    var ret = defaultView.getComputedStyle( elem, null );
    return !ret || ret.getPropertyValue("color") == "";
  } ;

  

  // Helper function used by the dimensions and offset modules
  function num(elem, prop) {
    return elem[0] && parseInt( CQ.curCSS(elem[0], prop, true), 10 ) || 0;
  } ;

  var CoreQuery, CQ ;
  
  // implement core methods here from jQuery that we want available all the
  // time.  Use this area to implement jQuery-compatible methods ONLY.
  // New methods should be added at the bottom of the file, where they will
  // be installed as plugins on CoreQuery or jQuery. 
  CQ = CoreQuery = SC.Builder.create( /** @scope SC.CoreQuery.fn */ {
    
    /** Indicates that this is a jQuery-like object. */
    jquery: 'SC.CoreQuery',
    
    /** 
      Called on a new CoreQuery instance when it is first created.  You
      can pass a variety of options to the CoreQuery constructor function 
      including:
      
      - a simple selector: this will find the element and return it
      - element or array of elements - this will return a query with them
      - html-string: this will convert to DOM.
      
      @returns {CoreQuery} CoreQuery instance
    */
    init: function( selector, context ) {
      
      // Make sure that a selection was provided
      selector = selector || document;

      // Handle $(DOMElement)
      if ( selector.nodeType ) {
        this[0] = selector;
        this.length = 1;
        return this ;

      // Handle HTML strings
      } else if ( typeof selector === "string" ) {
        // Are we dealing with HTML string or an ID?
        var match = quickExpr.exec( selector );

        // Verify a match, and that no context was specified for #id
        if ( match && (match[1] || !context) ) {

          // HANDLE: $(html) -> $(array)
          if ( match[1] )
            selector = CQ.clean( [ match[1] ], context );

          // HANDLE: $("#id")
          else {
            var elem = document.getElementById( match[3] );

            // Make sure an element was located
            if ( elem ){
              // Handle the case where IE and Opera return items
              // by name instead of ID
              if ( elem.id != match[3] ) return CQ().find( selector );

              // Otherwise, we inject the element directly into the jQuery object
              return CQ( elem );
            }
            selector = [];
          }

        // HANDLE: $(expr, [context])
        // (which is just equivalent to: $(content).find(expr)
        } else return CQ( context ).find( selector );

      // HANDLE: $(function)
      // Shortcut for document ready
      } else if (SC.typeOf(selector) === SC.T_FUNCTION) {
        return SC.ready(selector);
      }

      return this.setArray(CQ.makeArray(selector));
    },

    /** Return the number of elements in the matched set. */
    size: function() { return this.length; },

    /** Return the nth element of the working array OR return a clean array
      with the result set, if no number is passed.
      
      @param {Number} num (Optional)
      @returns {Object|Array}
    */
    get: function( num ) {
      return num === undefined ? CQ.makeArray(this) : this[num];
    },

    /** 
      Find subelements matching the passed selector.  Note that CoreQuery
      supports only a very simplified selector search method.  See 
      CoreQuery.find() for more information.
      
      @param {String} selector
      @returns {CoreQuery} new instance with match
    */
    find: function( selector ) {
      var elems = CQ.map(this, function(elem){
        return CQ.find( selector, elem );
      });

      return this.pushStack(elems);
    },

    /**
      Filters the matching set to include only those matching the passed 
      selector.  Note that CoreQuery supports only a simplified selector 
      search method.  See CoreQuery.find() for more information.
      
      Also note that CoreQuery implements SC.Enumerable, which means you can
      also call this method with a callback and target and the callback will
      be executed on every element in the matching set to return a result.
    
      @param {String} selector
      @returns {CoreQuery}
    */
    filter: function( selector ) {
      return this.pushStack(
        (SC.typeOf(selector) === SC.T_FUNCTION) &&
        CQ.grep(this, function(elem, i){
          return selector.call( elem, i );
        }) || CQ.multiFilter( selector, this ) );
    },

    /**
      Returns the results not matching the passed selector.  This is the 
      opposite of filter.
      
      
      @param {String} selector
      @returns {CoreQuery}
    */
    not: function( selector ) {
      if ( typeof selector === "string" ) {
        // test special case where just one selector is passed in
        if ( isSimple.test( selector ) )
          return this.pushStack( CQ.multiFilter( selector, this, true ) );
        else
          selector = CQ.multiFilter( selector, this );
      }

      var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
      return this.filter(function() {
        return isArrayLike ? CQ.inArray( this, selector ) < 0 : this != selector;
      });
    },
    
    /**    
      Force the current matched set of elements to become the specified array 
      of elements (destroying the stack in the process) You should use 
      pushStack() in order to do this, but maintain the stack.
      
      This method is mostly used internally.  You will not need to use it 
      yourself very often.
      
      @param {Array} elems
      @returns {CoreQuery} receiver
    */
    setArray: function( elems ) {
      // Resetting the length to 0, then using the native Array push
      // is a super-fast way to populate an object with array-like properties
      this.length = 0;
      Array.prototype.push.apply( this, elems );
      return this;
    },
    
    /** 
      Executes the passed function on every element in the CoreQuery object.
      Returns an array with the return values.  Note that null values will
      be omitted from the resulting set.  This differs from SC.Enumerable and
      the JavaScript standard. 
      
      The callback must have the signature:
      
      {{{
        function(currentElement, currentIndex) { return mappedValue; }
      }}}
      
      Note that "this" on the function will also be the currentElement.
      
      @param {Function} callback
      @returns {CoreQuery} results
    */
    map: function( callback ) {
      return this.pushStack( CQ.map(this, function(elem, i){
        return callback.call( elem, i, elem );
      }));
    },
    
    /**    
      Execute a callback for every element in the matched set. (You can seed 
      the arguments with an array of args, but this is only used internally.)
      
      @param {Function} callback
      @param {Object} args
      @returns {CoreQuery} receiver
    */
    each: function( callback, args ) {
      return CQ.each( this, callback, args );
    },

    /** 
      Determine the position of an element within a matched set of elements.
      jQuery-compatible name for indexOf().
      
      @param {Element} elem
      @returns {Number} location
    */
    index: function( elem ) {
      if (elem && elem.jquery) elem = elem[0];
      return Array.prototype.indexOf.call(this, elem);
    },

    /**
      Returns a new CoreQuery object that contains just the matching item.
      
      @param {Number} i
      @returns {CoreQuery}
    */
    eq: function( i ) {
      return this.slice( i, +i + 1 );
    },

    /** 
      Slice the CoreQuery result set just like you might slice and array.
      Returns a new CoreQuery object with the result set.

      @returns {CoreQuery}
    */
    slice: function() {
      return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
    },

    /** Adds the relevant elements to the existing matching set. */
    add: function( selector ) {
      return this.pushStack( CQ.merge(
        this.get(),
        typeof selector === 'string' ?
          CQ( selector ) :
          CQ.makeArray( selector )
      ).uniq()) ;
    },
    
    /** 
      Get to set the named attribute value on the element.  You can either
      pass in the name of an attribute you would like to read from the first
      matched element, a single attribute/value pair to set on all elements
      or a hash of attribute/value pairs to set on all elements.
      
      @param {String} name attribute name
      @param {Object} value attribute value
      @param {String} type ?
      @returns {CoreQuery} receiver
    */
    attr: function( name, value, type ) {
      var options = name;

      // Look for the case where we're accessing a style value
      if ( typeof name === "string" )
        if ( value === undefined )
          return this[0] && CQ[ type || "attr" ]( this[0], name );

        else {
          options = {};
          options[ name ] = value;
        }

      // Check to see if we're setting style values
      return this.each(function(i){
        // Set all the styles
        for ( name in options )
          CQ.attr(
            (type)?this.style:this,
            name, CQ.prop( this, options[ name ], type, i, name ));
      });
    },
    
    html: function( value ) {
      return value === undefined ?
      			(this[0] ?
      				this[0].innerHTML.replace(/ CQ\d+="(?:\d+|null)"/g, "") :
      				null) :
      			this.empty().append( value );
      // return value == undefined ?
      //  (this[0] ? this[0].innerHTML : null) :
      //  this.empty().append( value );
    },

    andSelf: function() { return this.add( this.prevObject ); },

    /** 
      Returns YES if every element in the matching set matches the passed
      selector.  Remember that only simple selectors are supported in 
      CoreQuery.
      
      @param {String} selector
      @return {Boolean} 
    */
    is: function( selector ) {
      return !!selector && CQ.multiFilter( selector, this ).length > 0;
    },

    /**
      Returns YES if every element in the matching set has the named CSS
      class.
      
      @param {String} className
      @returns {Boolean}
    */
    hasClass: function( className ) {
      return Array.prototype.every.call(this, function(elem) {
        return (elem.nodeType!=1) || CQ.className.has(elem, className) ;
      });
    },

    /** 
      Provides a standardized, cross-browser method to get and set the 
      value attribute of a form element.  Optionally pass a value to set or
      no value to get.
      
      @param {Object} value
      @return {Object|CoreQuery}
    */
    val: function( value ) {
      
      // get the value
      if ( value === undefined ) {     
        var elem = this[0];
        if (elem) {
          if(CQ.nodeName(elem, 'option'))
            return (elem.attributes.value || {}).specified ? elem.value : elem.text;

          // We need to handle select boxes special
          if ( CQ.nodeName( elem, "select" ) ) {
            var index = elem.selectedIndex,
              values = [],
              options = elem.options,
              one = elem.type == "select-one";

            // Nothing was selected
            if ( index < 0 ) return null;

            // Loop through all the selected options
            var i, max = one ? index+1:options.length;
            for (i = one ? index : 0; i < max; i++ ) {
              var option = options[ i ];
              if ( option.selected ) {
                value = CQ(option).val(); // get value
                if (one) return value; // We don't need an array for one
                values.push( value ); // Multi-Selects return an array
              }
            }

            return values;        
          }

          // Everything else, we just grab the value
          return (elem.value || "").replace(/\r/g, "");
        }
        return undefined;
        
      // otherwise set the value
      } else {
        if( typeof value === "number" ) value += ''; // force to string
        this.each(function(){
          if ( this.nodeType != 1 ) return;
          
          // handle radio/checkbox.  set the checked value
          if (SC.typeOf(value) === SC.T_ARRAY && (/radio|checkbox/).test(this.type)) {
            this.checked = (CQ.inArray(this.value, value) >= 0 ||
              CQ.inArray(this.name, value) >= 0);
              
          // handle selects
          } else if ( CQ.nodeName( this, "select" ) ) {
            var values = CQ.makeArray(value);
            CQ( "option", this ).each(function(){
              this.selected = (CQ.inArray( this.value, values ) >= 0 ||
                CQ.inArray( this.text, values ) >= 0);
            });

            if (!values.length) this.selectedIndex = -1;

          // otherwise, just set the value property
          } else this.value = value;
        });       
      }
      return this ;
    },

    /** 
      Returns a clone of the matching set of elements.  Note that this will
      NOT clone event handlers like the jQuery version does becaue CoreQuery
      does not deal with events.
    */
    clone: function() {
      // Do the clone
      var ret = this.map(function(){
        if ( SC.browser.msie && !CQ.isXMLDoc(this) ) {
          // IE copies events bound via attachEvent when
          // using cloneNode. Calling detachEvent on the
          // clone will also remove the events from the orignal
          // In order to get around this, we use innerHTML.
          // Unfortunately, this means some modifications to
          // attributes in IE that are actually only stored
          // as properties will not be copied (such as the
          // the name attribute on an input).
          var clone = this.cloneNode(true),
            container = document.createElement("div");
          container.appendChild(clone);
          return CQ.clean([container.innerHTML])[0];
        } else return this.cloneNode(true);
      });

      // Need to set the expando to null on the cloned set if it exists
      // removeData doesn't work here, IE removes it from the original as well
      // this is primarily for IE but the data expando shouldn't be copied 
      // over in any browser
      var clone = ret.find("*").andSelf().each(function(){
        if ( this[ SC.guidKey ] !== undefined )
          this[ SC.guidKey ] = null;
      });

      // Return the cloned set
      return ret;
    },

    /** 
      Set or retrieve the specified CSS value.  Pass only a key to get the
      current value, pass a key and value to change it.
      
      @param {String} key
      @param {Object} value
      @returns {Object|CoreQuery}
    */
    css: function( key, value ) {
      // ignore negative width and height values
      if ((key == 'width' || key == 'height') && parseFloat(value,0) < 0 ) {
        value = undefined;
      }
      return this.attr( key, value, "curCSS" );
    },

    /**
      Set or retrieve the text content of an element.  Pass a text element to
      update or set to end it.
      
      @param {String} text
      @returns {String|CoreQuery}
    */
    text: function( text ) {
      if ( typeof text !== "object" && text != null )
        return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );

      var ret = "";

      CQ.each( text || this, function(){
        CQ.each( this.childNodes, function(){
          if ( this.nodeType != 8 )
            ret += this.nodeType != 1 ?
              this.nodeValue : CQ.fn.text( [ this ] );
        });
      });

      return ret;
    },

    /** Simple method to show elements without animation. */
    show: function() {
      var isVisible = SC.$.isVisible;
      this.each(function() {
        if (!isVisible(this)) {
          
          // try to restore to natural layout as defined by CSS
          this.style.display = this.oldblock || '';
          
          // handle edge case where the CSS style is none so we can't detect
          // the natural display state.
          if (CQ.css(this,'display') == 'none') {
            var elem = CQ('<' + this.tagName + '/>');
            CQ('body').append(elem);
            this.style.display = elem.css('display');
            // edge case where we still can't get the display
            if (this.style.display === 'none') this.style.display = 'block';
            elem.remove(); elem = null;
          }
        }
      }) ;
      return this ;
    },

    /** Simple method to hide elements without animation. */
    hide: function() {
      var isVisible = SC.$.isVisible;
      this.each(function() {
        if (isVisible(this)) {
          this.oldblock = this.oldblock || CQ.css(this,'display');
          this.style.display = 'none';
        }
      }) ;
      return this ;
    },
    
    /** 
      Low-level dom manipulation method used by append(), before(), after()
      among others.  Unlike the jQuery version, this version does not execute
      <script> tags.  That is generally not a good way to input scripts.
    */
    domManip: function( args, table, reverse, callback ) {
      var clone = this.length > 1, elems;

      return this.each(function(){
        if ( !elems ) {
          elems = CQ.clean( args, this.ownerDocument );
          if (reverse) elems.reverse();
        }

        var obj = this;
        if ( table && CQ.nodeName( this, "table" ) && CQ.nodeName( elems[0], "tr" ) ) {
          obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
        }

        CQ.each(elems, function(){
          var elem = clone ? CQ( this ).clone( true )[0] : this;
          // Inject the elements into the document
          callback.call( obj, elem );
        });
      });
    },
    
    append: function() {
      return this.domManip(arguments, true, false, function(elem){
        if (this.nodeType == 1)
          this.appendChild( elem );
      });
    },

    prepend: function() {
      return this.domManip(arguments, true, true, function(elem){
        if (this.nodeType == 1)
          this.insertBefore( elem, this.firstChild );
      });
    },

    before: function() {
      return this.domManip(arguments, false, false, function(elem){
        this.parentNode.insertBefore( elem, this );
      });
    },

    after: function() {
      return this.domManip(arguments, false, true, function(elem){
        this.parentNode.insertBefore( elem, this.nextSibling );
      });
    },

    replaceWith: function( value ) {
      return this.after( value ).remove();
    },

    removeData: function( key ){
      return this.each(function(){ SC.removeData( this, key ); });
    }

  }) ;
  
  // add useful helper methods to CoreQuery
  CoreQuery.mixin(/** @scope SC.CoreQuery */ {
    
    nodeName: function( elem, name ) {
      return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
    },
    
    /**
      Execute the passed callback on the elems array, returning an array with
      the mapped values.  Note that null return values are left out of the
      resulting mapping array.  This differs from the standard map() function
      defined by SC.Enumerable and the JavaScript standard.
      
      The callback must have the signature:
      
      {{{
        function(currentElement, currentIndex) { return mappedValue; }
      }}}
      
      Note that "this" on the function will also be the currentElement.
      
      @param {Array} elems
      @param {Function} callback
      @returns {Array} mapped elements
    */
    map: function( elems, callback ) {
      var ret = [];

      // Go through the array, translating each of the items to their
      // new value (or values).
      for ( var i = 0, length = elems.length; i < length; i++ ) {
        var value = callback( elems[ i ], i );

        if ( value != null )
          ret[ ret.length ] = value;
      }
      
      return ret.concat.apply([],ret) ;
    },

    /** 
      Executes the passed callback on each item in the iterable object
      passed.  This deviates from the standard getEach() method defined in
      SC.Enumerable and in the JavaScript standards.
      
      @param {Array} object
      @param {Function} callback
      @param {Object} args internal use only
      @returns {Object} object
    */
    each: function( object, callback, args ) {
      var name, i = 0, length = object.length;

      if ( args ) {
        if ( length === undefined ) {
          for ( name in object )
            if ( callback.apply( object[ name ], args ) === false )
              break;
        } else
          for ( ; i < length; )
            if ( callback.apply( object[ i++ ], args ) === false )
              break;

      // A special, fast, case for the most common use of each
      } else {
        if ( length === undefined ) {
          for ( name in object )
            if ( callback.call( object[ name ], name, object[ name ] ) === false )
              break;
        } else
          for ( var value = object[0];
            i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
      }

      return object;
    },
    
    isXMLDoc: function( elem ) {
      return elem.documentElement && !elem.body ||
        elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
    },
    
    clean: function( elems, context ) {
      var ret = [];
      context = context || document;
      // !context.createElement fails in IE with an error but returns typeof 'object'
      if (typeof context.createElement == 'undefined') {
        context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
      }

      CQ.each(elems, function(i, elem){
        if ( typeof elem === 'number' ) elem += '';
        if ( !elem ) return;

        // Convert html string into DOM nodes
        if ( typeof elem === "string" ) {
          // Fix "XHTML"-style tags in all browsers
          elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
            return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
              all :
              front + "></" + tag + ">";
          });

          // Trim whitespace, otherwise indexOf won't work as expected
          var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(), 
              div = context.createElement("div");

          var wrap =
            // option or optgroup
            !tags.indexOf("<opt") &&
            [ 1, "<select multiple='multiple'>", "</select>" ] ||

            !tags.indexOf("<leg") &&
            [ 1, "<fieldset>", "</fieldset>" ] ||

            tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
            [ 1, "<table>", "</table>" ] ||

            !tags.indexOf("<tr") &&
            [ 2, "<table><tbody>", "</tbody></table>" ] ||

            // <thead> matched above
            (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
            [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||

            !tags.indexOf("<col") &&
            [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||

            // IE can't serialize <link> and <script> tags normally
            SC.browser.msie &&
            [ 1, "div<div>", "</div>" ] ||

            [ 0, "", "" ];

          // Go to html and back, then peel off extra wrappers
          div.innerHTML = wrap[1] + elem + wrap[2];

          // Move to the right depth
          while ( wrap[0]-- ) div = div.lastChild;

          // Remove IE's autoinserted <tbody> from table fragments
          if ( SC.browser.msie ) {

            // String was a <table>, *may* have spurious <tbody>
            var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
              div.firstChild && div.firstChild.childNodes :

              // String was a bare <thead> or <tfoot>
              wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
                div.childNodes :
                [];

            for ( var j = tbody.length - 1; j >= 0 ; --j )
              if ( CQ.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
                tbody[ j ].parentNode.removeChild( tbody[ j ] );

            // IE completely kills leading whitespace when innerHTML is used
            if ( /^\s/.test( elem ) )
              div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );

          }

          elem = CQ.makeArray( div.childNodes );
        }

        if (elem.length === 0 && (!CQ.nodeName( elem, "form" ) && !CQ.nodeName( elem, "select" ))) return;

        if (elem[0] === undefined || CQ.nodeName( elem, "form" ) || elem.options) ret.push( elem );

        else ret = CQ.merge( ret, elem );

      });

      return ret;
    },
    
    /** 
      Core element finder function in SC.CoreQuery.  CoreQuery supports only
      a very simple set of finders.  Namely, you can specify the following
      simple types of selectors:
      
      - .class-name: this will find all elements with the matching class name
      - #id: this will find all elements matching the ID
      - tagname: this will find all elements with the matching tags.
      
      You can also do some basic joined expressions like:
      
      {{{
        tagname.class-name and tagname#id
      }}}
      
      Finally, you can do simple compound searches like
      
      {{{
        tagname .class-name tagname#id
      }}}
      
      You can also pass multiple selectors separated by commas.  The return
      set will be the OR of all the result set.
      
      {{{
        #item1,#item2,#item3
      }}}
      
      You cannot do any child searches, psuedo-selectors or other complex 
      searches.  These are only the kinds of selectors that can be parsed
      quickly and use built-in methods on the browser.
      
      @param {String} t selector
      @param {Element} context
      @returns {Array} matched elements
    */
    find: function( t, context ) {
      
      // Quickly handle non-string expressions
      if ( typeof t != "string" ) return [ t ];

      // if the selector contains commas, then we actually want to search
      // multiple selectors.
      if (t.indexOf(',')>=0) {
        var ret = t.split(',').map(function(sel) {
          return CQ.find(sel,context);
        });

        // flatten arrays
        return ret.concat.apply([],ret).uniq() ;
      }
      
      // check to make sure context is a DOM element or a document
      if ( context && context.nodeType != 1 && context.nodeType != 9) {
        return [];
      }

      // Set the correct context (if none is provided)
      context = context || document;

      // Initialize the search.  split the selector into pieces
      var ret = [context], nodeName, inFindMode = YES;
      var parts = t.match(quickSplit), len = parts.length, m ;
      
      // loop through each part and either find or filter as needed
      for(var idx=0;idx<len;idx++) {
        t = parts[idx]; // the current selector to parse
        
        // handle space separators.  this just resets to find mode
        if (t === ' ' || t === '') {
          inFindMode = YES ;
          
        // if we are in find mode, then use the current selector to
        // find new elements that are children. at the end, leave findMode.
        } else if (inFindMode) {

          // split into parts to test result
          m = singleClass.exec(t);
          
          // handle special case where we get a tag name followed by an ID.
          // in this case, just swap the two and proceed.
          if ((m[1] === '') && (idx<(len-1)) && (parts[idx+1].charAt(0)==='#')) {
            t = parts[idx+1]; parts[idx+1] = parts[idx]; // swap
            m = singleClass.exec(t); // reparse
          }

          // now loop through and find elements based on tag
          var next = [], retlen = ret.length, retidx, cur, val = m[2], found;
          for(retidx=0;retidx<retlen;retidx++) {
            cur = ret[retidx]; 
            switch(m[1]) {
            case '': // tag
              if (!val) val = '*';
              // Handle IE7 being really dumb about <object>s
              if ( val == "*" && cur.nodeName.toLowerCase() == "object" ) {
                val = "param";
              }
              
              next = CQ.merge(next, cur.getElementsByTagName(val));
              break;
              
            case '#': // id
              // handle special case where we are searching the document
              if (cur === document) {
                found = document.getElementById(val) ;
                
                // if this is IE, verify that it didn't search by name
                if (SC.browser.msie && found && found.getAttribute('id')!==val){
                  found = NO; // clear
                } else {
                  if (found) next.push(found) ;
                  found = YES ; // do not do slow search
                }
              } else found = NO;
              
              // Otherwise, we have to do a slow search
              if (!found) {
                // the return value of getElementsByTagName is not an Array
                // so we need to fake it.
                found = cur.getElementsByTagName('*') ;
                found = Array.prototype.find.call(found, function(el){
                  return el.getAttribute && (el.getAttribute('id')===val);
                }) ;
                if (found) next.push(found) ;
              }
              break ;
              
            case '.': // class
              if (cur.getElementsByClassName) {
                next = CQ.merge(next, cur.getElementsByClassName(val));
              } else {
                next = CQ.merge(next, 
                  CQ.classFilter(cur.getElementsByTagName('*'),val));
              }
              break;
            default:
              // do nothing
            }
          }
          delete ret; ret = next ; // swap array
          inFindMode = NO;
          
        // if we are not in findMode then simply filter the results.
        } else ret = CQ.filter(t, ret) ;
      }
      
      // remove original context if still there
      if (ret && ret[0] == context) ret.shift();
      return ret.uniq() ; // make sure no duplicated are returned
    },

    classFilter: function(r,m,not){
      m = " " + m + " ";
      var tmp = [];
      for ( var i = 0; r[i]; i++ ) {
        var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
        if ( !not && pass || not && !pass )
          tmp.push( r[i] );
      }
      return tmp;
    },
    
    /** 
      Filters a set of elements according to those matching the passed
      selector.  The selector can contain only tag, class, and id options.
      
      The CoreQuery filter function is only capable of handling simple querys
      such as a tag, class or ID.  You cannot combine them.  Instead call
      filter several times.
      
      @param {String} t the selector to filter by
      @param {Array} r the element to filter
      @param {Boolean} not invert filter
      @returns {Array} filtered array
    */
    filter: function(t,r,not) {
      // split into parts to test result
      var m = singleClass.exec(t), val = m[2], kind = m[1], filter ;
      if (kind === '.') { // special case class
        return CQ.classFilter(CQ.makeArray(r), val, not) ;
      } else {
        if (kind === '#') { // id
          filter = function(el) {
            var ret=el && el.getAttribute && (el.getAttribute('id') === val);
            return (not) ? !ret : ret ;
          } ;
          
        } else { // tag
          filter = function(el) {
            var ret= CQ.nodeName(el, val);
            return (not) ? !ret : ret ;
          } ;
        }
        
        // the return value may not be a real instance of Array, so fake it.
        return Array.prototype.filter.call(CQ.makeArray(r), filter) ;
      }
    },

    /** @private Accepts filters separated by commas. */
    multiFilter: function( expr, elems, not ) {
      expr = (expr.indexOf(',')) ? expr.split(',') : [expr];
      var loc=expr.length,cur,ret=[];
      while(--loc >= 0) { // unit tests expect reverse iteration
        cur = CQ.filter(expr[loc].trim(), elems, not) ;
        ret = not ? elems = cur : CQ.merge(cur,ret);
      }
      return ret ;
    },

    /** 
      Merge two result sets together.  This method knows how to handle 
      the special iterables returned by IE as well.  Used internally.
    */
    merge: function(first, second) {
      // We have to loop this way because IE & Opera overwrite the length
      // expando of getElementsByTagName
      var i = 0, elem, pos = first.length;
      // Also, we need to make sure that the correct elements are being 
      // returned (IE returns comment nodes in a '*' query)
      if ( SC.browser.msie ) {
        while ( elem = second[ i++ ] ) {
          if ( elem.nodeType != 8 ) first[ pos++ ] = elem;
        }

      } else {
        while ( elem = second[ i++ ] ) first[ pos++ ] = elem;
      }

      return first;
    },
    
    // makeArray is the CoreQuery version of $A().
    makeArray: function(array) {
      var ret = [];

      if( array != null ){
        var i = array.length;
        // The window, strings (and functions) also have 'length'
        if( i == null || typeof array === 'string' || array.setInterval )
          ret[0] = array;
        else
          while( i )
            ret[--i] = array[i];
      }

      return ret;
    },

    inArray: function(elem,array) {
      return (array.indexOf) ? array.indexOf(elem) : Array.prototype.indexOf.call(array, elem);
    },
    
    // Check to see if the W3C box model is being used
    boxModel: !SC.browser.msie || document.compatMode == "CSS1Compat",

    props: {
      "for": "htmlFor",
      "class": "className",
      "float": styleFloat,
      cssFloat: styleFloat,
      styleFloat: styleFloat,
      readonly: "readOnly",
      maxlength: "maxLength",
      cellspacing: "cellSpacing",
      rowspan: "rowSpan"
    },
    
    /** @private Prepares a property string for insertion. */
    prop: function( elem, value, type, i, name ) {
      // Handle executable functions
      if (SC.typeOf(value) === SC.T_FUNCTION) value = value.call(elem, i);

      // Handle passing in a number to a CSS property
      return value && (typeof value === "number") && type == "curCSS" && !exclude.test( name ) ? value + "px" : value;
    },
    
    
    grep: function( elems, callback, inv ) {
      var ret = [];

      // Go through the array, only saving the items
      // that pass the validator function
      for ( var i = 0, length = elems.length; i < length; i++ ) {
        if ( !inv != !callback( elems[ i ], i ) ) ret.push( elems[ i ] );
      }
      return ret;
    },
    
    /** @private internal use only */
    className: {

      // internal only, use addClass("class")
      add: function( elem, classNames ) {
        var has = CQ.className.has ;
        CQ.each((classNames || "").split(/\s+/), function(i, className){
          if ( elem.nodeType == 1 && !has( elem.className, className ) )
            elem.className += (elem.className ? " " : "") + className;
        });
      },

      // internal only, use removeClass("class")
      remove: function( elem, classNames ) {
        if (elem.nodeType == 1) {
          elem.className = classNames !== undefined ?
            CQ.grep(elem.className.split(/\s+/), function(className){
              return !CQ.className.has( classNames, className );
            }).join(" ") : "";
        }
      },

      // internal only, use hasClass("class")
      has: function( elem, className ) {
        return elem && CQ.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
      }
    },
    
    /** @private A method for quickly swapping in/out CSS properties to get 
      correct calculations */
    swap: function( elem, options, callback, direction, arg ) {
      var old = {};
      // Remember the old values, and insert the new ones
      for ( var name in options ) {
        old[ name ] = elem.style[ name ];
        elem.style[ name ] = options[ name ];
      }

      var ret = callback(elem, direction, arg );

      // Revert the old values
      for ( var name in options ) elem.style[ name ] = old[ name ];
      return ret ;
    },
    
    /** returns a normalized value for the specified style name. */
    css: function( elem, name, force ) {
      // handle special case for width/height
      if ( name == "width" || name == "height" ) {
        var val, which = (name == "width") ? LEFT_RIGHT : TOP_BOTTOM,
        props = CSS_DISPLAY_PROPS;

        val = SC.$.isVisible(elem) ? getWH(elem,name,which) : CQ.swap(elem,props,getWH,name,which) ;

        return Math.max(0, val);
      }

      return CQ.curCSS( elem, name, force );
    },

    /** @private internal method to retrieve current CSS. */
    curCSS: function( elem, name, force ) {
      var ret, style = elem.style;

      // We need to handle opacity special in IE
      if ( name == "opacity" && SC.browser.msie ) {
        ret = CQ.attr( style, "opacity" );
        return ret == "" ? "1" : ret;
      }
      
      // Opera sometimes will give the wrong display answer, this fixes it, 
      // see #2037
      if ( SC.browser.opera && name === "display" ) {
        var save = style.outline;
        style.outline = "0 solid black";
        style.outline = save;
      }

      // Make sure we're using the right name for getting the float value
      var isFloat = name.match(/float/i); 
      if (isFloat) name = styleFloat;

      // simple case to collect the value
      if ( !force && style && style[ name ] ) {
        ret = style[ name ];

      // otherwise try to use cached computed value
      } else if ( defaultView.getComputedStyle ) {

        // Only "float" is needed here
        if (isFloat) name = "float";

        name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();

        // get the computed style and verify its not broken.
        var computedStyle = defaultView.getComputedStyle( elem, null );
        if ( computedStyle && !styleIsBorked(elem, defaultView) ) {
          ret = computedStyle.getPropertyValue( name );

        // If the element isn't reporting its values properly in Safari
        // then some display: none elements are involved
        } else {
          var swap = [], stack = [], a = elem, i = 0, swLen, stLen;

          // Locate all of the parent display: none elements
          for ( ; a && styleIsBorked(a); a = a.parentNode ) stack.unshift(a);

          // Go through and make them visible, but in reverse
          // (It would be better if we knew the exact display type that they 
          // had)
          for (stLen = stack.length ; i < stLen; i++ ) {
            if (styleIsBorked(stack[i])) {
              swap[i] = stack[i].style.display;
              stack[i].style.display = "block";
            }
          }

          // Since we flip the display style, we have to handle that
          // one special, otherwise get the value
          ret = (name == "display" && swap[stack.length-1]!=null) ? "none" :
            (computedStyle && computedStyle.getPropertyValue(name)) || "";

          // Finally, revert the display styles back
          for ( i = 0, swLen = swap.length; i < swLen; i++ ) {
            if (swap[i]!=null) stack[i].style.display = swap[i];
          }
        }

        // We should always get a number back from opacity
        if (name == "opacity" && ret == "") ret = "1";

      } else if (elem.currentStyle) {
        // var camelCase = name.camelize();

        ret = elem.currentStyle[ name ] || elem.currentStyle[ name.camelize() ];

        // From the awesome hack by Dean Edwards
        // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
        // If we're not dealing with a regular pixel number
        // but a number that has a weird ending, we need to convert it to 
        // pixels
        if (!(/^\d+(px)?$/i).test(ret) && (/^\d/).test(ret)) {
          // Remember the original values
          var left = style.left, rsLeft = elem.runtimeStyle.left;

          // Put in the new values to get a computed value out
          elem.runtimeStyle.left = elem.currentStyle.left;
          style.left = ret || 0;
          ret = style.pixelLeft + "px";

          // Revert the changed values
          style.left = left;
          elem.runtimeStyle.left = rsLeft;
        }
      }

      return ret;
    },

    /** @private
      returns all of the actual nodes (excluding whitespace, comments, etc) in 
      the passed element.
    */
    dir: function( elem, dir ){
      var matched = [], cur = elem[dir];
      while ( cur && cur != document ) {
        if ( cur.nodeType == 1 ) matched.push( cur );
        cur = cur[dir];
      }
      return matched;
    },

    /** @private
      Returns the nth actual node (not whitespace, comment, etc) in the passed
      element.
    */
    nth: function(cur,result,dir,elem){
      result = result || 1;
      var num = 0;
      for ( ; cur; cur = cur[dir] ) {
        if ( cur.nodeType == 1 && ++num == result ) break;
      }
      return cur;
    },

    /** @private Finds the regular element-style siblings. */
    sibling: function( n, elem ) {
      var r = [];
      for ( ; n; n = n.nextSibling ) {
        if ( n.nodeType == 1 && n != elem ) r.push( n );
      }
      return r;
    },
    
    /** Primitive helper can read or update an attribute on an element. */
    attr: function( elem, name, value ) {
      // don't set attributes on text and comment nodes
      if (!elem || elem.nodeType == 3 || elem.nodeType == 8) return undefined;

      var notxml = !CQ.isXMLDoc( elem ),
        set = value !== undefined,
        msie = SC.browser.msie;

      // Try to normalize/fix the name
      name = notxml && CQ.props[ name ] || name;

      // Only do all the following if this is a node (faster for style)
      // IE elem.getAttribute passes even for style
      if ( elem.tagName ) {

        // These attributes require special treatment
        var special = /href|src|style/.test( name );

        // Safari mis-reports the default selected property of a hidden option
        // Accessing the parent's selectedIndex property fixes it
        if ( name == "selected" && elem.parentNode ) {
          elem.parentNode.selectedIndex;
        }

        // If applicable, access the attribute via the DOM 0 way
        if ( name in elem && notxml && !special ) {
          if ( set ){
            // We can't allow the type property to be changed (since it causes 
            // problems in IE)
            if ( name == "type" && CQ.nodeName( elem, "input" ) && elem.parentNode ) {
              throw "type property can't be changed";
            }

            elem[ name ] = value;
          }

          // browsers index elements by id/name on forms, give priority to 
          // attributes.
          if( CQ.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
            return elem.getAttributeNode( name ).nodeValue;
          }
          
          // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
          // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
          if ( name == "tabIndex" ) {
          	var attributeNode = elem.getAttributeNode( "tabIndex" );
          	return attributeNode && attributeNode.specified
          				? attributeNode.value
          				: elem.nodeName.match(/(button|input|object|select|textarea)/i)
          					? 0
          					: elem.nodeName.match(/^(a|area)$/i) && elem.href
          						? 0
          						: undefined;
          }

          return elem[ name ];
        }

        if ( msie && notxml &&  name === "style" )
          return CQ.attr( elem.style, "cssText", value );

        // convert the value to a string (all browsers do this but IE) see 
        // #1070 (jQuery)
        if ( set ) elem.setAttribute( name, "" + value );

        // Some attributes require a special call on IE
        var attr = (msie && notxml && special)
            ? elem.getAttribute( name, 2 )
            : elem.getAttribute( name );

        // Non-existent attributes return null, we normalize to undefined
        return attr === null ? undefined : attr;
      }

      // elem is actually elem.style ... set the style

      // IE uses filters for opacity
      if ( msie && name == "opacity" ) {
        if ( set ) {
          // IE has trouble with opacity if it does not have layout
          // Force it by setting the zoom level
          elem.zoom = 1;

          // Set the alpha filter to set the opacity
          elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
            (parseInt(value,0) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
        }

        return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
          (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
          "";
      }

      name = name.camelize();
      if ( set ) elem[ name ] = value;

      return elem[ name ];
    }
        
  }) ;
  
  CQ.fn.init.prototype = CQ.fn;
  
  // Create a new generic handlers. 
  CQ.each({
    parent: function(elem){return elem.parentNode;},

    parents: function(elem){return CQ.dir(elem,"parentNode");},

    next: function(elem){return CQ.nth(elem,2,"nextSibling");},

    prev: function(elem){return CQ.nth(elem,2,"previousSibling");},
    
    nextAll: function(elem){
      return CQ.dir(elem,"nextSibling");
    },
    
    prevAll: function(elem){
      return CQ.dir(elem,"previousSibling");
    },
    
    siblings: function(elem){
      return CQ.sibling(elem.parentNode.firstChild,elem);
    },
    
    children: function(elem){return CQ.sibling(elem.firstChild);},
    
    contents: function(elem){
      return CQ.nodeName(elem,"iframe") ?
      elem.contentDocument||elem.contentWindow.document :
      CQ.makeArray(elem.childNodes);
    }
    
  }, function(name, fn){
    CQ.fn[ name ] = function( selector ) {
      var ret = CQ.map( this, fn );

      if ( selector && typeof selector == "string" )
        ret = CQ.multiFilter( selector, ret );

      return this.pushStack(ret.uniq());
    };
  });
  
  CQ.each({
    appendTo: "append",
    prependTo: "prepend",
    insertBefore: "before",
    insertAfter: "after",
    replaceAll: "replaceWith"
  }, function(name, original){
    CQ.fn[ name ] = function() {
      var args = arguments;

      return this.each(function(){
        for ( var i = 0, length = args.length; i < length; i++ )
          CQ( args[ i ] )[ original ]( this );
      });
    };
  });
  
  CQ.each({
    removeAttr: function( name ) {
      CQ.attr( this, name, "" );
      if (this.nodeType == 1) this.removeAttribute( name );
    },

    addClass: function( classNames ) {
      CQ.className.add( this, classNames );
    },

    removeClass: function( classNames ) {
      CQ.className.remove( this, classNames );
    },

    toggleClass: function( classNames ) {
      CQ.className[ CQ.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
    },

    /**  
      Removes either all elements or elements matching the selector.  Note
      that this does NOT account for event handling, since events are not
      managed by CoreQuery, unlike jQuery.
    */
    remove: function( selector ) {
      if ( !selector || CQ.filter( selector, [ this ] ).length ) {
        if (this.parentNode) this.parentNode.removeChild( this );
      }
    },

    /** 
      Removes the contents of the receiver, leaving it empty.  Note that this
      does NOT deal with Event handling since that is not managed by 
      CoreQuery.
    */
    empty: function() {
      while ( this.firstChild ) this.removeChild( this.firstChild );
    }
    
  }, function(name, fn){
    CQ.fn[name] = function(){ return this.each(fn, arguments); };
  });
  
  // Setup width and height functions
  CQ.each([ "Height", "Width" ], function(i, name){
    var type = name.toLowerCase();

    CQ.fn[ type ] = function( size ) {
      
      // Get window width or height
      if(this[0] === window) {
        
        // Opera reports document.body.client[Width/Height] properly in both 
        // quirks and standards
        if (SC.browser.opera) {
          ret = document.body["client" + name];

        // Safari reports inner[Width/Height] just fine (Mozilla and Opera 
        // include scroll bar widths)
        } else if (SC.browser.safari) {
          ret = window["inner" + name] ;

        // Everyone else use document.documentElement or document.body 
        // depending on Quirks vs Standards mode
        } else if (document.compatMode) {
          ret = documentElement['client' + name];
        } else ret = document.body['client' + name];
        
      // get document width or height
      } else if (this[0] === document) {
        // Either scroll[Width/Height] or offset[Width/Height], whichever is 
        // greater
        ret = Math.max(
          Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]),
          Math.max(document.body["offset" + name], document.documentElement["offset" + name])) ;        
          
      // get/set element width/or height
      } else {
        if (size == undefined) {
          return this.length ? CQ.css(this[0], type) : null ;

          // Set the width or height on the element (default to pixels if value is unitless)
        } else {
          return this.css(type, (typeof size === "string") ? size : size+"px");
        }
      }
      return ret ;
    };
    
    var tl = i ? "Left"  : "Top",  // top or left
      br = i ? "Right" : "Bottom"; // bottom or right

    // innerHeight and innerWidth
    CQ.fn["inner" + name] = function(){
      return this[ name.toLowerCase() ]() +
        num(this, "padding" + tl) +
        num(this, "padding" + br);
    };

    // outerHeight and outerWidth
    CQ.fn["outer" + name] = function(margin) {
      return this["inner" + name]() +
        num(this, "border" + tl + "Width") +
        num(this, "border" + br + "Width") +
        (margin ? num(this, "margin" + tl) + num(this, "margin" + br) : 0);
    };
    
  });
    
  // The Offset Method
  // Originally By Brandon Aaron, part of the Dimension Plugin
  // http://jquery.com/plugins/project/dimensions
  
  /** Calculates the offset for the first passed element. */
  CoreQuery.fn.offset = function() {
    var left = 0, top = 0, elem = this[0], br = SC.browser, results;
    if (!elem) return undefined; 

    function border(elem) {
      add( CQ.curCSS(elem, "borderLeftWidth", true), CQ.curCSS(elem, "borderTopWidth", true) );
    }

    function add(l, t) {
      left += parseInt(l, 10) || 0;
      top += parseInt(t, 10) || 0;
    }

    var parent       = elem.parentNode,
        offsetChild  = elem,
        offsetParent = elem.offsetParent,
        doc          = elem.ownerDocument,
        safari2      = br.safari && parseInt(br.version,0) < 522 && !(/adobeair/i).test(br.userAgent),
        css          = CQ.curCSS,
        fixed        = CQ.css(elem, "position") == "fixed";

    // Use getBoundingClientRect if available
    if (!(br.mozilla && elem==document.body) && elem.getBoundingClientRect){
      var box = elem.getBoundingClientRect();

      // Add the document scroll offsets
      add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
        box.top  + Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));

      // IE adds the HTML element's border, by default it is medium which is 
      // 2px IE 6 and 7 quirks mode the border width is overwritable by the 
      // following css html { border: 0; } IE 7 standards mode, the border is 
      // always 2px This border/offset is typically represented by the 
      // clientLeft and clientTop properties
      // However, in IE6 and 7 quirks mode the clientLeft and clientTop 
      // properties are not updated when overwriting it via CSS
      // Therefore this method will be off by 2px in IE while in quirksmode
      add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );

    // Otherwise loop through the offsetParents and parentNodes
    } else {

      // Initial element offsets
      add( elem.offsetLeft, elem.offsetTop );

      // Get parent offsets
      while ( offsetParent ) {
        // Add offsetParent offsets
        add( offsetParent.offsetLeft, offsetParent.offsetTop );

        // Mozilla and Safari > 2 does not include the border on offset parents
        // However Mozilla adds the border for table or table cells
        if ( br.mozilla && !(/^t(able|d|h)$/i).test(offsetParent.tagName) || br.safari && !safari2 ) border( offsetParent );

        // Add the document scroll offsets if position is fixed on any 
        // offsetParent
        if (!fixed && css(offsetParent, "position") == "fixed") fixed = true;

        // Set offsetChild to previous offsetParent unless it is the body 
        // element
        offsetChild  = (/^body$/i).test(offsetParent.tagName) ? offsetChild : offsetParent;
        // Get next offsetParent
        offsetParent = offsetParent.offsetParent;
      }

      // Get parent scroll offsets
      while ( parent && parent.tagName && !(/^body|html$/i).test(parent.tagName)) {
        
        // Remove parent scroll UNLESS that parent is inline or a table to 
        // work around Opera inline/table scrollLeft/Top bug
        if ( !(/^inline|table.*$/i).test(css(parent, "display")) ) {
          // Subtract parent scroll offsets
          add( -parent.scrollLeft, -parent.scrollTop );
        }

        // Mozilla does not add the border for a parent that has overflow != 
        // visible
        if ( br.mozilla && css(parent, "overflow") != "visible" ) border(parent);

        // Get next parent
        parent = parent.parentNode;
      }

      // Safari <= 2 doubles body offsets with a fixed position 
      // element/offsetParent or absolutely positioned offsetChild
      // Mozilla doubles body offsets with a non-absolutely positioned 
      // offsetChild
      if ((safari2 && (fixed || css(offsetChild, "position") == "absolute"))||
        (br.mozilla && css(offsetChild, "position") != "absolute") ) {
          add( -doc.body.offsetLeft, -doc.body.offsetTop );
        }

      // Add the document scroll offsets if position is fixed
      if ( fixed ) {
        add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
          Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
      }
    }

    // Return an object with top and left properties
    results = { top: top, left: left };

    return results;
  };

  CoreQuery.fn.mixin({
    position: function() {
      var left = 0, top = 0, results;

      if ( this[0] ) {
        // Get *real* offsetParent
        var offsetParent = this.offsetParent(),

        // Get correct offsets
        offset       = this.offset(),
        parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();

        // Subtract element margins
        // note: when an element has margin: auto the offsetLeft and marginLeft 
        // are the same in Safari causing offset.left to incorrectly be 0
        offset.top  -= num( this, 'marginTop' );
        offset.left -= num( this, 'marginLeft' );

        // Add offsetParent borders
        parentOffset.top  += num( offsetParent, 'borderTopWidth' );
        parentOffset.left += num( offsetParent, 'borderLeftWidth' );

        // Subtract the two offsets
        results = {
          top:  offset.top  - parentOffset.top,
          left: offset.left - parentOffset.left
        };
      }

      return results;
    },

    offsetParent: function() {
      var offsetParent = this[0].offsetParent || document.body;
      while ( offsetParent && (!(/^body|html$/i).test(offsetParent.tagName) && CQ.css(offsetParent, 'position') == 'static') ) {
        offsetParent = offsetParent.offsetParent;
      }
      return CQ(offsetParent);
    }
  }) ;


  // Create scrollLeft and scrollTop methods
  CQ.each( ['Left', 'Top'], function(i, name) {
    var method = 'scroll' + name;

    CQ.fn[ method ] = function(val) {
      if (!this[0]) return;

      return val != undefined ?

        // Set the scroll offset
        this.each(function() {
          this == window || this == document ?
            window.scrollTo(
              !i ? val : CQ(window).scrollLeft(),
               i ? val : CQ(window).scrollTop()
            ) :
            this[ method ] = val;
        }) :

        // Return the scroll offset
        this[0] == window || this[0] == document ?
          self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
            CQ.boxModel && document.documentElement[ method ] ||
            document.body[ method ] : this[0][ method ];
    };
  });
  
  
  return CoreQuery ;
}()) ;

// Install CoreQuery or jQuery, depending on what is available, as SC.$().
SC.$ = (typeof jQuery == "undefined") ? SC.CoreQuery : jQuery ;

// Add some plugins to CoreQuery.  If jQuery is installed, it will get these
// also. -- test in system/core_query/additions
SC.mixin(SC.$.fn, /** @scope SC.CoreQuery.prototype */ {
  
  isCoreQuery: YES, // walk like a duck
  
  /** @private - better loggin */
  toString: function() {
    var values = [];
    var len = this.length, idx=0;
    for(idx=0;idx<len;idx++) {
      values[idx] = '%@: %@'.fmt(idx,(this[idx]) ? this[idx].toString() : '(null)');
    }
    return "<$:%@>(%@)".fmt(SC.guidFor(this),values.join(' , '));  
  },
  
  /** 
    Returns YES if all member elements are visible.  This is provided as a
    common test since CoreQuery does not support filtering by 
    psuedo-selector.
  */
  isVisible: function() {
    return Array.prototype.every.call(this, function(elem){
      return SC.$.isVisible(elem);
    });
  },
    
  /** Returns a new CQ object with only the first item in the object. */
  first: function() {
    return this.pushStack([this[0]]);
  },
  
  /** Returns a new CQ object with only the last item in the set. */
  last: function() {
    return this.pushStack([this[this.length-1]]);
  },
  
  /** 
    Attempts to find the views managing the passed DOM elements and returns
    them.   This will start with the matched element and walk up the DOM until
    it finds an element managed by a view.
    
    @returns {Array} array of views or null.
  */
  view: function() {
    return this.map(function() { 
      var ret=null, guidKey = SC.viewKey, dom = this, value;
      while(!ret && dom && (dom !== document)) {
        if (value = dom.getAttribute('id')) ret = SC.View.views[value] ;
        dom = dom.parentNode;
      }
      dom =null;
      return ret ;
    });
  },
  
  /**
    You can either pass a single class name and a boolean indicating whether
    the value should be added or removed, or you can pass a hash with all
    the class names you want to add or remove with a boolean indicating 
    whether they should be there or not.
    
    This is far more efficient than using addClass/removeClass.
    
    @param {String|Hash} className class name or hash of classNames + bools
    @param {Boolean} shouldAdd for class name if a string was passed
    @returns {SC.CoreQuery} receiver
  */
  setClass: function(className, shouldAdd) {
    if (SC.none(className)) return this; //nothing to do
    var isHash = SC.typeOf(className) !== SC.T_STRING ;
    var fix = this._fixupClass, key;
    this.each(function() {
      if (this.nodeType !== 1) return; // nothing to do
      
      // collect the class name from the element and build an array
      var classNames = this.className.split(/\s+/), didChange = NO;
      
      // loop through hash or just fix single className
      if (isHash) {
        for(var key in className) {
          if (!className.hasOwnProperty(key)) continue ;
          didChange = fix(classNames, key, className[key]) || didChange;
        } 
      } else didChange = fix(classNames, className, shouldAdd);

      // if classNames were changed, join them and set...
      if (didChange) this.className = classNames.join(' ');
    });
    return this ;
  },

  /** @private used by setClass */
  _fixupClass: function(classNames, name, shouldAdd) {
    var indexOf = classNames.indexOf(name);
    // if should add, add class...
    if (shouldAdd) {
      if (indexOf < 0) { classNames.push(name); return YES ; }
      
    // otherwise, null out class name (this will leave some extra spaces)
    } else if (indexOf >= 0) { classNames[indexOf]=null; return YES; }
    return NO ;
  },
  
  /**
    Returns YES if any of the matched elements have the passed element or CQ object as a child element.
  */
  within: function(el) {
    el = SC.$(el); // make into CQ object
    var ret, elCur, myCur, idx, len = el.length;
    var loc = this.length;
    while(!ret && (--loc >= 0)) {
      myCur = this[loc];
      for(idx=0; !ret && (idx<len); idx++) {
        elCur = el[idx];
        while(elCur && (elCur !== myCur)) elCur = elCur.parentNode;
        ret = elCur === myCur ;
      }
    }
    myCur = elCur = null ; // clear memory
    return ret ;
  }
  
});

/** 
  Make CoreQuery enumerable.  Since some methods need to be disambiguated,
  we will implement some wrapper functions here. 
  
  Note that SC.Enumerable is implemented on SC.Builder, which means the
  CoreQuery object inherits this automatically.  jQuery does not extend from
  SC.Builder though, so we reapply SC.Enumerable just to be safe.
*/
(function() {
  var original = {};
  
  var wrappers = {
    
    // if you call find with a selector, then use the jQuery way.  If you 
    // call with a function/target, use Enumerable way
    find: function(callback,target) {
      return (target !== undefined) ? SC.Enumerable.find.call(this, callback, target) : original.find.call(this, callback) ;
    },

    // ditto for filter - execute SC.Enumerable style if a target is passed.
    filter: function(callback,target) {
      return (target !== undefined) ? 
        this.pushStack(SC.Enumerable.filter.call(this, callback, target)) : 
        original.filter.call(this, callback) ;
    },
    
    // filterProperty is an SC.Enumerable thing, but it needs to be wrapped
    // in a CoreQuery object.
    filterProperty: function(key, value) {
      return this.pushStack(
        SC.Enumerable.filterProperty.call(this,key,value));
    },
    
    // indexOf() is best implemented using the jQuery index()
    indexOf: SC.$.index,
    
    // map() is a little tricky because jQuery is non-standard.  If you pass
    // a context object, we will treat it like SC.Enumerable.  Otherwise use
    // jQuery.
    map: function(callback, target) {
      return (target !== undefined) ?  
        SC.Enumerable.map.call(this, callback, target) : 
        original.map.call(this, callback);
    }
  };

  // loop through an update some enumerable methods.  If this is CoreQuery,
  // we just need to patch up the wrapped methods.  If this is jQuery, we
  // need to go through the entire set of SC.Enumerable.
  var isCoreQuery = SC.$.jquery === 'SC.CoreQuery';
  var fn = SC.$.fn, enumerable = isCoreQuery ? wrappers : SC.Enumerable ;
  for(var key in enumerable) {
    if (!enumerable.hasOwnProperty(key)) continue ;
    var value = enumerable[key];
    if (key in wrappers) {
      original[key] = fn[key]; value = wrappers[key];
    }
    fn[key] = value;
  }
})();

// Add some global helper methods.
SC.mixin(SC.$, {
  
  /** @private helper method to determine if an element is visible.  Exposed
   for use in testing. */
  isVisible: function(elem) {
    var CQ = SC.$;
    return ("hidden"!=elem.type) && (CQ.css(elem,"display")!="none") && (CQ.css(elem,"visibility")!="hidden");
  }
  
}) ;



/* >>>>>>>>>> BEGIN source/system/event.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/core_query') ;

/**
  The event class provides a simple cross-platform library for capturing and
  delivering events on DOM elements and other objects.  While this library
  is based on code from both jQuery and Prototype.js, it includes a number of
  additional features including support for handler objects and event 
  delegation.

  Since native events are implemented very unevenly across browsers,
  SproutCore will convert all native events into a standardized instance of
  this special event class.  
  
  SproutCore events implement the standard W3C event API as well as some 
  additional helper methods.

  @constructor
  @param {Event} originalEvent
  @returns {SC.Event} event instance
  
  @since SproutCore 1.0
*/
SC.Event = function(originalEvent) { 

  // copy properties from original event, if passed in.
  if (originalEvent) {
    this.originalEvent = originalEvent ;
    var props = SC.Event._props, len = props.length, idx = len , key;
    while(--idx >= 0) {
      key = props[idx] ;
      this[key] = originalEvent[key] ;
    }
  }

  // Fix timeStamp
  this.timeStamp = this.timeStamp || Date.now();

  // Fix target property, if necessary
  // Fixes #1925 where srcElement might not be defined either
  if (!this.target) this.target = this.srcElement || document; 

  // check if target is a textnode (safari)
  if (this.target.nodeType === 3 ) this.target = this.target.parentNode;

  // Add relatedTarget, if necessary
  if (!this.relatedTarget && this.fromElement) {
    this.relatedTarget = (this.fromElement === this.target) ? this.toElement : this.fromElement;
  }

  // Calculate pageX/Y if missing and clientX/Y available
  if (SC.none(this.pageX) && !SC.none(this.clientX)) {
    var doc = document.documentElement, body = document.body;
    this.pageX = this.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
    this.pageY = this.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
  }

  // Add which for key events
  if (!this.which && ((this.charCode || originalEvent.charCode === 0) ? this.charCode : this.keyCode)) {
    this.which = this.charCode || this.keyCode;
  }

  // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
  if (!this.metaKey && this.ctrlKey) this.metaKey = this.ctrlKey;

  // Add which for click: 1 == left; 2 == middle; 3 == right
  // Note: button is not normalized, so don't use it
  if (!this.which && this.button) {
    this.which = ((this.button & 1) ? 1 : ((this.button & 2) ? 3 : ( (this.button & 4) ? 2 : 0 ) ));
  }
  
  // normalize wheelDelta, wheelDeltaX, & wheelDeltaY for Safari
  if (SC.browser.safari && originalEvent.wheelDelta!==undefined) {
    this.wheelDelta = this.wheelDeltaY = 0-(originalEvent.wheelDeltaY || originalEvent.wheelDelta);
    this.wheelDeltaX = 0-(originalEvent.wheelDeltaX||0) ;
    
  // normalize wheelDelta for Firefox
  // note that we multiple the delta on FF to make it's acceleration more 
  // natural.
  } else if (!SC.none(originalEvent.detail)) {
    var detail = Math.floor(originalEvent.detail * 2);
    if (originalEvent.axis && (originalEvent.axis === originalEvent.HORIZONTAL_AXIS)) {
      this.wheelDeltaX = detail;
      this.wheelDeltaY = this.wheelDelta = 0;
    } else {
      this.wheelDeltaY = this.wheelDelta = detail ;
      this.wheelDeltaX = 0 ;
    }
    
  // handle all other legacy browser
  } else {
    this.wheelDelta = this.wheelDeltaY = SC.browser.msie ? 0-originalEvent.wheelDelta : originalEvent.wheelDelta ;
    this.wheelDeltaX = 0 ;
  }
  
  return this; 
} ;

SC.mixin(SC.Event, /** @scope SC.Event */ {

  /** 
    Standard method to create a new event.  Pass the native browser event you
    wish to wrap if needed.
  */
  create: function(e) { return new SC.Event(e); },

  // the code below was borrowed from jQuery, Dean Edwards, and Prototype.js
  
  /**
    Bind an event to an element.

    This method will cause the passed handler to be executed whenever a
    relevant event occurs on the named element.  This method supports a
    variety of handler types, depending on the kind of support you need.
    
    h2. Simple Function Handlers
    
      SC.Event.add(anElement, "click", myClickHandler) ;
      
    The most basic type of handler you can pass is a function.  This function
    will be executed everytime an event of the type you specify occurs on the
    named element.  You can optionally pass an additional context object which
    will be included on the event in the event.data property.
    
    When your handler function is called the, the function's "this" property
    will point to the element the event occurred on.
    
    The click handler for this method must have a method signature like:
    
      function(event) { return YES|NO; }
      
    h2. Method Invocations
    
      SC.Event.add(anElement, "click", myObject, myObject.aMethod) ;
      
    Optionally you can specify a target object and a method on the object to 
    be invoked when the event occurs.  This will invoke the method function
    with the target object you pass as "this".  The method should have a 
    signature like:
    
      function(event, targetElement) { return YES|NO; }
      
    Like function handlers, you can pass an additional context data paramater
    that will be included on the event in the event.data property.
      
    h2. Handler Return Values
    
    Both handler functions should return YES if you want the event to 
    continue to propagate and NO if you want it to stop.  Returning NO will
    both stop bubbling of the event and will prevent any default action 
    taken by the browser.  You can also control these two behaviors separately
    by calling the stopPropagation() or preventDefault() methods on the event
    itself, returning YES from your method.
    
    h2. Limitations
    
    Although SproutCore's event implementation is based on jQuery, it is 
    much simpler in design.  Notably, it does not support namespaced events
    and you can only pass a single type at a time.
    
    If you need more advanced event handling, consider the SC.ClassicResponder 
    functionality provided by SproutCore or use your favorite DOM library.

    @param {Element} elem a DOM element, window, or document object
    @param {String} eventType the event type you want to respond to
    @param {Object} target The target object for a method call or a function.
    @param {Object} method optional method or method name if target passed
    @param {Object} context optional context to pass to the handler as event.data
    @returns {Object} receiver
  */
  add: function(elem, eventType, target, method, context) {

    // if a CQ object is passed in, either call add on each item in the 
    // matched set, or simply get the first element and use that.
    if (elem && elem.isCoreQuery) {
      if (elem.length > 0) {
        elem.forEach(function(e) { 
          this.add(e, eventType, target, method, context);
        }, this);
        return this;
      } else elem = elem.get(0);
    }
    if (!elem) return this; // nothing to do
    
    // cannot register events on text nodes, etc.
    if ( elem.nodeType == 3 || elem.nodeType == 8 ) return SC.Event;

    // For whatever reason, IE has trouble passing the window object
    // around, causing it to be cloned in the process
    if (SC.browser.msie && elem.setInterval) elem = window;

    // if target is a function, treat it as the method, with optional context
    if (SC.typeOf(target) === SC.T_FUNCTION) {
      context = method; method = target; target = null;
      
    // handle case where passed method is a key on the target.
    } else if (target && SC.typeOf(method) === SC.T_STRING) {
      method = target[method] ;
    }

    // Get the handlers queue for this element/eventType.  If the queue does
    // not exist yet, create it and also setup the shared listener for this
    // eventType.
    var events = SC.data(elem, "events") || SC.data(elem, "events", {}) ,
        handlers = events[eventType]; 
    if (!handlers) {
      handlers = events[eventType] = {} ;
      this._addEventListener(elem, eventType) ;
    }
    
    // Build the handler array and add to queue
    handlers[SC.guidFor(method)] = [target, method, context];
    SC.Event._global[eventType] = YES ; // optimization for global triggers

    // Nullify elem to prevent memory leaks in IE
    elem = events = handlers = null ;
    return this ;
  },

  /**
    Removes a specific handler or all handlers for an event or event+type.

    To remove a specific handler, you must pass in the same function or the
    same target and method as you passed into SC.Event.add().  See that method
    for full documentation on the parameters you can pass in.
    
    If you omit a specific handler but provide both an element and eventType,
    then all handlers for that element will be removed.  If you provide only
    and element, then all handlers for all events on that element will be
    removed.
    
    h2. Limitations
    
    Although SproutCore's event implementation is based on jQuery, it is 
    much simpler in design.  Notably, it does not support namespaced events
    and you can only pass a single type at a time.
    
    If you need more advanced event handling, consider the SC.ClassicResponder 
    functionality provided by SproutCore or use your favorite DOM library.
    
    @param {Element} elem a DOM element, window, or document object
    @param {String} eventType the event type to remove
    @param {Object} target The target object for a method call.  Or a function.
    @param {Object} method optional name of method
    @returns {Object} receiver
  */
  remove: function(elem, eventType, target, method) {

    // if a CQ object is passed in, either call add on each item in the 
    // matched set, or simply get the first element and use that.
    if (elem && elem.isCoreQuery) {
      if (elem.length > 0) {
        elem.forEach(function(e) { 
          this.remove(e, eventType, target, method);
        }, this);
        return this;
      } else elem = elem.get(0);
    }
    if (!elem) return this; // nothing to do
    
    // don't do events on text and comment nodes
    if ( elem.nodeType == 3 || elem.nodeType == 8 ) return SC.Event;

    // For whatever reason, IE has trouble passing the window object
    // around, causing it to be cloned in the process
    if (SC.browser.msie && elem.setInterval) elem = window;

    var handlers, key, events = SC.data(elem, "events") ;
    if (!events) return this ; // nothing to do if no events are registered

    // if no type is provided, remove all types for this element.
    if (eventType === undefined) {
      for(eventType in events) this.remove(elem, eventType) ;

    // otherwise, remove the handler for this specific eventType if found
    } else if (handlers = events[eventType]) {

      var cleanupHandlers = NO ;
      
      // if a target/method is provided, remove only that one
      if (target || method) {
        
        // normalize the target/method
        if (SC.typeOf(target) === SC.T_FUNCTION) {
          method = target; target = null ;
        } else if (SC.typeOf(method) === SC.T_STRING) {
          method = target[method] ;
        }
        
        delete handlers[SC.guidFor(method)] ;
        
        // check to see if there are handlers left on this event/eventType.
        // if not, then cleanup the handlers.
        key = null ;
        for(key in handlers) break ;
        if (key===null) cleanupHandlers = YES ;

      // otherwise, just cleanup all handlers
      } else cleanupHandlers = YES ;
      
      // If there are no more handlers left on this event type, remove 
      // eventType hash from queue.
      if (cleanupHandlers) {
        delete events[eventType] ;
        this._removeEventListener(elem, eventType) ;
      }
      
      // verify that there are still events registered on this element.  If 
      // there aren't, cleanup the element completely to avoid memory leaks.
      key = null ;
      for(key in events) break;
      if(!key) {
        SC.removeData(elem, "events") ;
        delete this._elements[SC.guidFor(elem)]; // important to avoid leaks
      }
      
    }
    
    elem = events = handlers = null ; // avoid memory leaks
    return this ;
  },

  NO_BUBBLE: ['blur', 'focus', 'change'],
  
  /**
    Generates a simulated event object.  This is mostly useful for unit 
    testing.  You can pass the return value of this property into the 
    trigger() method to actually send the event.
    
    @param {Element} elem the element the event targets
    @param {String} eventType event type.  mousedown, mouseup, etc
    @param {Hash} attrs optional additonal attributes to apply to event.
    @returns {Hash} simulated event object
  */
  simulateEvent: function(elem, eventType, attrs) {
    var ret = SC.Event.create({
      type: eventType,
      target: elem,
      preventDefault: function(){ this.cancelled = YES; },
      stopPropagation: function(){ this.bubbles = NO; },
      allowDefault: function() { this.hasCustomEventHandling = YES; },
      timeStamp: Date.now(),
      bubbles: (this.NO_BUBBLE.indexOf(eventType)<0),
      cancelled: NO,
      normalized: YES
    });
    if (attrs) SC.mixin(ret, attrs) ;
    return ret ;
  },
  
  /**
    Trigger an event execution immediately.  You can use this method to 
    simulate arbitrary events on arbitary elements.

    h2. Limitations
    
    Note that although this is based on the jQuery implementation, it is 
    much simpler.  Notably namespaced events are not supported and you cannot
    trigger events globally.
    
    If you need more advanced event handling, consider the SC.Responder 
    functionality provided by SproutCore or use your favorite DOM library.

    h2. Example
    
    {{{
      SC.Event.trigger(view.get('layer'), 'mousedown');
    }}}
    
    @param elem {Element} the target element
    @param eventType {String} the event type
    @param args {Array} optional argument or arguments to pass to handler.
    @param donative ??
    @returns {Boolean} Return value of trigger or undefined if not fired
  */
  trigger: function(elem, eventType, args, donative) {

    // if a CQ object is passed in, either call add on each item in the 
    // matched set, or simply get the first element and use that.
    if (elem && elem.isCoreQuery) {
      if (elem.length > 0) {
        elem.forEach(function(e) { 
          this.trigger(e, eventType, args, donative);
        }, this);
        return this;
      } else elem = elem.get(0);
    }
    if (!elem) return this; // nothing to do

    // don't do events on text and comment nodes
    if ( elem.nodeType == 3 || elem.nodeType == 8 ) return undefined;
    
    // Normalize to an array
    args = SC.A(args) ;

    var ret, fn = SC.typeOf(elem[eventType] || null) === SC.T_FUNCTION , 
        event, current, onfoo;

    // Get the event to pass, creating a fake one if necessary
    event = args[0];
    if (!event || !event.preventDefault) {
      event = this.simulateEvent(elem, eventType) ;
      args.unshift(event) ;
    }
    
    event.type = eventType ;
    
    // Trigger the event - bubble if enabled
    current = elem;
    do {
      ret = SC.Event.handle.apply(current, args);
      current = (current===document) ? null : (current.parentNode || document);
    } while(!ret && event.bubbles && current);    
    current = null ;

    // Handle triggering native .onfoo handlers
    onfoo = elem["on" + eventType] ;
    isClick = SC.CoreQuery.nodeName(elem, 'a') && eventType === 'click';
    if ((!fn || isClick) && onfoo && onfoo.apply(elem, args) === NO) ret = NO;

    // Trigger the native events (except for clicks on links)
    if (fn && donative !== NO && ret !== NO && !isClick) {
      this.triggered = YES;
      try {
        elem[ eventType ]();
      // prevent IE from throwing an error for some hidden elements
      } catch (e) {}
    }
    
    this.triggered = NO;

    return ret;
  },

  /**
    This method will handle the passed event, finding any registered listeners
    and executing them.  If you have an event you want handled, you can 
    manually invoke this method.  This function expects it's "this" value to
    be the element the event occurred on, so you should always call this 
    method like:
    
      SC.Event.handle.call(element, event) ;
      
    Note that like other parts of this library, the handle function does not
    support namespaces.
    
    @param event {Event} the event to handle
    @returns {Boolean}
  */
  handle: function(event) {

    // ignore events triggered after window is unloaded or if double-called
    // from within a trigger.
    if ((typeof SC === "undefined") || SC.Event.triggered) return YES ;
    
    // returned undefined or NO
    var val, ret, namespace, all, handlers, args, key, handler, method, target;

    // normalize event across browsers.  The new event will actually wrap the
    // real event with a normalized API.
    args = SC.A(arguments);
    args[0] = event = SC.Event.normalizeEvent(event || window.event);

    // get the handlers for this event type
    handlers = (SC.data(this, "events") || {})[event.type];
    if (!handlers) return NO ; // nothing to do
    
    // invoke all handlers
    for (key in handlers ) {
      handler = handlers[key];
      method = handler[1] ;

      // Pass in a reference to the handler function itself
      // So that we can later remove it
      event.handler = method;
      event.data = event.context = handler[2];

      target = handler[0] || this ;
      ret = method.apply( target, args );
      
      if (val !== NO) val = ret;

      // if method returned NO, do not continue.  Stop propogation and
      // return default.  Note that we test explicitly for NO since 
      // if the handler returns no specific value, we do not want to stop.
      if ( ret === NO ) {
        event.preventDefault();
        event.stopPropagation();
      }
    }

    return val;
  },

  /**
    This method is called just before the window unloads to unhook all 
    registered events.
  */
  unload: function() {
    var key, elements = this._elements ;
    for(key in elements) this.remove(elements[key]) ;
    
    // just in case some book-keeping was screwed up.  avoid memory leaks
    for(key in elements) delete elements[key] ;
    delete this._elements ; 
  },
  
  /**
    This hash contains handlers for special or custom events.  You can add
    your own handlers for custom events here by simply naming the event and
    including a hash with the following properties:
    
     - setup: this function should setup the handler or return NO
     - teardown: this function should remove the event listener
     
  */
  special: {
    
    ready: {
      setup: function() {
        // Make sure the ready event is setup
        SC._bindReady() ;
        return;
      },

      teardown: function() { return; }

    },

    /** @private
        Implement support for mouseenter on browsers other than IE */
    mouseenter: {
      setup: function() {
        if ( SC.browser.msie ) return NO;
        SC.Event.add(this, 'mouseover', SC.Event.special.mouseover.handler);
        return YES;
      },

      teardown: function() {
        if ( SC.browser.msie ) return NO;
        SC.Event.remove(this, 'mouseover', SC.Event.special.mouseover.handler);
        return YES;
      },

      handler: function(event) {
        // If we actually just moused on to a sub-element, ignore it
        if ( SC.Event._withinElement(event, this) ) return YES;
        // Execute the right handlers by setting the event type to mouseenter
        event.type = "mouseenter";
        return SC.Event.handle.apply(this, arguments);
      }
    },

    /** @private
        Implement support for mouseleave on browsers other than IE */
    mouseleave: {
      setup: function() {
        if ( SC.browser.msie ) return NO;
        SC.Event.add(this, "mouseout", SC.Event.special.mouseleave.handler);
        return YES;
      },

      teardown: function() {
        if ( SC.browser.msie ) return NO;
        SC.Event.remove(this, "mouseout", SC.Event.special.mouseleave.handler);
        return YES;
      },

      handler: function(event) {
        // If we actually just moused on to a sub-element, ignore it
        if ( SC.Event._withinElement(event, this) ) return YES;
        // Execute the right handlers by setting the event type to mouseleave
        event.type = "mouseleave";
        return SC.Event.handle.apply(this, arguments);
      }
    }
  },

  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,
  KEY_INSERT:   45,
    
  _withinElement: function(event, elem) {
    // Check if mouse(over|out) are still within the same parent element
    var parent = event.relatedTarget;
    
    // Traverse up the tree
    while ( parent && parent != elem ) {
      try { parent = parent.parentNode; } catch(error) { parent = elem; }
    }

    // Return YES if we actually just moused on to a sub-element
    return parent === elem;
  },
  
  /** @private
    Adds the primary event listener for the named type on the element.
    
    If the event type has a special handler defined in SC.Event.special, 
    then that handler will be used.  Otherwise the normal browser method will
    be used.
    
    @param elem {Element} the target element
    @param eventType {String} the event type
  */
  _addEventListener: function(elem, eventType) {
    var listener, special = this.special[eventType] ;

    // Check for a special event handler
    // Only use addEventListener/attachEvent if the special
    // events handler returns NO
    if ( !special || special.setup.call(elem)===NO) {
      
      // Save element in cache.  This must be removed later to avoid 
      // memory leaks.
      var guid = SC.guidFor(elem) ;
      this._elements[guid] = elem;
      
      listener = SC.data(elem, "listener") || SC.data(elem, "listener", 
       function() {
         return SC.Event.handle.apply(SC.Event._elements[guid], arguments); 
      }) ;
      
      // Bind the global event handler to the element
      if (elem.addEventListener) {
        elem.addEventListener(eventType, listener, NO);
      } else if (elem.attachEvent) {
        // attachEvent is not working for IE8 and xhr objects
        // there is currently a hack in request , but it needs to fixed here.
        elem.attachEvent("on" + eventType, listener);
      }
      //  
      // else {
      //         elem.onreadystatechange = listener;
      //       }
    }
    
    elem = special = listener = null ; // avoid memory leak
  },

  /** @private
    Removes the primary event listener for the named type on the element.
    
    If the event type has a special handler defined in SC.Event.special, 
    then that handler will be used.  Otherwise the normal browser method will
    be used.
    
    Note that this will not clear the _elements hash from the element.  You
    must call SC.Event.unload() on unload to make sure that is cleared.
    
    @param elem {Element} the target element
    @param eventType {String} the event type
  */
  _removeEventListener: function(elem, eventType) {
    var listener, special = SC.Event.special[eventType] ;
    if (!special || (special.teardown.call(elem)===NO)) {
      listener = SC.data(elem, "listener") ;
      if (listener) {
        if (elem.removeEventListener) {
          elem.removeEventListener(eventType, listener, NO);
        } else if (elem.detachEvent) {
          elem.detachEvent("on" + eventType, listener);
        }
      }
    }
    
    elem = special = listener = null ;
  },

  _elements: {},
  
  // implement preventDefault() in a cross platform way
  
  /** @private Take an incoming event and convert it to a normalized event. */
  normalizeEvent: function(event) {
    if (event == window.event) {
      // IE can't do event.normalized on an Event object
      return SC.Event.create(event) ; 
    } else {
      return event.normalized ? event : SC.Event.create(event) ;
    }
  },
  
  _global: {},

  /** @private properties to copy from native event onto the event */
  _props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view which touches targetTouches changedTouches".split(" ")
  
}) ;

SC.Event.prototype = {

  /**
    Set to YES if you have called either preventDefault() or stopPropagation().  This allows a generic event handler to notice if you want to provide detailed control over how the browser handles the real event.
  */
  hasCustomEventHandling: NO,
  
  /**
    Indicates that you want to allow the normal default behavior.  Sets
    the hasCustomEventHandling property to YES but does not cancel the event.
    
    @returns {SC.Event} receiver
  */
  allowDefault: function() {
    this.hasCustomEventHandling = YES ;
    return this ;  
  },
  
  /** 
    Implements W3C standard.  Will prevent the browser from performing its
    default action on this event.
    
    @returns {SC.Event} receiver
  */
  preventDefault: function() {
    var evt = this.originalEvent ;
    if (evt) {
      if (evt.preventDefault) evt.preventDefault() ;
      evt.returnValue = NO ; // IE
    }
    this.hasCustomEventHandling = YES ;
    return this ;
  },

  /**
    Implements W3C standard.  Prevents further bubbling of the event.
    
    @returns {SC.Event} receiver
  */
  stopPropagation: function() {
    var evt = this.originalEvent ;
    if (evt) {
      if (evt.stopPropagation) evt.stopPropagation() ;
      evt.cancelBubble = YES ; // IE
    }
    this.hasCustomEventHandling = YES ; 
    return this ;
  },

  /** 
    Stops both the default action and further propogation.  This is more 
    convenient than calling both.
    
    @returns {SC.Event} receiver
  */
  stop: function() {
    return this.preventDefault().stopPropagation();
  },
  
  /** Always YES to indicate the event was normalized. */
  normalized: YES,

  /** Returns the pressed character (found in this.which) as a string. */
  getCharString: function() { 
    return (this.charCode>0) ? String.fromCharCode(this.which) : null;
  },
  
  /** Returns character codes for the event.  The first value is the normalized code string, with any shift or ctrl characters added to the begining.  The second value is the char string by itself.
  
    @returns {Array}
  */
  commandCodes: function() {
    var code=this.keyCode, ret=null, key=null, modifiers='', lowercase ;
    
    // handle function keys.
    if (code) {
      ret = SC.FUNCTION_KEYS[code] ;
      if (!ret && (this.altKey || this.ctrlKey || this.metaKey)) {
        ret = SC.PRINTABLE_KEYS[code];
      }
      
      if (ret) {
        if (this.altKey) modifiers += 'alt_' ;
        if (this.ctrlKey || this.metaKey) modifiers += 'ctrl_' ;
        if (this.shiftKey) modifiers += 'shift_' ;
      }
    }

    // otherwise just go get the right key.
    if (!ret) {
      code = this.which ;
      key = ret = String.fromCharCode(code) ;
      lowercase = ret.toLowerCase() ;
      if (this.metaKey) {
        modifiers = 'meta_' ;
        ret = lowercase;
        
      } else ret = null ;
    }

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

// Also provide a Prototype-like API so that people can use either one.

/** Alias for add() method.  This provides a Prototype-like API. */
SC.Event.observe = SC.Event.add ;

/** Alias for remove() method.  This provides a Prototype-like API */
SC.Event.stopObserving = SC.Event.remove ;

/** Alias for trigger() method.  This provides a Prototype-like API */
SC.Event.fire = SC.Event.trigger;

// Register unload handler to eliminate any registered handlers
// This avoids leaks in IE and issues with mouseout or other handlers on 
// other browsers.
SC.Event.add(window, 'unload', SC.Event.prototype, SC.Event.unload) ;

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:"\""
} ;

/* >>>>>>>>>> BEGIN source/system/cursor.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

// standard browser cursor definitions
SC.SYSTEM_CURSOR = 'default' ;
SC.AUTO_CURSOR = SC.DEFAULT_CURSOR = 'auto' ;
SC.CROSSHAIR_CURSOR = 'crosshair' ;
SC.HAND_CURSOR = SC.POINTER_CURSOR = 'pointer' ;
SC.MOVE_CURSOR = 'move' ;
SC.E_RESIZE_CURSOR = 'e-resize' ;
SC.NE_RESIZE_CURSOR = 'ne-resize' ;
SC.NW_RESIZE_CURSOR = 'nw-resize' ;
SC.N_RESIZE_CURSOR = 'n-resize' ;
SC.SE_RESIZE_CURSOR = 'se-resize' ;
SC.SW_RESIZE_CURSOR = 'sw-resize' ;
SC.S_RESIZE_CURSOR = 's-resize' ;
SC.W_RESIZE_CURSOR = 'w-resize' ;
SC.IBEAM_CURSOR = SC.TEXT_CURSOR = 'text' ;
SC.WAIT_CURSOR = 'wait' ;
SC.HELP_CURSOR = 'help' ;

/**
  @class SC.Cursor

  A Cursor object is used to sychronize the cursor used by multiple views at 
  the same time. For example, thumb views within a split view acquire a cursor
  instance from the split view and set it as their cursor. The split view is 
  able to update its cursor object to reflect the state of the split view.
  Because cursor objects are implemented internally with CSS, this is a very 
  efficient way to update the same cursor for a group of view objects.
  
  Note: This object creates an anonymous CSS class to represent the cursor. 
  The anonymous CSS class is automatically added by SproutCore to views that
  have the cursor object set as "their" cursor. Thus, all objects attached to 
  the same cursor object will have their cursors updated simultaneously with a
  single DOM call.
  
  @extends SC.Object
*/
SC.Cursor = SC.Object.extend(
/** @scope SC.Cursor.prototype */ {
  
  /** @private */
  init: function() {
    arguments.callee.base.apply(this,arguments) ;
    
    // create a unique style rule and add it to the shared cursor style sheet
    var cursorStyle = this.get('cursorStyle') || SC.DEFAULT_CURSOR ,
        ss = this.constructor.sharedStyleSheet() ;
    
    if (ss.insertRule) { // WC3
      ss.insertRule(
        '.%@ {cursor: %@;}'.fmt(SC.guidFor(this), cursorStyle),
        ss.cssRules ? ss.cssRules.length : 0
      ) ;
    } else if (ss.addRule) { // IE
      ss.addRule('.'+SC.guidFor(this), 'cursor: '+cursorStyle) ;
    }
    
    this.cursorStyle = cursorStyle ;
    this.className = SC.guidFor(this) ; // used by cursor clients...
    return this ;
  },
  
  /**
    This property is the connection between cursors and views. The default
    SC.View behavior is to add this className to a view's layer if it has
    its cursor property defined.
    
    @readOnly
    @property {String} the css class name updated by this cursor
  */
  className: null,
  
  /**
    @property {String} the cursor value, can be 'url("path/to/cursor")'
  */
  cursorStyle: SC.DEFAULT_CURSOR,
  
  /** @private */
  cursorStyleDidChange: function() {
    var cursorStyle, rule, selector, ss, rules, idx, len;
    cursorStyle = this.get('cursorStyle') || SC.DEFAULT_CURSOR;
    rule = this._rule;
    if (rule) {
      rule.style.cursor = cursorStyle ; // fast path
      return ;
    }
    
    // slow path, taken only once
    selector = '.'+this.get('className') ;
    ss = this.constructor.sharedStyleSheet() ;
    rules = (ss.cssRules ? ss.cssRules : ss.rules) || [] ;
    
    // find our rule, cache it, and update the cursor style property
    for (idx=0, len = rules.length; idx<len; ++idx) {
      rule = rules[idx] ;
      if (rule.selectorText === selector) {
        this._rule = rule ; // cache for next time
        rule.style.cursor = cursorStyle ; // update the cursor
        break ;
      }
    }
  }.observes('cursorStyle')
  
  // TODO implement destroy
  
});

/** @private */
SC.Cursor.sharedStyleSheet = function() {
  var head, ss = this._styleSheet ;
  if (!ss) {
    // create the stylesheet object the hard way (works everywhere)
    ss = document.createElement('style') ;
    ss.type = 'text/css' ;
    head = document.getElementsByTagName('head')[0] ;
    if (!head) head = document.documentElement ; // fix for Opera
    head.appendChild(ss) ;
    
    // get the actual stylesheet object, not the DOM element
    ss = document.styleSheets[document.styleSheets.length-1] ;
    this._styleSheet = ss ;
  }
  return ss ;
};

/* >>>>>>>>>> BEGIN source/system/responder.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @class

  Provides common methods for sending events down a responder chain.
  Responder chains are used most often to deliver events to user interface
  elements in your application, but you can also use them to deliver generic
  events to any part of your application, including controllers.

  @extends SC.Object
  @since SproutCore 1.0
*/
SC.Responder = SC.Object.extend( /** SC.Responder.prototype */ {

  isResponder: YES,
  
  /** @property
    The pane this responder belongs to.  This is used to determine where you 
    belong to in the responder chain.  Normally you should leave this property
    set to null.
  */
  pane: null,
  
  /** @property
    The app this responder belongs to.  For non-user-interface responder 
    chains, this is used to determine the context.  Usually this
    is the property you will want to work with.
  */
  responderContext: null,
  
  /** @property
    This is the nextResponder in the responder chain.  If the receiver does 
    not implement a particular event handler, it will bubble to the next 
    responder.
    
    This can point to an object directly or it can be a string, in which case
    the path will be resolved from the responderContext root.
  */
  nextResponder: null,
  
  /** @property 
    YES if the view is currently first responder.  This property is always 
    edited by the pane during its makeFirstResponder() method.
  */
  isFirstResponder: NO,
  
  /** @property
  
    YES the responder is somewhere in the responder chain.  This currently
    only works when used with a ResponderContext.
    
    @type {Boolean}
  */
  hasFirstResponder: NO,    
  
  /** @property
    Set to YES if your view is willing to accept first responder status.  This is used when calculcating key responder loop.
  */
  acceptsFirstResponder: YES,
  
  /** 
    Call this method on your view or responder to make it become first 
    responder.
    
    @returns {SC.Responder} receiver
  */
  becomeFirstResponder: function() {  
    var pane = this.get('pane') || this.get('responderContext') ||
              this.pane();
    if (pane && this.get('acceptsFirstResponder')) {
      if (pane.get('firstResponder') !== this) pane.makeFirstResponder(this);
    } 
    return this ;
  },
  
  /**
    Call this method on your view or responder to resign your first responder 
    status. Normally this is not necessary since you will lose first responder 
    status automatically when another view becomes first responder.
    
    @returns {SC.Responder} receiver
  */
  resignFirstResponder: function() {
    var pane = this.get('pane') || this.get('responderContext');
    if (pane && (pane.get('firstResponder') === this)) {
      pane.makeFirstResponder(null);
    }
    return YES;  
  },

  /**
    Called just before the responder or any of its subresponder's are about to
    lose their first responder status.  The passed responder is the responder
    that is about to lose its status. 
    
    Override this method to provide any standard teardown when the first 
    responder changes.
    
    @param {SC.Responder} responder the responder that is about to change
    @returns {void}
  */
  willLoseFirstResponder: function(responder) {},
  
  /**
    Called just after the responder or any of its subresponder's becomes a 
    first responder.  
    
    Override this method to provide any standard setup when the first 
    responder changes.
    
    @param {SC.Responder} responder the responder that changed
    @returns {void}
  */
  didBecomeFirstResponder: function(responder) {}

});

/* >>>>>>>>>> BEGIN source/views/view.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/browser');
sc_require('system/event');
sc_require('system/cursor');
sc_require('system/responder') ;

sc_require('mixins/string') ;

SC.viewKey = SC.guidKey + "_view" ;

/** Select a horizontal layout for various views.*/
SC.LAYOUT_HORIZONTAL = 'sc-layout-horizontal';

/** Select a vertical layout for various views.*/
SC.LAYOUT_VERTICAL = 'sc-layout-vertical';

/** @private */
SC._VIEW_DEFAULT_DIMS = 'marginTop marginLeft'.w();

/**
  Layout properties needed to anchor a view to the top.
*/
SC.ANCHOR_TOP = { top: 0 };

/**
  Layout properties needed to anchor a view to the left.
*/
SC.ANCHOR_LEFT = { left: 0 };

/*
  Layout properties to anchor a view to the top left 
*/
SC.ANCHOR_TOP_LEFT = { top: 0, left: 0 };

/**
  Layout properties to anchoe view to the bottom.
*/
SC.ANCHOR_BOTTOM = { bottom: 0 };

/**
  Layout properties to anchor a view to the right.
*/
SC.ANCHOR_RIGHT = { right: 0 } ;

/**
  Layout properties to anchor a view to the bottom right.
*/
SC.ANCHOR_BOTTOM_RIGHT = { bottom: 0, right: 0 };

/**
  Layout properties to take up the full width of a parent view.
*/
SC.FULL_WIDTH = { left: 0, right: 0 };

/**
  Layout properties to take up the full height of a parent view.
*/
SC.FULL_HEIGHT = { top: 0, bottom: 0 };

/**
  Layout properties to center.  Note that you must also specify a width and
  height for this to work.
*/
SC.ANCHOR_CENTER = { centerX: 0, centerY: 0 };

/**
  Layout property for width, height
*/

SC.LAYOUT_AUTO = 'auto';

/** @private - custom array used for child views */
SC.EMPTY_CHILD_VIEWS_ARRAY = [];
SC.EMPTY_CHILD_VIEWS_ARRAY.needsClone = YES;

/** 
  @class
  
  Base class for managing a view.  View's provide two functions:
  
  1. They translate state and events into drawing instructions for the 
     web browser and
  
  2. They act as first responders for incoming keyboard, mouse, and 
     touch events.
  
  h2. View Initialization
  
  When a view is setup, there are several methods you can override that 
  will be called at different times depending on how your view is created.
  Here is a guide to which method you want to override and when:
  
  - *init:* override this method for any general object setup (such as 
    observers, starting timers and animations, etc) that you need to happen 
    everytime the view is created, regardless of whether or not its layer 
    exists yet.
    
  - *render:* override this method to generate or update your HTML to reflect
    the current state of your view.  This method is called both when your view
    is first created and later anytime it needs to be updated.

  - *didCreateLayer:* the render() method is used to generate new HTML.  
    Override this method to perform any additional setup on the DOM you might
    need to do after creating the view.  For example, if you need to listen
    for events.
    
  - *willDestroyLayer:* if you implement didCreateLayer() to setup event 
    listeners, you should implement this method as well to remove the same 
    just before the DOM for your view is destroyed.
    
  - *updateLayer:* Normally, when a view needs to update its content, it will
    re-render the view using the render() method.  If you would like to 
    override this behavior with your own custom updating code, you can 
    replace updateLayer() with your own implementation instead.
  
  @extends SC.Responder
  @extends SC.DelegateSupport
  @since SproutCore 1.0
*/
SC.View = SC.Responder.extend(SC.DelegateSupport,
/** @scope SC.View.prototype */ {
  
  concatenatedProperties: 'outlets displayProperties layoutProperties classNames renderMixin didCreateLayerMixin willDestroyLayerMixin'.w(),
  
  /** 
    The current pane. 
    @property {SC.Pane}
  */
  pane: function() {
    var view = this ;
    while (view && !view.isPane) view = view.get('parentView') ;
    return view ;
  }.property('parentView').cacheable(),
  
  /**
    The page this view was instantiated from.  This is set by the page object
    during instantiation.
    
    @property {SC.Page}
  */
  page: null,
    
  /** 
    The current split view this view is embedded in (may be null). 
    @property {SC.SplitView}
  */
  splitView: function() {
    var view = this ;
    while (view && !view.isSplitView) view = view.get('parentView') ;
    return view ;
  }.property('parentView').cacheable(),
  
  /**
    If the view is currently inserted into the DOM of a parent view, this
    property will point to the parent of the view.
  */
  parentView: null,
  
  /**
    Optional background color.  Will be applied to the view's element if 
    set.  This property is intended for one-off views that need a background
    element.  If you plan to create many view instances it is probably better
    to use CSS.
  
    @property {String}
  */
  backgroundColor: null,
  
  // ..........................................................
  // IS ENABLED SUPPORT
  // 
  
  /** 
    Set to true when the item is enabled.   Note that changing this value
    will also alter the isVisibleInWindow property for this view and any
    child views.
    
    Note that if you apply the SC.Control mixin, changing this property will
    also automatically add or remove a 'disabled' CSS class name as well.
    
    This property is observable and bindable.
    
    @property {Boolean}
  */
  isEnabled: YES,
  isEnabledBindingDefault: SC.Binding.oneWay().bool(),
  
  /**
    Computed property returns YES if the view and all of its parent views
    are enabled in the pane.  You should use this property when deciding 
    whether to respond to an incoming event or not.
    
    This property is not observable.
    
    @property {Boolean}
  */
  isEnabledInPane: function() {
    var ret = this.get('isEnabled'), pv ;
    if (ret && (pv = this.get('parentView'))) ret = pv.get('isEnabledInPane');
    return ret ;
  }.property('parentView', 'isEnabled'),
  
  // ..........................................................
  // IS VISIBLE IN WINDOW SUPPORT
  // 
  
  /**
    The isVisible property determines if the view is shown in the view 
    hierarchy it is a part of. A view can have isVisible == YES and still have
    isVisibleInWindow == NO. This occurs, for instance, when a parent view has
    isVisible == NO. Default is YES.
    
    The isVisible property is considered part of the layout and so changing it
    will trigger a layout update.
    
    @property {Boolean}
  */
  isVisible: YES,
  isVisibleBindingDefault: SC.Binding.bool(),
  
  /**
    YES only if the view and all of its parent views are currently visible
    in the window.  This property is used to optimize certain behaviors in
    the view.  For example, updates to the view layer are not performed 
    if the view until the view becomes visible in the window.
  */
  isVisibleInWindow: NO,
  
  /**
    Recomputes the isVisibleInWindow property based on the visibility of the 
    view and its parent.  If the recomputed value differs from the current 
    isVisibleInWindow state, this method will also call 
    recomputIsVisibleInWindow() on its child views as well.  As an optional 
    optimization, you can pass the isVisibleInWindow state of the parentView 
    if you already know it.
    
    You will not generally need to call or override this method yourself. It 
    is used by the SC.View hierarchy to relay window visibility changes up 
    and down the chain.
    
    @property {Boolean} parentViewIsVisible
    @returns {SC.View} receiver 
  */
  recomputeIsVisibleInWindow: function(parentViewIsVisible) {
    var last = this.get('isVisibleInWindow') ;
    var cur = this.get('isVisible'), parentView ;
    
    // isVisibleInWindow = isVisible && parentView.isVisibleInWindow
    // this approach only goes up to the parentView if necessary.
    if (cur) {
      cur = (parentViewIsVisible === undefined) ? 
       ((parentView=this.get('parentView')) ? 
         parentView.get('isVisibleInWindow') : NO) : parentViewIsVisible ;
    }
    
    // if the state has changed, update it and notify children
    if (last !== cur) {
      this.set('isVisibleInWindow', cur) ;
      this._needsVisibiltyChange = YES ; // update even if we aren't visible
      
      var childViews = this.get('childViews'), len = childViews.length, idx;
      for(idx=0;idx<len;idx++) {
        childViews[idx].recomputeIsVisibleInWindow(cur);
      }
        
      // if we just became visible, update layer + layout if needed...
      if (cur) {
        if (this.parentViewDidResize) this.parentViewDidResize();
        
        if (this.get('childViewsNeedLayout')) {
          this.invokeOnce(this.layoutChildViewsIfNeeded);
        }
      }
      
      this.set('layerNeedsUpdate', YES) ;
      
      // if we were firstResponder, resign firstResponder also if no longer
      // visible.
      if (!cur && this.get('isFirstResponder')) this.resignFirstResponder();
      
    }
    return this ;
  }.observes('isVisible'),
  
  // ..........................................................
  // CHILD VIEW SUPPORT
  // 
  
  /** 
    Array of child views.  You should never edit this array directly unless
    you are implementing createChildViews().  Most of the time, you should
    use the accessor methods such as appendChild(), insertBefore() and 
    removeChild().
    
    @property {Array} 
  */
  childViews: SC.EMPTY_CHILD_VIEWS_ARRAY,
  
  /**
    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 {SC.View} view
    @param {SC.View} beforeView
    @returns {SC.View} the receiver
  */
  insertBefore: function(view, beforeView) { 
    view.beginPropertyChanges(); // limit notifications
    
    // remove view from old parent if needed.  Also notify views.
    if (view.get('parentView')) view.removeFromParent() ;
    if (this.willAddChild) this.willAddChild(view, beforeView) ;
    if (view.willAddToParent) view.willAddToParent(this, beforeView) ;
    
    // set parentView of child
    view.set('parentView', this);
    
    // add to childView's array.
    var idx, childViews = this.get('childViews') ;
    if (childViews.needsClone) this.set(childViews = []);
    idx = (beforeView) ? childViews.indexOf(beforeView) : childViews.length;
    if (idx<0) idx = childViews.length ;
    childViews.insertAt(idx, view) ;
    
    // The DOM will need some fixing up, note this on the view.
    view.parentViewDidChange() ;
    view.layoutDidChange() ;
    
    // notify views
    if (this.didAddChild) this.didAddChild(view, beforeView) ;
    if (view.didAddToParent) view.didAddToParent(this, beforeView) ;
    
    view.endPropertyChanges();
    
    return this ;
  },
  
  /**
    Removes the child view from the parent view.  
    
    @param {SC.View} view
    @returns {SC.View} receiver
  */
  removeChild: function(view) {
    if (!view) return this; // nothing to do
    if (view.parentView !== this) {
      throw "%@.removeChild(%@) must belong to parent".fmt(this,view);
    }
    
    // notify views
    if (view.willRemoveFromParent) view.willRemoveFromParent() ;
    if (this.willRemoveChild) this.willRemoveChild(view) ;
    
    // update parent node
    view.set('parentView', null) ;
    
    // remove view from childViews array.
    var childViews = this.get('childViews') ;
    var idx = childViews.indexOf(view) ;
    if (idx>=0) childViews.removeAt(idx);
    
    // The DOM will need some fixing up, note this on the view.
    view.parentViewDidChange() ;
    
    // notify views
    if (this.didRemoveChild) this.didRemoveChild(view);
    if (view.didRemoveFromParent) view.didRemoveFromParent(this) ;
    
    return this ;
  },
  
  /**
    Removes all children from the parentView.
    
    @returns {SC.View} receiver 
  */
  removeAllChildren: function() {
    var childViews = this.get('childViews'), view ;
    while (view = childViews.objectAt(childViews.get('length')-1)) {
      this.removeChild(view) ;
    }
    return this ;
  },
  
  /** 
    Removes the view from its parentView, if one is found.  Otherwise
    does nothing.
    
    @returns {SC.View} receiver
  */
  removeFromParent: function() {
    var parent = this.get('parentView') ;
    if (parent) parent.removeChild(this) ;
    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) {
    // suspend notifications
    view.beginPropertyChanges();
    oldView.beginPropertyChanges();
    this.beginPropertyChanges();
    
    this.insertBefore(view,oldView).removeChild(oldView) ;
    
    // resume notifications
    this.endPropertyChanges();
    oldView.endPropertyChanges();
    view.endPropertyChanges(); 
    
    return this;
  },
  
  /**
    Replaces the current array of child views with the new array of child 
    views.
    
    @param {Array} views views you want to add
    @returns {SC.View} receiver
  */
  replaceAllChildren: function(views) {
    var len = views.get('length'), idx;
    
    this.beginPropertyChanges();
    this.destroyLayer().removeAllChildren();
    for(idx=0;idx<len;idx++) this.appendChild(views.objectAt(idx));
    this.replaceLayer();
    this.endPropertyChanges();
    
    return this ;
  },
  
  /**
    Appends the specified view to the end of the receivers childViews 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) {
    return this.insertBefore(view, null);
  },
  
  /** 
    This method is called whenever the receiver's parentView has changed.  
    The default implementation of this method marks the view's display 
    location as dirty so that it will update at the end of the run loop.
    
    You will not usually need to override or call this method yourself, though
    if you manually patch the parentView hierarchy for some reason, you should
    call this method to notify the view that it's parentView has changed.
    
    @returns {SC.View} receiver
  */
  parentViewDidChange: function() {
    this.recomputeIsVisibleInWindow() ;
    
    this.set('layerLocationNeedsUpdate', YES) ;
    this.invokeOnce(this.updateLayerLocationIfNeeded) ;
    
    // We also need to iterate down through the view hierarchy and invalidate
    // all our child view's caches for 'pane', since it could have changed.
    //
    // Note:  In theory we could try to avoid this invalidation if we
    //        do this only in cases where we "know" the 'pane' value might
    //        have changed, but those cases are few and far between.
    
    this._invalidatePaneCacheForSelfAndAllChildViews();
    
    return this ;
  }.observes('isVisible'),
  
  /** @private
    We want to cache the 'pane' property, but it's impossible for us to
    declare a dependence on all properties that can affect the value.  (For
    example, if our grandparent gets attached to a new pane, our pane will
    have changed.)  So when there's the potential for the pane changing, we
    need to invalidate the caches for all our child views, and their child
    views, and so on.
  */
  _invalidatePaneCacheForSelfAndAllChildViews: function () {
    this.notifyPropertyChange('pane');
    
    var childViews = this.get('childViews');
    var len = childViews.length ;
    for (var idx=0; idx<len; ++idx) {
      var childView = childViews[idx];
      if (childView._invalidatePaneCacheForSelfAndAllChildViews) childView._invalidatePaneCacheForSelfAndAllChildViews();
    }
  },
  
  // ..........................................................
  // LAYER SUPPORT
  // 
  
  /**
    Returns the current layer for the view.  The layer for a view is only 
    generated when the view first becomes visible in the window and even 
    then it will not be computed until you request this layer property.
    
    If the layer is not actually set on the view itself, then the layer will
    be found by calling this.findLayerInParentLayer().
    
    You can also set the layer by calling set on this property.
    
    @property {DOMElement} the layer
  */
  layer: function(key, value) {
    if (value !== undefined) {
      this._view_layer = value ;
      
    // no layer...attempt to discover it...  
    } else {
      value = this._view_layer;
      if (!value) {
        var parent = this.get('parentView');
        if (parent) parent = parent.get('layer');
        if (parent) {
          this._view_layer = value = this.findLayerInParentLayer(parent);
        }
        parent = null ; // avoid memory leak
      }
    }
    return value ;
  }.property('isVisibleInWindow').cacheable(),
  
  /**
    Get a CoreQuery object for this view's layer, or pass in a selector string
    to get a CoreQuery object for a DOM node nested within this layer.
    
    @param {String} sel a CoreQuery-compatible selector string
    @returns {SC.CoreQuery} the CoreQuery object for the DOM node
  */
  $: function(sel) {
    var ret, layer = this.get('layer') ;
    // note: SC.$([]) returns an empty CoreQuery object.  SC.$() would 
    // return an object selecting the document.
    ret = !layer ? SC.$([]) : (sel === undefined) ? SC.$(layer) : SC.$(sel, layer) ;
    layer = null ; // avoid memory leak
    return ret ;
  },
  
  /**
    Returns the DOM element that should be used to hold child views when they
    are added/remove via DOM manipulation.  The default implementation simply
    returns the layer itself.  You can override this to return a DOM element
    within the layer.
    
    @property {DOMElement} the container layer
  */
  containerLayer: function() {
    return this.get('layer') ;
  }.property('layer').cacheable(), 
  
  /**
    The ID to use when trying to locate the layer in the DOM.  If you do not
    set the layerId explicitly, then the view's GUID will be used instead.
    This ID must be set at the time the view is created.
    
    @property {String}
    @readOnly
  */
  layerId: function() {
    return SC.guidFor(this) ;
  }.property().cacheable(),
  
  /**
    Attempts to discover the layer in the parent layer.  The default 
    implementation looks for an element with an ID of layerId (or the view's
    guid if layerId is null).  You can override this method to provide your
    own form of lookup.  For example, if you want to discover your layer using
    a CSS class name instead of an ID.
    
    @param {DOMElement} parentLayer the parent's DOM layer
    @returns {DOMElement} the discovered layer
  */
  findLayerInParentLayer: function(parentLayer) {
    var layerId = this.get('layerId') ;
    var node, i, ilen,found, elem;
    
    // first, let's try the fast path...
    if(parentLayer.getElementById) elem = parentLayer.getElementById(layerId) ;
    else elem = document.getElementById(layerId) ;
    
    // TODO: use code generation to only really do this check on IE
    if (SC.browser.msie && elem && elem.id !== layerId) elem = null ;
    
    // if browser supports querySelector use that.
    if (!elem && parentLayer.querySelector) {
      // TODO: make querySelector work on all platforms...
      // elem = parentLayer.querySelector('#' + layerId)[0];
    }
    
    // if no element was found the fast way, search down the parentLayer for
    // the element.  This code should not be invoked very often.  Usually a
    // DOM element will be discovered by the first method above.
    // This code uses a BFS algorithm as is expected to find the layer right 
    // below the parent.
    if (!elem) {
      elem = parentLayer.firstChild ;
      var q=[];
      q.push(parentLayer);
      while(q.length!==0){
        node=q[0];
        q.shift();
        if(node.id===layerId){
          found=true;
          elem=node;
          break;
        } 
        for(i=0, ilen=node.childNodes.length; i<ilen; i++){
          q.push(node.childNodes[i]);
        }
      }
      if(!found) elem=null;  
    }
    
    return elem;
  },
  
  /**
    This method is invoked whenever a display property changes.  It will set 
    the layerNeedsUpdate method to YES.  If you need to perform additional
    setup whenever the display changes, you can override this method as well.
    
    @returns {SC.View} receiver
  */
  displayDidChange: function() {
    this.set('layerNeedsUpdate', YES) ;
    return this;
  },
  
  /**
    Setting this property to YES will cause the updateLayerIfNeeded method to 
    be invoked at the end of the runloop.  You can also force a view to update
    sooner by calling updateLayerIfNeeded() directly.  The method will update 
    the layer only if this property is YES.
    
    @property {Boolean}
    @test in updateLayer
  */
  layerNeedsUpdate: NO,
  
  /** @private
    Schedules the updateLayerIfNeeded method to run at the end of the runloop
    if layerNeedsUpdate is set to YES.
  */  
  _view_layerNeedsUpdateDidChange: function() {
    if (this.get('layerNeedsUpdate')) {
      this.invokeOnce(this.updateLayerIfNeeded) ;
    }
  }.observes('layerNeedsUpdate'),
  
  /**
    Updates the layer only if the view is visible onscreen and if 
    layerNeedsUpdate is set to YES.  Normally you will not invoke this method
    directly.  Instead you set the layerNeedsUpdate property to YES and this
    method will be called once at the end of the runloop.
    
    If you need to update view's layer sooner than the end of the runloop, you
    can call this method directly.  If your view is not visible in the window
    but you want it to update anyway, then call this method, passing YES for
    the 'force' parameter.
    
    You should not override this method.  Instead override updateLayer() or
    render().
    
    @param {Boolean} isVisible if true assume view is visible even if it is not.
    @returns {SC.View} receiver
    @test in updateLayer
  */
  updateLayerIfNeeded: function() {
    var viz = this.get('isVisibleInWindow') ;
    if ((viz || this._needsVisibiltyChange) && this.get('layerNeedsUpdate')) {
      this._needsVisibiltyChange = NO ;
      // only update a layer if it already exists
      if (this.get('layer')) {
        this.beginPropertyChanges() ;
        this.set('layerNeedsUpdate', NO) ;
        this.updateLayer() ;
        this.endPropertyChanges() ;
      }
    }
    else this.set('layerNeedsUpdate', NO) ;
    return this ;
  },
  
  /**
    This is the core method invoked to update a view layer whenever it has 
    changed.  This method simply creates a render context focused on the 
    layer element and then calls your render() method.
    
    You will not usually call or override this method directly.  Instead you
    should set the layerNeedsUpdate property to YES to cause this method to
    run at the end of the run loop, or you can call updateLayerIfNeeded()
    to force the layer to update immediately.  
    
    Instead of overriding this method, consider overidding the render() method
    instead, which is called both when creating and updating a layer.  If you
    do not want your render() method called when updating a layer, then you
    should override this method instead.
    
    @returns {SC.View} receiver 
  */
  updateLayer: function() {
    var context = this.renderContext(this.get('layer')) ;
    this.prepareContext(context, NO) ;
    context.update() ;
    if (this.didUpdateLayer) this.didUpdateLayer(); // call to update DOM
    if(this.designer && this.designer.viewDidUpdateLayer) this.designer.viewDidUpdateLayer(); //let the designer know
    return this ;
  },
  
  /**
    Creates a new renderContext with the passed tagName or element.  You
    can override this method to provide further customization to the context
    if needed.  Normally you will not need to call or override this method.
    
    @returns {SC.RenderContext}
  */
  renderContext: function(tagNameOrElement) {
    return SC.RenderContext(tagNameOrElement) ;
  },
  
  /**
    Creates the layer by creating a renderContext and invoking the view's
    render() method.  This will only create the layer if the layer does not
    already exist.
    
    When you create a layer, it is expected that your render() method will
    also render the HTML for all child views as well.  This method will 
    notify the view along with any of its childViews that its layer has been
    created.
    
    @returns {SC.View} receiver
  */
  createLayer: function() {
    if (this.get('layer')) return this ; // nothing to do
    
    var context = this.renderContext(this.get('tagName')) ;
    
    // now prepare the content like normal.
    this.prepareContext(context, YES) ;
    this.set('layer', context.element()) ;
    
    // now notify the view and its child views..
    this._notifyDidCreateLayer() ;
    
    return this ;
  },
  
  /** @private - 
    Invokes the receivers didCreateLayer() method if it exists and then
    invokes the same on all child views.
  */
  _notifyDidCreateLayer: function() {
    if (this.didCreateLayer) this.didCreateLayer() ;
    var mixins = this.didCreateLayerMixin, len, idx ;
    if (mixins) {
      len = mixins.length ;
      for (idx=0; idx<len; ++idx) mixins[idx].call(this) ;
    }
    
    var childViews = this.get('childViews') ;
    len = childViews.length ;
    for (idx=0; idx<len; ++idx) {
      if (!childViews[idx]) continue;
      childViews[idx]._notifyDidCreateLayer() ;
    }
  },
  
  /**
    Destroys any existing layer along with the layer for any child views as 
    well.  If the view does not currently have a layer, then this method will
    do nothing.
    
    If you implement willDestroyLayer() on your view or if any mixins 
    implement willDestroLayerMixin(), then this method will be invoked on your
    view before your layer is destroyed to give you a chance to clean up any
    event handlers, etc.
    
    If you write a willDestroyLayer() handler, you can assume that your 
    didCreateLayer() handler was called earlier for the same layer.
    
    Normally you will not call or override this method yourself, but you may
    want to implement the above callbacks when it is run.
    
    @returns {SC.View} receiver
  */
  destroyLayer: function() {
    var layer = this.get('layer') ;
    if (layer) {
      // Now notify the view and its child views.  It will also set the
      // layer property to null.
      this._notifyWillDestroyLayer() ;
      
      // do final cleanup
      if (layer.parentNode) layer.parentNode.removeChild(layer) ;
      layer = null ;
    }
    return this ;
  },
  
  /**
    Destroys and recreates the current layer.  This can be more efficient than
    modifying individual child views.
    
    @returns {SC.View} receiver
  */
  replaceLayer: function() {
    this.destroyLayer();
    this.set('layerLocationNeedsUpdate', YES) ;
    this.invokeOnce(this.updateLayerLocationIfNeeded) ;
  },
    
  /** @private - 
    Invokes willDestroyLayer() on view and child views.  Then sets layer to
    null for receiver.
  */
  _notifyWillDestroyLayer: function() {
    if (this.willDestroyLayer) this.willDestroyLayer() ;
    var mixins = this.willDestroyLayerMixin, len, idx ;
    if (mixins) {
      len = mixins.length ;
      for (idx=0; idx<len; ++idx) mixins[idx].call(this) ;
    }
    
    var childViews = this.get('childViews') ;
    len = childViews.length ;
    for (idx=0; idx<len; ++idx) childViews[idx]._notifyWillDestroyLayer() ;
    
    this.set('layer', null) ;
  },
  
  /**
    Invoked by createLayer() and updateLayer() to actually render a context.
    This method calls the render() method on your view along with any 
    renderMixin() methods supplied by mixins you might have added.
    
    You should not override this method directly.  However, you might call
    this method if you choose to override updateLayer() or createLayer().
    
    @param {SC.RenderContext} context the render context
    @param {Boolean} firstTime YES if this is creating a layer
    @returns {void}
  */
  prepareContext: function(context, firstTime) {
    var mixins, len, idx, layerId, bgcolor, cursor ;
    
    // do some initial setup only needed at create time.
    if (firstTime) {
      // TODO: seems like things will break later if SC.guidFor(this) is used
      
      layerId = this.layerId ? this.get('layerId') : SC.guidFor(this) ;
      context.id(layerId).classNames(this.get('classNames'), YES) ;
      this.renderLayout(context, firstTime) ;
    }else{
      context.resetClassNames();
      context.classNames(this.get('classNames'), YES);  
    }
    
    // do some standard setup...
    if (this.get('isTextSelectable')) context.addClass('allow-select') ;
    if (!this.get('isEnabled')) context.addClass('disabled') ;
    if (!this.get('isVisible')) context.addClass('hidden') ;
    if (this.get('isFirstResponder')) context.addClass('focus');
    
    bgcolor = this.get('backgroundColor');
    if (bgcolor) context.addStyle('backgroundColor', bgcolor);
    
    cursor = this.get('cursor') ;
    if (cursor) context.addClass(cursor.get('className')) ;
    
    this.beginPropertyChanges() ;
    this.set('layerNeedsUpdate', NO) ;
    this.render(context, firstTime) ;
    if (mixins = this.renderMixin) {
      len = mixins.length;
      for(idx=0; idx<len; ++idx) mixins[idx].call(this, context, firstTime) ;
    }
    this.endPropertyChanges() ;
  },
  
  /**
    Your render method should invoke this method to render any child views,
    especially if this is the first time the view will be rendered.  This will
    walk down the childView chain, rendering all of the children in a nested
    way.
    
    @param {SC.RenderContext} context the context
    @param {Boolean} firstName true if the layer is being created
    @returns {SC.RenderContext} the render context
    @test in render
  */
  renderChildViews: function(context, firstTime) {
    var cv = this.get('childViews'), len = cv.length, idx, view ;
    for (idx=0; idx<len; ++idx) {
      view = cv[idx] ;
      if (!view) continue;
      context = context.begin(view.get('tagName')) ;
      view.prepareContext(context, firstTime) ;
      context = context.end() ;
    }
    return context ;  
  },
  
  /**
    Invoked whenever your view needs to be rendered, including when the view's
    layer is first created and any time in the future when it needs to be 
    updated.
    
    You will normally override this method in your subclassed views to 
    provide whatever drawing functionality you will need in order to 
    render your content.
    
    You can use the passed firstTime property to determine whether or not 
    you need to completely re-render the view or only update the surrounding
    HTML.  
    
    The default implementation of this method simply calls renderChildViews()
    if this is the first time you are rendering, or null otherwise.
    
    @param {SC.RenderContext} context the render context
    @param {Boolean} firstTime YES if this is creating a layer
    @returns {void}
  */
  render: function(context, firstTime) {
    if (firstTime) this.renderChildViews(context, firstTime) ;
  },
  
  // ..........................................................
  // STANDARD RENDER PROPERTIES
  // 
  
  /** 
    Tag name for the view's outer element.  The tag name is only used when
    a layer is first created.  If you change the tagName for an element, you
    must destroy and recreate the view layer.
    
    @property {String}
  */
  tagName: 'div',
  
  /**
    Standard CSS class names to apply to the view's outer element.  This 
    property automatically inherits any class names defined by the view's
    superclasses as well.  
    
    @property {Array}
  */
  classNames: ['sc-view'],
  
  /**
    Tool tip property that will be set to the title attribute on the HTML 
    rendered element.
    
    @property {String}
  */
  toolTip: null,

  /**
    Determines if the user can select text within the view.  Normally this is
    set to NO to disable text selection.  You should set this to YES if you
    are creating a view that includes editable text.  Otherwise, settings this
    to YES will probably make your controls harder to use and it is not 
    recommended.
    
    @property {Boolean}
    @readOnly
  */
  isTextSelectable: NO,
  
  /** 
    You can set this array to include any properties that should immediately
    invalidate the display.  The display will be automatically invalidated
    when one of these properties change.
    
    @property {Array}
    @readOnly
  */
  displayProperties: ['isFirstResponder', 'isVisible'],
  
  /**
    You can set this to an SC.Cursor instance; it's className will 
    automatically be added to the layer's classNames. The cursor is only used 
    when a layer is first created.  If you change the cursor for an element, 
    you must destroy and recreate the view layer.
    
    @property {SC.Cursor}
  */
  cursor: null,
  
  // ..........................................................
  // LAYER LOCATION
  // 
  
  /**
    Set to YES when the view's layer location is dirty.  You can call 
    updateLayerLocationIfNeeded() to clear this flag if it is set.
    
    @property {Boolean}
  */
  layerLocationNeedsUpdate: NO,
  
  /**
    Calls updateLayerLocation(), but only if the view's layer location
    currently needs to be updated.  This method is called automatically at 
    the end of a run loop if you have called parentViewDidChange() at some
    point.
    
    @property {Boolean} force This property is ignored.
    @returns {SC.View} receiver 
    @test in updateLayerLocation
  */
  updateLayerLocationIfNeeded: function(force) {
    if (this.get('layerLocationNeedsUpdate')) {
      this.set('layerLocationNeedsUpdate', NO) ;
      this.updateLayerLocation() ;
    }
    return this ;
  },
  
  /**
    This method is called when a view changes its location in the view 
    hierarchy.  This method will update the underlying DOM-location of the 
    layer so that it reflects the new location.
    
    @returns {SC.View} receiver
  */
  updateLayerLocation: function() {
    // collect some useful value
    // if there is no node for some reason, just exit
    var node = this.get('layer') ;
    var parentView = this.get('parentView') ;
    var parentNode = parentView ? parentView.get('containerLayer') : null ;
    
    // remove node from current parentNode if the node does not match the new 
    // parent node.
    if (node && node.parentNode && node.parentNode !== parentNode) {
      node.parentNode.removeChild(node);
    }
    
    // CASE 1: no new parentView.  just remove from parent (above).
    if (!parentView) {
      if (node && node.parentNode) node.parentNode.removeChild(node);
      
    // CASE 2: parentView has no layer, view has layer.  destroy layer
    // CASE 3: parentView has no layer, view has no layer, nothing to do
    } else if (!parentNode) {
      if (node) {
        if (node.parentNode) node.parentNode.removeChild(node);
        this.destroyLayer();
      }
      
    // CASE 4: parentView has layer, view has no layer.  create layer & add
    // CASE 5: parentView has layer, view has layer.  move layer
    } else {
      if (!node) {
        this.createLayer() ;
        node = this.get('layer') ;
      }
      
      var siblings = parentView.get('childViews') ;
      var nextView = siblings.objectAt(siblings.indexOf(this)+1) ;
      var nextNode = (nextView) ? nextView.get('layer') : null ;
      
      // before we add to parent node, make sure that the nextNode exists...
      if (nextView && (!nextNode || nextNode.parentNode!==parentNode)) {
        nextView.updateLayerLocationIfNeeded() ;
        nextNode = nextView.get('layer') ;
      }
      
      // add to parentNode if needed.  If we do add, then also notify view
      // that its parentView has resized since joining a parentView has the
      // same effect.
      if ((node.parentNode!==parentNode) || (node.nextSibling!==nextNode)) {
        parentNode.insertBefore(node, nextNode) ;
        if (this.parentViewDidResize) this.parentViewDidResize() ;
      }
    }
    
    parentNode = parentView = node = null ; // avoid memory leaks
    return this ; 
  },
  
  // .......................................................
  // SC.RESPONDER SUPPORT
  //
  
  /** @property
    The nextResponder is usually the parentView.
  */
  nextResponder: function() {
    return this.get('parentView') ;
  }.property('parentView').cacheable(),

  
  /** @property
    Set to YES if your view is willing to accept first responder status.  This 
    is used when calculcating key responder loop.
  */
  acceptsFirstResponder: NO,

  // ..........................................................
  // KEY RESPONDER
  // 
  
  /** @property
    YES if the view is currently first responder and the pane the view belongs 
    to is also key pane.  While this property is set, you should expect to 
    receive keyboard events.
  */
  isKeyResponder: NO,

  /**
    This method is invoked just before you lost the key responder status.  The passed view is the view that is about to gain keyResponder status.  This gives you a chance to do any early setup.
    
    Remember that you can gain/lose key responder status either because another view in the same pane is becoming first responder or because another pane is about to become key.
    
    @param {SC.Responder} responder
  */
  willLoseKeyResponderTo: function(responder) {},
  
  /**
    This method is invoked just before you become the key responder.  The passed view is the view that is about to lose keyResponder status.  You can use this to do any setup before the view changes.
    
    Remember that you can gain/lose key responder status either because another view in the same pane is becoming first responder or because another pane is about to become key.
    
    @param {SC.Responder} responder
  */
  willBecomeKeyResponderFrom: function(responder) {},
  
  /**
    Invokved just after the responder loses key responder status.
  */
  didLoseKeyResponderTo: function(responder) {},
  
  /**
    Invoked just after the responder gains key responder status.
  */
  didBecomeKeyResponderFrom: function(responder) {},
    
  /**
    This method will process a key input event, attempting to convert it to an appropriate action method and sending it up the responder chain.  The event is converted using the SC.KEY_BINDINGS hash, which maps key events into method names.  If no key binding is found, then the key event will be passed along using an insertText() method.
    
    @param {SC.Event} event
    @returns {Object} object that handled event, if any
  */
  interpretKeyEvents: function(event) {
    var codes = event.commandCodes(), cmd = codes[0], chr = codes[1], ret;

    if (!cmd && !chr) return null ;  //nothing to do.

    // if this is a command key, try to do something about it.
    if (cmd) {
      var methodName = SC.MODIFIED_KEY_BINDINGS[cmd] || SC.BASE_KEY_BINDINGS[cmd.match(/[^_]+$/)[0]];
      if (methodName) {
        var target = this, pane = this.get('pane'), handler = null;
        while(target && !(handler = target.tryToPerform(methodName, event))){
          target = (target===pane)? null: target.get('nextResponder') ;
        }
        return handler ;
      }
    } 

    if (chr && this.respondsTo('insertText')) {
      // if we haven't returned yet and there is plain text, then do an insert 
      // of the text.  Since this is not an action, do not send it up the 
      // responder chain.
      ret = this.insertText(chr, event);
      return ret ? (ret===YES ? this : ret) : null ; // map YES|NO => this|nil
    }

    return null ; //nothing to do.
  },
  
  /**
    This method is invoked by interpretKeyEvents() when you receive a key event matching some plain text.  You can use this to actually insert the text into your application, if needed.
    
    @param {SC.Event} event
    @returns {Object} receiver or object that handled event
  */
  insertText: function(chr) {
    return NO ;
  },
    
  /**
    Recursively travels down the view hierarchy looking for a view that 
    implements the key equivalent (returning to YES to indicate it handled 
    the event).  You can override this method to handle specific key 
    equivalents yourself.
    
    The keystring is a string description of the key combination pressed.
    The evt is the event itself. If you handle the equivalent, return YES.
    Otherwise, you should just return sc_super.
    
    @param {String} keystring
    @param {SC.Event} evt
    @returns {Boolean}
  */
  performKeyEquivalent: function(keystring, evt) {
    var ret = NO,
        childViews = this.get('childViews'),
        len = childViews.length,
        idx = -1 ;
    while (!ret && (++idx < len)) {
      ret = childViews[idx].performKeyEquivalent(keystring, evt) ;
    }
    return ret ;
  },
  
  /**
    Optionally points to the next key view that should gain focus when tabbing
    through an interface.  If this is not set, then the next key view will
    be set automatically to the next child.
  */
  nextKeyView: null,
  
  /**
    Computes the next valid key view, possibly returning the receiver or null.
    This is the next key view that acceptsFirstResponder.
    
    @property
    @type SC.View
  */
  nextValidKeyView: function() {
    var seen = SC.CoreSet.create(),
        ret  = this._computeNextValidKeyView(seen);
    seen.destroy();
    return ret ;
  }.property('nextKeyView'),
  
  _computeNextValidKeyView: function(seen) {  
    var ret = this.get('nextKeyView'),
        pv, cv, idx;

    seen.add(this); // avoid cycles
    
    // find next sibling
    if (!ret) {
      pv = this.get('parentView');
      cv = pv ? pv.get('childViews') : null;
      idx = cv ? cv.indexOf(this) : -1 ;
      
      // get next child if possible
      if (idx<0) ret = null;
      else if (idx+1 >= cv.get('length')) ret = cv.objectAt(0);
      else ret = cv.objectAt(idx+1);
    }
    
    // if next view does not accept responder then get nextValidKeyView...
    if (ret && !ret.get('acceptsFirstResponder')) {
      if (seen.contains(ret)) ret = null;
      else ret = ret._computeNextValidKeyView(seen);
    }
    
    return ret ;
  },
  
  /**
    Optionally points to the previous key view that should gain focus when
    tabbing through the interface. If this is not set then the previous 
    key view will be set automatically to the previous child.
  */
  previousKeyView: null,

  /**
    Computes the previous valid key view, possibly returning the receiver or 
    null.  This is the previous key view that acceptsFirstResponder.
    
    @property
    @type SC.View
  */
  previousValidKeyView: function() {
    var seen = SC.CoreSet.create(),
        ret  = this._computePreviousValidKeyView(seen);
    seen.destroy();
    return ret ;
  }.property('previousKeyView'),
  
  _computePreviousValidKeyView: function(seen) {  
    var ret = this.get('previousKeyView'),
        pv, cv, idx;

    seen.add(this); // avoid cycles
    
    // find previous sibling
    if (!ret) {
      pv = this.get('parentView');
      cv = pv ? pv.get('childViews') : null;
      idx = cv ? cv.indexOf(this) : -1 ;
      
      // get next child if possible
      if (idx<0) ret = null;
      else if (idx > 0) ret = cv.objectAt(idx-1);
      else ret = cv.objectAt(cv.get('length')-1);
    }
    
    // if next view does not accept responder then get nextValidKeyView...
    if (ret && !ret.get('acceptsFirstResponder')) {
      if (seen.contains(ret)) ret = null;
      else ret = ret._computePreviousValidKeyView(seen);
    }
    
    return ret ;
  },
  
  // .......................................................
  // CORE DISPLAY METHODS
  //
  
  /** @private 
    Setup a view, but do not finish waking it up. 
    - configure childViews
    - generate DOM + plug in outlets/childViews unless rootElement is defined
    - register the view with the global views hash, which is used for mgmt
  */
  init: function() {
    var parentView, path, root, idx, len, lp, dp ;
    
    arguments.callee.base.apply(this,arguments) ;
    
    // register for event handling now if we're not a materialized view
    // (materialized views register themselves as needed)
    if (!this.get('isMaterialized')) {
      SC.View.views[this.get('layerId')] = this ;
    }
    
    var childViews = this.get('childViews');
    
    // setup child views.  be sure to clone the child views array first
    this.childViews = childViews ? childViews.slice() : [] ;
    this.createChildViews() ; // setup child Views
    
    // register display property observers ..
    // TODO: Optimize into class setup 
    dp = this.get('displayProperties') ; 
    idx = dp.length ;
    while (--idx >= 0) {
      this.addObserver(dp[idx], this, this.displayDidChange) ;
    }
    
    // register for drags
    if (this.get('isDropTarget')) SC.Drag.addDropTarget(this) ;
    
    // register scroll views for autoscroll during drags
    if (this.get('isScrollable')) SC.Drag.addScrollableView(this) ;
  },
  
  /**
    Wakes up the view. The default implementation immediately syncs any 
    bindings, which may cause the view to need its display updated. You 
    can override this method to perform any additional setup. Be sure to 
    call sc_super to setup bindings and to call awake on childViews.
    
    It is best to awake a view before you add it to the DOM.  This way when
    the DOM is generated, it will have the correct initial values and will
    not require any additional setup.
    
    @returns {void}
  */
  awake: function() {
    arguments.callee.base.apply(this,arguments);
    var childViews = this.get('childViews'), len = childViews.length, idx ;
    for (idx=0; idx<len; ++idx) {
      if (!childViews[idx]) continue ;
      childViews[idx].awake() ;
    } 
  },
    
  /** 
    You must call this method on a view to destroy the view (and all of its 
    child views). This will remove the view from any parent node, then make 
    sure that the DOM element managed by the view can be released by the 
    memory manager.
  */
  destroy: function() {
    if (this.get('isDestroyed')) return this; // nothing to do
     
    arguments.callee.base.apply(this,arguments);
    
    // remove from parent if found
    this.removeFromParent() ;
    this._destroy(); // core destroy method
    
    // unregister for drags
    if (this.get('isDropTarget')) SC.Drag.removeDropTarget(this) ;
    
    // unregister for autoscroll during drags
    if (this.get('isScrollable')) SC.Drag.removeScrollableView(this) ;
    return this; // done with cleanup
  },
  
  _destroy: function() {
    if (this.get('isDestroyed')) return this ; // nothing to do
    
    // destroy the layer -- this will avoid each child view destroying 
    // the layer over and over again...
    this.destroyLayer() ; 
    
    // first destroy any children.
    var childViews = this.get('childViews'), len = childViews.length, idx ;
    if (len) {
      childViews = childViews.slice() ;
      for (idx=0; idx<len; ++idx) childViews[idx]._destroy() ;
    }
    
    // next remove view from global hash
    delete SC.View.views[this.get('layerId')] ;
    delete this._CQ ; 
    delete this.page ;
    
    // mark as destroyed so we don't do this again
    this.set('isDestroyed', YES) ;
    return this ;
  },
  
  /** 
    This method is called when your view is first created to setup any  child 
    views that are already defined on your class.  If any are found, it will 
    instantiate them for you.
    
    The default implementation of this method simply steps through your 
    childViews array, which is expects to either be empty or to contain View 
    designs that can be instantiated
    
    Alternatively, you can implement this method yourself in your own 
    subclasses to look for views defined on specific properties and then build
     a childViews array yourself.
    
    Note that when you implement this method yourself, you should never 
    instantiate views directly.  Instead, you should use 
    this.createChildView() method instead.  This method can be much faster in 
    a production environment than creating views yourself.
    
    @returns {SC.View} receiver
  */
  createChildViews: function() {
    var childViews = this.get('childViews'), 
        len        = childViews.length, 
        idx, key, views, view ;
    
    this.beginPropertyChanges() ;
    
    // swap the array
    for (idx=0; idx<len; ++idx) {
      if (key = (view = childViews[idx])) {

        // is this is a key name, lookup view class
        if (typeof key === SC.T_STRING) {
          view = this[key];
        } else key = null ;
        
        if (!view) {
          console.error ("No view with name "+key+" has been found in "+this.toString());
          // skip this one.
          continue;
        }
        
        if (view.isClass) {
          view = this.createChildView(view) ; // instantiate if needed
          if (key) this[key] = view ; // save on key name if passed
        } 
      }
      childViews[idx] = view;
    }
    
    this.endPropertyChanges() ;
    return this ;
  },
  
  /**
    Instantiates a view to be added to the childViews array during view 
    initialization. You generally will not call this method directly unless 
    you are overriding createChildViews(). Note that this method will 
    automatically configure the correct settings on the new view instance to 
    act as a child of the parent.
    
    @param {Class} viewClass
    @param {Hash} attrs optional attributes to add
    @returns {SC.View} new instance
    @test in createChildViews
  */
  createChildView: function(view, attrs) {
    // attrs should always exist...
    if (!attrs) attrs = {} ;
    attrs.owner = attrs.parentView = this ;
    attrs.isVisibleInWindow = this.get('isVisibleInWindow');
    if (!attrs.page) attrs.page = this.page ;
    
    // Now add this to the attributes and create.
    view = view.create(attrs) ;
    return view ;
  },
  
  // ...........................................
  // LAYOUT
  //
  
  /** 
    This convenience method will take the current layout, apply any changes
    you pass and set it again.  It is more convenient than having to do this
    yourself sometimes.
    
    You can pass just a key/value pair or a hash with several pairs.  You can
    also pass a null value to delete a property.
    
    This method will avoid actually setting the layout if the value you pass
    does not edit the layout.
    
    @param {String|Hash} key
    @param {Object} value
    @returns {SC.View} receiver
  */
  adjust: function(key, value) {
    var layout = SC.clone(this.get('layout')), didChange = NO, cur ;
    
    if (key === undefined) return this ; // nothing to do.
    
    // handle string case
    if (SC.typeOf(key) === SC.T_STRING) {
      cur = layout[key] ;
      if (SC.none(value)) {
        if (cur !== undefined) didChange = YES ;
        delete layout[key] ;
      } else {
        if (cur !== value) didChange = YES ;
        layout[key] = value ;
      }
      
    // handle hash -- do it this way to avoid creating memory unless needed
    } else {
      var hash = key;
      for(key in hash) {
        if (!hash.hasOwnProperty(key)) continue;
        value = hash[key] ;
        cur = layout[key] ;
        
        if (value === null) {
          if (cur !== undefined) didChange = YES ;
          delete layout[key] ;
        } else if (value !== undefined) {
          if (cur !== value) didChange = YES ;
          layout[key] = value ;
        }
      }
    }
    
    // now set adjusted layout
    if (didChange) this.set('layout', layout) ;
    
    return this ;
  },
  
  /** 
    The layout describes how you want your view to be positions on the 
    screen.  You can define the following properties:
    
    - left: the left edge
    - top: the top edge
    - right: the right edge
    - bottom: the bottom edge
    - height: the height
    - width: the width
    - centerX: an offset from center X 
    - centerY: an offset from center Y
    - minWidth: a minimum width
    - minHeight: a minimum height
    - maxWidth: a maximum width
    - maxHeight: a maximum height
    
    Note that you can only use certain combinations to set layout.  For 
    example, you may set left/right or left/width, but not left/width/right,
    since that combination doesn't make sense.
    
    Likewise, you may set a minWidth/minHeight, or maxWidth/maxHeight, but
    if you also set the width/height explicitly, then those constraints won't
    matter as much.
    
    Layout is designed to maximize reliance on the browser's rendering 
    engine to keep your app up to date.
    
    @test in layoutStyle
  */
  layout: { top: 0, left: 0, bottom: 0, right: 0 },
  
  /**
    Converts a frame from the receiver's offset to the target offset.  Both
    the receiver and the target must belong to the same pane.  If you pass
    null, the conversion will be to the pane level.
    
    Note that the context of a view's frame is the view's parent frame.  In
    other words, if you want to convert the frame of your view to the global
    frame, then you should do:
    
    {{{
      var pv = this.get('parentView'), frame = this.get('frame');
      var newFrame = pv ? pv.convertFrameToView(frame, null) : frame;
    }}}
    
    @param {Rect} frame the source frame
    @param {SC.View} targetView the target view to convert to
    @returns {Rect} converted frame
    @test in converFrames
  */
  convertFrameToView: function(frame, targetView) {
    var myX=0, myY=0, targetX=0, targetY=0, view = this, f ;
    
    if (this.get('useStaticLayout')) {
      throw "convertFrameToView is not available with static layout";
    }
    
    // walk up this side
    while (view) {
      f = view.get('frame'); myX += f.x; myY += f.y ;
      view = view.get('layoutView') ; 
    }
    
    // walk up other size
    if (targetView) {
      view = targetView ;
      while (view) {
        f = view.get('frame'); targetX += f.x; targetY += f.y ;
        view = view.get('layoutView') ; 
      }
    }
    
    // now we can figure how to translate the origin.
    myX = frame.x + myX - targetX ;
    myY = frame.y + myY - targetY ;
    return { x: myX, y: myY, width: frame.width, height: frame.height } ;
  },
  
  /**
    Converts a frame offset in the coordinates of another view system to the 
    reciever's view.
    
    Note that the convext of a view's frame is relative to the view's 
    parentFrame.  For example, if you want to convert the frame of view that
    belongs to another view to the receiver's frame you would do:
    
    {{{
      var frame = view.get('frame');
      var newFrame = this.convertFrameFromView(frame, view.get('parentView'));
    }}}
    
    @param {Rect} frame the source frame
    @param {SC.View} targetView the target view to convert to
    @returns {Rect} converted frame
    @test in converFrames
  */
  convertFrameFromView: function(frame, targetView) {
    var myX=0, myY=0, targetX=0, targetY=0, view = this, next, f ;
    
    if (this.get('useStaticLayout')) {
      throw "convertFrameToView is not available with static layout";
    }
    
    // walk up this side
    while (view && view.get('frame')) {
      f = view.get('frame'); myX += f.x; myY += f.y ;
      view = view.get('parentView') ; 
    }
    
    // walk up other size
    if (targetView) {
      view = targetView ;
      while(view) {
        f = view.get('frame'); targetX += f.x; targetY += f.y ;
        view = view.get('parentView') ; 
      }
    }
    
    // now we can figure how to translate the origin.
    myX = frame.x - myX + targetX ;
    myY = frame.y - myY + targetY ;
    return { x: myX, y: myY, width: frame.width, height: frame.height } ;
  },
  
  /**
    Attempt to scroll the view to visible.  This will walk up the parent
    view hierarchy looking looking for a scrollable view.  It will then 
    call scrollToVisible() on it.
    
    Returns YES if an actual scroll took place, no otherwise.
    
    @returns {Boolean} 
  */
  scrollToVisible: function() {
    var pv = this.get('parentView');
    while(pv && !pv.get('isScrollable')) pv = pv.get('parentView');
    
    // found view, first make it scroll itself visible then scroll this.
    if (pv) {
      pv.scrollToVisible();
      return pv.scrollToVisible(this);
    } else return NO ;
  },
  
  /**
    Frame describes the current bounding rect for your view.  This is always
    measured from the top-left corner of the parent view.
    
    @property {Rect}
    @test in layoutStyle
  */
  frame: function() {
    return this.computeFrameWithParentFrame(null) ;
  }.property('layout', 'useStaticLayout').cacheable(),
  
  /**
    Computes what the frame of this view would be if the parent were resized
    to the passed dimensions.  You can use this method to project the size of
    a frame based on the resize behavior of the parent.
    
    This method is used especially by the scroll view to automatically 
    calculate when scrollviews should be visible.
  
    Passing null for the parent dimensions will use the actual current 
    parent dimensions.  This is the same method used to calculate the current
    frame when it changes.
    
    @param {Rect} pdim the projected parent dimensions
    @returns {Rect} the computed frame
  */
  computeFrameWithParentFrame: function(pdim) {
    var layout = this.get('layout') ;
    var f = {} , error, layer, AUTO = SC.LAYOUT_AUTO;
    var stLayout = this.get('useStaticLayout') ;
    
    if (layout.width !== undefined &&
        layout.width === SC.LAYOUT_AUTO &&
        stLayout !== undefined && !stLayout) {
     error = SC.Error.desc("%@.layout() you cannot use width:auto if staticLayout is disabled".fmt(this),"%@".fmt(this), -1) ;
     console.error(error.toString()) ;
     throw error ;
    }
    
    if (layout.height !== undefined &&
        layout.height === SC.LAYOUT_AUTO &&
        stLayout !== undefined && !stLayout) {
      error = SC.Error.desc("%@.layout() you cannot use height:auto if staticLayout is disabled".fmt(this),"%@".fmt(this), -1) ;
      console.error(error.toString())  ;
      throw error ;
    }
    
    if (stLayout) return null; // can't compute
    
    // handle left aligned and left/right 
    if (!SC.none(layout.left)) {
      f.x = Math.floor(layout.left) ;
      if (layout.width !== undefined) {
        if(layout.width === AUTO) f.width = AUTO ;
        else f.width = Math.floor(layout.width) ;
      } else { // better have layout.right!
        if (!pdim) pdim = this.computeParentDimensions(layout) ;
        f.width = Math.floor(pdim.width - f.x - (layout.right || 0)) ;
      }
      
    // handle right aligned
    } else if (!SC.none(layout.right)) {
      if (!pdim) pdim = this.computeParentDimensions(layout) ;
      if (SC.none(layout.width)) {
        f.width = pdim.width - layout.right ;
        f.x = 0 ;
      } else {
        if(layout.width === AUTO) f.width = AUTO ;
        else f.width = Math.floor(layout.width || 0) ;
        f.x = Math.floor(pdim.width - layout.right - f.width) ;
      }
      
    // handle centered
    } else if (!SC.none(layout.centerX)) {
      if (!pdim) pdim = this.computeParentDimensions(layout) ; 
      if(layout.width === AUTO) f.width = AUTO ;
      else f.width = Math.floor(layout.width || 0) ;
      f.x = Math.floor((pdim.width - f.width)/2 + layout.centerX) ;
    } else {
      f.x = 0 ; // fallback
      if (SC.none(layout.width)) {
        if (!pdim) pdim = this.computeParentDimensions(layout) ;
        f.width = Math.floor(pdim.width) ;
      } else {
        if(layout.width === AUTO) f.width = AUTO ;
        else f.width = Math.floor(layout.width || 0) ;
      }
    }
    
    // handle top aligned and top/bottom 
    if (!SC.none(layout.top)) {
      f.y = Math.floor(layout.top) ;
      if (layout.height !== undefined) {
        if(layout.height === AUTO) f.height = AUTO ;
        else f.height = Math.floor(layout.height) ;
      } else { // better have layout.bottm!
        if (!pdim) pdim = this.computeParentDimensions(layout) ;
        f.height = Math.floor(pdim.height - f.y - (layout.bottom || 0)) ;
      }
      
    // handle bottom aligned
    } else if (!SC.none(layout.bottom)) {
      if (!pdim) pdim = this.computeParentDimensions(layout) ;
      if (SC.none(layout.height)) {
        f.height = pdim.height - layout.bottom ;
        f.y = 0 ;
      } else {
        if(layout.height === AUTO) f.height = AUTO ;
        else f.height = Math.floor(layout.height || 0) ;
        f.y = Math.floor(pdim.height - layout.bottom - f.height) ;
      }
      
    // handle centered
    } else if (!SC.none(layout.centerY)) {
      if (!pdim) pdim = this.computeParentDimensions(layout) ; 
      if(layout.height === AUTO) f.height = AUTO ;
      else f.height = Math.floor(layout.height || 0) ;
      f.y = Math.floor((pdim.height - f.height)/2 + layout.centerY) ;
      
    // fallback
    } else {
      f.y = 0 ; // fallback
      if (SC.none(layout.height)) {
        if (!pdim) pdim = this.computeParentDimensions(layout) ; 
        f.height = Math.floor(pdim.height) ;
      } else {
        if(layout.height === AUTO) f.height = AUTO ;
        else f.height = Math.floor(layout.height || 0) ;
      }
    }
    
    // if width or height were set to auto and we have a layer, try lookup
    if (f.height === AUTO || f.width === AUTO) {
      layer = this.get('layer');
      if (f.height === AUTO) f.height = layer ? layer.clientHeight : 0;
      if (f.width === AUTO) f.width = layer ? layer.clientWidth : 0;
    }
    
    // make sure the width/height fix min/max...
    if (!SC.none(layout.maxHeight) && (f.height > layout.maxHeight)) {
      f.height = layout.maxHeight ;
    }

    if (!SC.none(layout.minHeight) && (f.height < layout.minHeight)) {
      f.height = layout.minHeight ;
    }

    if (!SC.none(layout.maxWidth) && (f.width > layout.maxWidth)) {
      f.width = layout.maxWidth ;
    }
    
    if (!SC.none(layout.minWidth) && (f.width < layout.minWidth)) {
      f.width = layout.minWidth ;
    }
    
    // make sure width/height are never < 0
    if (f.height < 0) f.height = 0 ;
    if (f.width < 0) f.width = 0 ;
    
    return f;
  },
  
  computeParentDimensions: function(frame) {
    var ret, pv = this.get('parentView'), pf = (pv) ? pv.get('frame') : null ;
    
    if (pf) {
      ret = { width: pf.width, height: pf.height };
    } else {
      var f = frame ;
      ret = {
        width: (f.left || 0) + (f.width || 0) + (f.right || 0),
        height: (f.top || 0) + (f.height || 0) + (f.bottom || 0)
      };
    }
    return ret ;
  },
  
  /**
    The clipping frame returns the visible portion of the view, taking into
    account the clippingFrame of the parent view.  Keep in mind that the 
    clippingFrame is in the context of the view itself, not it's parent view.
    
    Normally this will be calculate based on the intersection of your own 
    clippingFrame and your parentView's clippingFrame.  
    
    @property {Rect}
  */
  clippingFrame: function() {
    var pv= this.get('parentView'), f = this.get('frame'), ret = f ;
    if (pv) {
     pv = pv.get('clippingFrame') ;
     ret = SC.intersectRects(pv, f) ;
    }
    ret.x -= f.x ;
    ret.y -= f.y ;
    return ret ;
  }.property('parentView', 'frame').cacheable(),
  
  /** @private
    Whenever the clippingFrame changes, this observer will fire, notifying
    child views that their frames have also changed.
  */
  _sc_view_clippingFrameDidChange: function() {
    var cvs = this.get('childViews'), len = cvs.length, idx, cv ;
    for (idx=0; idx<len; ++idx) {
      cv = cvs[idx] ;
      if (!cv.hasStaticLayout) cv.notifyPropertyChange('clippingFrame') ;
    }
  }.observes('clippingFrame'),
    
  /** 
    This method may be called on your view whenever the parent view resizes.
    
    The default version of this method will reset the frame and then call 
    viewDidResize().  You will not usually override this method, but you may
    override the viewDidResize() method.
    
    @returns {void}
    @test in viewDidResize
  */
  parentViewDidResize: function() {
    var layout = this.get('layout') ;
    
    // only resizes if the layout does something other than left/top - fixed
    // size.
    var isFixed = (
      (layout.left !== undefined) && (layout.top !== undefined) &&
      (layout.width !== undefined) && (layout.height !== undefined)
    );
    
    if (!isFixed) {
      this.notifyPropertyChange('frame') ;
      this.viewDidResize() ;
    }
  },
  
  /**
    This method is invoked on your view when the view resizes due to a layout
    change or due to the parent view resizing.  You can override this method
    to implement your own layout if you like, such as performing a grid 
    layout.
    
    The default implementation simply calls parentViewDidResize on all of
    your children.
    
    @returns {void}
  */
  viewDidResize: function() {
    var cv = this.childViews, len = cv.length, idx, view ;
    for (idx=0; idx<len; ++idx) {
      view = cv[idx] ;
      if (view.parentViewDidResize) view.parentViewDidResize() ;
    }
  }.observes('layout'),
  
  // Implementation note: As a general rule, paired method calls, such as 
  // beginLiveResize/endLiveResize that are called recursively on the tree
  // should reverse the order when doing the final half of the call. This 
  // ensures that the calls are propertly nested for any cleanup routines.
  //
  // -> View A.beginXXX()
  //   -> View B.beginXXX()
  //     -> View C.begitXXX()
  //   -> View D.beginXXX()
  //
  // ...later on, endXXX methods are called in reverse order of beginXXX...
  //
  //   <- View D.endXXX()
  //     <- View C.endXXX()
  //   <- View B.endXXX()
  // <- View A.endXXX()
  //
  // See the two methods below for an example implementation.
  
  /**
    Call this method when you plan to begin a live resize.  This will 
    notify the receiver view and any of its children that are interested
    that the resize is about to begin.
    
    @returns {SC.View} receiver
    @test in viewDidResize
  */
  beginLiveResize: function() {
    // call before children have been notified...
    if (this.willBeginLiveResize) this.willBeginLiveResize() ;
    
    // notify children in order
    var ary = this.get('childViews'), len = ary.length, idx, view ;
    for (idx=0; idx<len; ++idx) {
      view = ary[idx] ;
      if (view.beginLiveResize) view.beginLiveResize();
    }
    return this ;
  },
  
  /**
    Call this method when you are finished with a live resize.  This will
    notify the receiver view and any of its children that are interested
    that the live resize has ended.
    
    @returns {SC.View} receiver
    @test in viewDidResize
  */
  endLiveResize: function() {
    // notify children in *reverse* order
    var ary = this.get('childViews'), len = ary.length, idx, view ;
    for (idx=len-1; idx>=0; --idx) { // loop backwards
      view = ary[idx] ;
      if (view.endLiveResize) view.endLiveResize() ;
    }
    
    // call *after* all children have been notified...
    if (this.didEndLiveResize) this.didEndLiveResize() ;
    return this ;
  },
  
  /**
    layoutStyle describes the current styles to be written to your element
    based on the layout you defined.  Both layoutStyle and frame reset when
    you edit the layout property.  Both are read only.
    
    Computes the layout style settings needed for the current anchor.
    
    @property {Hash}
    @readOnly
  */
  layoutStyle: function() {
    var layout = this.get('layout'), ret = {}, pdim = null, error, AUTO = SC.LAYOUT_AUTO;
    var stLayout = this.get('useStaticLayout');
    
    if (layout.width !== undefined &&
        layout.width === SC.LAYOUT_AUTO &&
        !stLayout) {
     error= SC.Error.desc("%@.layout() you cannot use width:auto if  staticLayout is disabled".fmt(this),"%@".fmt(this),-1);
     console.error(error.toString()) ;
     throw error ;
    }
    
    if (layout.height !== undefined &&
        layout.height === SC.LAYOUT_AUTO &&
        !stLayout) {
      error = SC.Error.desc("%@.layout() you cannot use height:auto if  staticLayout is disabled".fmt(this),"%@".fmt(this),-1);  
      console.error(error.toString()) ;
      throw error ;
    }
    
    // X DIRECTION
    
    // handle left aligned and left/right
    if (!SC.none(layout.left)) {
      ret.left = Math.floor(layout.left);
      if (layout.width !== undefined) {
        if(layout.width === SC.LAYOUT_AUTO) ret.width = SC.LAYOUT_AUTO ;
        else ret.width = Math.floor(layout.width) ;
        ret.right = null ;
      } else {
        ret.width = null ;
        ret.right = Math.floor(layout.right || 0) ;
      }
      ret.marginLeft = 0 ;
      
    // handle right aligned
    } else if (!SC.none(layout.right)) {
      ret.right = Math.floor(layout.right) ;
      ret.marginLeft = 0 ;
      
      if (SC.none(layout.width)) {
        ret.left = 0;
        ret.width = null;
      } else {
        ret.left = null ;
        if(layout.width === SC.LAYOUT_AUTO) ret.width = SC.LAYOUT_AUTO ;
        else ret.width = Math.floor(layout.width || 0) ;
      }
      
    // handle centered
    } else if (!SC.none(layout.centerX)) {
      ret.left = "50%";
      ret.width = Math.floor(layout.width || 0) ;
      ret.marginLeft = Math.floor(layout.centerX - ret.width/2) ;
      ret.right = null ;
    
    // if width defined, assume top/left of zero
    } else if (!SC.none(layout.width)) {
      ret.left =  0;
      ret.right = null;
      if(layout.width === SC.LAYOUT_AUTO) ret.width = SC.LAYOUT_AUTO ;
      else ret.width = Math.floor(layout.width);
      ret.marginLeft = 0;
      
    // fallback, full width.
    } else {
      ret.left = 0;
      ret.right = 0;
      ret.width = null ;
      ret.marginLeft= 0;
    }
    
    
    // handle min/max
    ret.minWidth = (layout.minWidth === undefined) ? null : layout.minWidth ;
    ret.maxWidth = (layout.maxWidth === undefined) ? null : layout.maxWidth ;
    
    // Y DIRECTION
    
    // handle left aligned and left/right
    if (!SC.none(layout.top)) {
      ret.top = Math.floor(layout.top);
      if (layout.height !== undefined) {
        if(layout.height === SC.LAYOUT_AUTO) ret.height = SC.LAYOUT_AUTO ;
        else ret.height = Math.floor(layout.height) ;
        ret.bottom = null ;
      } else {
        ret.height = null ;
        ret.bottom = Math.floor(layout.bottom || 0) ;
      }
      ret.marginTop = 0 ;
      
    // handle right aligned
    } else if (!SC.none(layout.bottom)) {
      ret.marginTop = 0 ;
      ret.bottom = Math.floor(layout.bottom) ;
      if (SC.none(layout.height)) {
        ret.top = 0;
        ret.height = null ;
      } else {
        ret.top = null ;
        if(layout.height === SC.LAYOUT_AUTO) ret.height = SC.LAYOUT_AUTO ;
        else ret.height = Math.floor(layout.height || 0) ;
      }
      
    // handle centered
    } else if (!SC.none(layout.centerY)) {
      ret.top = "50%";
      ret.height = Math.floor(layout.height || 0) ;
      ret.marginTop = Math.floor(layout.centerY - ret.height/2) ;
      ret.bottom = null ;
    
    } else if (!SC.none(layout.height)) {
      ret.top = 0;
      ret.bottom = null;
      if(layout.height === SC.LAYOUT_AUTO) ret.height = SC.LAYOUT_AUTO ;
      else ret.height = Math.floor(layout.height || 0) ;
      ret.marginTop = 0;
      
    // fallback, full width.
    } else {
      ret.top = 0;
      ret.bottom = 0;
      ret.height = null ;
      ret.marginTop= 0;
    }
    
    // handle min/max
    ret.minHeight = (layout.minHeight === undefined) ?
      null :
      layout.minHeight ;
    ret.maxHeight = (layout.maxHeight === undefined) ?
      null :
      layout.maxHeight ;
    
    // if zIndex is set, use it.  otherwise let default shine through
    ret.zIndex = SC.none(layout.zIndex) ? null : layout.zIndex.toString();
    
    // if backgroundPosition is set, use it.
    // otherwise let default shine through
    ret.backgroundPosition = SC.none(layout.backgroundPosition) ?
      null :
      layout.backgroundPosition.toString() ;
    
    // set default values to null to allow built-in CSS to shine through
    // currently applies only to marginLeft & marginTop
    var dims = SC._VIEW_DEFAULT_DIMS, loc = dims.length, x;
    while(--loc >=0) {
      x = dims[loc];
      if (ret[x]===0) ret[x]=null;
    }
    
    // convert any numbers into a number + "px".
    for(var key in ret) {
      var value = ret[key];
      if (typeof value === SC.T_NUMBER) ret[key] = (value + "px");
    }
    return ret ;
  }.property().cacheable(),
  
  /**
    The view responsible for laying out this view.  The default version 
    returns the current parent view.
  */
  layoutView: function() {
    return this.get('parentView') ;
  }.property('parentView').cacheable(),
  
  /**
    This method is called whenever a property changes that invalidates the 
    layout of the view.  Changing the layout will do this automatically, but 
    you can add others if you want.
    
    @returns {SC.View} receiver
  */
  layoutDidChange: function() {
    this.beginPropertyChanges() ;
    if (this.frame) this.notifyPropertyChange('frame') ;
    this.notifyPropertyChange('layoutStyle') ;
    this.endPropertyChanges() ;
    
    // notify layoutView...
    var layoutView = this.get('layoutView');
    if (layoutView) {
      layoutView.set('childViewsNeedLayout', YES);
      layoutView.layoutDidChangeFor(this) ;
      if (layoutView.get('childViewsNeedLayout')) {
        layoutView.invokeOnce(layoutView.layoutChildViewsIfNeeded);
      }
     }
    
    return this ;
  }.observes('layout'),
  
  /**
    This this property to YES whenever the view needs to layout its child
    views.  Normally this property is set automatically whenever the layout
    property for a child view changes.
    
    @property {Boolean}
  */
  childViewsNeedLayout: NO,
  
  /**
    One of two methods that are invoked whenever one of your childViews 
    layout changes.  This method is invoked everytime a child view's layout
    changes to give you a chance to record the information about the view.
      
    Since this method may be called many times during a single run loop, you
    should keep this method pretty short.  The other method called when layout
    changes, layoutChildViews(), is invoked only once at the end of 
    the run loop.  You should do any expensive operations (including changing
    a childView's actual layer) in this other method.
    
    Note that if as a result of running this method you decide that you do not
    need your layoutChildViews() method run later, you can set the 
    childViewsNeedsLayout property to NO from this method and the layout 
    method will not be called layer.
     
    @param {SC.View} childView the view whose layout has changed.
    @returns {void}
  */
  layoutDidChangeFor: function(childView) {
    var set = this._needLayoutViews ;
    if (!set) set = this._needLayoutViews = SC.CoreSet.create();
    set.add(childView);
  },
  
  /**
    Called your layout method if the view currently needs to layout some
    child views.
    
    @param {Boolean} isVisible if true assume view is visible even if it is not.
    @returns {SC.View} receiver
    @test in layoutChildViews
  */
  layoutChildViewsIfNeeded: function(isVisible) {
    if (!isVisible) isVisible = this.get('isVisibleInWindow');
    if (isVisible && this.get('childViewsNeedLayout')) {
      this.set('childViewsNeedLayout', NO);
      this.layoutChildViews();
    }
    return this ;
  },
  
  /**
    Applies the current layout to the layer.  This method is usually only
    called once per runloop.  You can override this method to provide your 
    own layout updating method if you want, though usually the better option
    is to override the layout method from the parent view.
    
    The default implementation of this method simply calls the renderLayout()
    method on the views that need layout.
    
    @returns {void}
  */
  layoutChildViews: function() {
    var set = this._needLayoutViews, len = set ? set.length : 0, idx;
    var view, context, layer;
    for(idx=0;idx<len;idx++) {
      view = set[idx];
      view.updateLayout();
    }
    view = context = layer = null ; // cleanup
    set.clear(); // reset & reuse
  },
  
  /**
    Invoked by the layoutChildViews method to update the layout on a 
    particular view.  This method creates a render context and calls the 
    renderLayout() method, which is probably what you want to override instead 
    of this.
    
    You will not usually override this method, but you may call it if you 
    implement layoutChildViews() in a view yourself.
    
    @returns {SC.View} receiver
    @test in layoutChildViews
  */
  updateLayout: function() {
    var layer = this.get('layer'), context;
    if (layer) {
      context = this.renderContext(layer);
      this.renderLayout(context);
      context.update();
    }
    layer = null ;
    return this ;
  },
  
  /**
    Default method called by the layout view to actually apply the current
    layout to the layer.  The default implementation simply assigns the 
    current layoutStyle to the layer.  This method is also called whenever
    the layer is first created.
    
    @param {SC.RenderContext} the render context
    @returns {void}
    @test in layoutChildViews
  */
  renderLayout: function(context, firstTime) {
    context.addStyle(this.get('layoutStyle'));
  },
  
  /** walk like a duck */
  isView: YES,
  
  /**
    Default method called when a selectstart event is triggered. This event is 
    only supported by IE. Used in sproutcore to disable text selection and 
    IE8 accelerators. The accelerators will be enabled only in 
    text selectable views. In FF and Safari we use the css style 'allow-select'.
    
    If you want to enable text selection in certain controls is recommended
    to override this function to always return YES , instead of setting 
    isTextSelectable to true. 
    
    For example in textfield you dont want to enable textSelection on the text
    hint only on the actual text you are entering. You can achieve that by
    only overriding this method.
    
    @param evt {SC.Event} the selectstart event
    @returns YES if selectable
  */
  selectStart: function(evt) {
    return this.get('isTextSelectable');
  }
  
});

SC.View.mixin(/** @scope SC.View */ {
  
  /** @private walk like a duck -- used by SC.Page */
  isViewClass: YES,
  
  /**
    This method works just like extend() except that it will also preserve
    the passed attributes in case you want to use a view builder later, if 
    needed.
    
    @param {Hash} attrs Attributes to add to view
    @returns {Class} SC.View subclass to create
    @function
  */ 
  design: function() {
    if (this.isDesign) return this; // only run design one time
    var ret = this.extend.apply(this, arguments);
    ret.isDesign = YES ;
    if (SC.ViewDesigner) {
      SC.ViewDesigner.didLoadDesign(ret, this, SC.A(arguments));
    }
    return ret ;
  },
  
  /**
    Helper applies the layout to the prototype. 
  */
  layout: function(layout) {
    this.prototype.layout = layout ;
    return this ;
  },
  
  /**
    Convert any layout to a Top, Left, Width, Height layout
  */
  convertLayoutToAnchoredLayout: function(layout, parentFrame){
    var ret = {top: 0, left: 0, width: parentFrame.width, height: parentFrame.height};
    
    // X Conversion
    // handle left aligned and left/right
    if (!SC.none(layout.left)) {
      ret.left = Math.floor(layout.left);
      if (layout.width !== undefined) {
        if(layout.width === SC.LAYOUT_AUTO) ret.width = SC.LAYOUT_AUTO ;
        else ret.width = Math.floor(layout.width) ;
      } else {
        ret.width = parentFrame.width - ret.left - Math.floor(layout.right || 0);
      }

    // handle right aligned
    } else if (!SC.none(layout.right)) {
      
      // if no width, calculate it from the parent frame
      if (SC.none(layout.width)) {
        ret.left = 0;
        ret.width = parentFrame.width - Math.floor(layout.right || 0);
      
      // If has width, calculate the left anchor from the width and right and parent frame
      } else {
        if(layout.width === SC.LAYOUT_AUTO) ret.width = SC.LAYOUT_AUTO ;
        else { 
          ret.width = layout.width;
          ret.left = parentFrame.width - (layout.width + layout.right); 
        }
      }

    // handle centered
    } else if (!SC.none(layout.centerX)) {
      ret.width = Math.floor(layout.width || 0) ;
      ret.left = Math.floor((parentFrame.width - ret.width)/2) + layout.centerX;
    
    // if width defined, assume left of zero
    } else if (!SC.none(layout.width)) {
      ret.left =  0;
      if(layout.width === SC.LAYOUT_AUTO) ret.width = SC.LAYOUT_AUTO ;
      else {
        ret.width = Math.floor(layout.width);
      }

    // fallback, full width.
    } else {
      ret.left = 0;
      ret.width = 0;
    }

    // handle min/max
    if (layout.minWidth !== undefined) ret.minWidth = layout.minWidth ;
    if (layout.maxWidth !== undefined) ret.maxWidth = layout.maxWidth ; 
    
    // Y Conversion
    // handle left aligned and top/bottom
    if (!SC.none(layout.top)) {
      ret.top = Math.floor(layout.top);
      if (layout.height !== undefined) {
        if(layout.height === SC.LAYOUT_AUTO) ret.height = SC.LAYOUT_AUTO ;
        else ret.height = Math.floor(layout.height) ;
      } else {
        ret.height = parentFrame.height - ret.top - Math.floor(layout.bottom || 0);
      }

    // handle bottom aligned
    } else if (!SC.none(layout.bottom)) {
      
      // if no height, calculate it from the parent frame
      if (SC.none(layout.height)) {
        ret.top = 0;
        ret.height = parentFrame.height - Math.floor(layout.bottom || 0);
      
      // If has height, calculate the top anchor from the height and bottom and parent frame
      } else {
        if(layout.height === SC.LAYOUT_AUTO) ret.height = SC.LAYOUT_AUTO ;
        else { 
          ret.height = layout.height;
          ret.top = parentFrame.height - (layout.height + layout.bottom); 
        }
      }

    // handle centered
    } else if (!SC.none(layout.centerY)) {
      ret.height = Math.floor(layout.height || 0) ;
      ret.top = Math.floor((parentFrame.height - ret.height)/2) + layout.centerY;
    
    // if height defined, assume top of zero
    } else if (!SC.none(layout.height)) {
      ret.top =  0;
      if(layout.height === SC.LAYOUT_AUTO) ret.height = SC.LAYOUT_AUTO ;
      else ret.height = Math.floor(layout.height);

    // fallback, full height.
    } else {
      ret.top = 0;
      ret.height = 0;
    }

    // handle min/max
    if (layout.minHeight !== undefined) ret.minHeight = layout.minHeight ;
    if (layout.maxHeight !== undefined) ret.maxHeight = layout.maxHeight ;
    
    return ret;
  },
  
  /**
    For now can only convert Top/Left/Width/Height to a Custom Layout
  */
  convertLayoutToCustomLayout: function(layout, layoutParams, parentFrame){
    // TODO: [EG] Create Top/Left/Width/Height to a Custom Layout conversion
  },
  
  /**
    Helper applies the classNames to the prototype
  */
  classNames: function(sc) {
    sc = (this.prototype.classNames || []).concat(sc);
    this.prototype.classNames = sc;
    return this ;
  },
  
  /**
    Help applies the tagName
  */
  tagName: function(tg) {
    this.prototype.tagName = tg;
    return this ;
  },
  
  /**
    Helper adds the childView
  */
  childView: function(cv) {
    var childViews = this.prototype.childViews || [];
    if (childViews === this.superclass.prototype.childViews) {
      childViews = childViews.slice();
    }
    childViews.push(cv) ;
    this.prototype.childViews = childViews;
    return this ;
  },
  
  /**
    Helper adds a binding to a design
  */
  bind: function(keyName, path) {
    var p = this.prototype, s = this.superclass.prototype;
    var bindings = p._bindings ;
    if (!bindings || bindings === s._bindings) {
      bindings = p._bindings = (bindings || []).slice() ;
    }  
    
    keyName = keyName + "Binding";
    p[keyName] = path ;
    bindings.push(keyName);
    
    return this ;
  },

  /**
    Helper sets a generic property on a design.
  */
  prop: function(keyName, value) {
    this.prototype[keyName] = value;
    return this ;
  },
  
  /**
    Used to construct a localization for a view.  The default implementation
    will simply return the passed attributes.
  */
  localization: function(attrs, rootElement) { 
    // add rootElement
    if (rootElement) attrs.rootElement = SC.$(rootElement).get(0);
    return attrs; 
  },
  
  /**
    Creates a view instance, first finding the DOM element you name and then
    using that as the root element.  You should not use this method very 
    often, but it is sometimes useful if you want to attach to already 
    existing HTML.
    
    @param {String|Element} element
    @param {Hash} attrs
    @returns {SC.View} instance
  */
  viewFor: function(element, attrs) {
    var args = SC.$A(arguments); // prepare to edit
    if (SC.none(element)) {
      args.shift(); // remove if no element passed
    } else args[0] = { rootElement: SC.$(element).get(0) } ;
    var ret = this.create.apply(this, arguments) ;
    args = args[0] = null;
    return ret ;
  },
    
  /**
    Create a new view with the passed attributes hash.  If you have the 
    Designer module loaded, this will also create a peer designer if needed.
  */
  create: function() {
    var C=this, ret = new C(arguments); 
    if (SC.ViewDesigner) {
      SC.ViewDesigner.didCreateView(ret, SC.$A(arguments));
    }
    return ret ; 
  },
  
  /**
    Applies the passed localization hash to the component views.  Call this
    method before you call create().  Returns the receiver.  Typically you
    will do something like this:
    
    view = SC.View.design({...}).loc(localizationHash).create();
    
    @param {Hash} loc 
    @param rootElement {String} optional rootElement with prepped HTML
    @returns {SC.View} receiver
  */
  loc: function(loc) {
    var childLocs = loc.childViews;
    delete loc.childViews; // clear out child views before applying to attrs
    
    this.applyLocalizedAttributes(loc) ;
    if (SC.ViewDesigner) {
      SC.ViewDesigner.didLoadLocalization(this, SC.$A(arguments));
    }
    
    // apply localization recursively to childViews
    var childViews = this.prototype.childViews, idx = childViews.length;
    while(--idx>=0) {
      var viewClass = childViews[idx];
      loc = childLocs[idx];
      if (loc && viewClass && viewClass.loc) viewClass.loc(loc) ;
    }
    
    return this; // done!
  },
  
  /**
    Internal method actually updates the localizated attributes on the view
    class.  This is overloaded in design mode to also save the attributes.
  */
  applyLocalizedAttributes: function(loc) {
    SC.mixin(this.prototype, loc) ;
  },
  
  views: {}
    
}) ;

// .......................................................
// OUTLET BUILDER
//

/** 
  Generates a computed property that will look up the passed property path
  the first time you try to get the value.  Use this whenever you want to 
  define an outlet that points to another view or object.  The root object
  used for the path will be the receiver.
*/
SC.outlet = function(path) {
  return function(key) {
    return (this[key] = SC.objectForPropertyPath(path, this)) ;
  }.property();
};

/** @private on unload clear cached divs. */
SC.View.unload = function() {
  
  // delete view items this way to ensure the views are cleared.  The hash
  // itself may be owned by multiple view subclasses.
  var views = SC.View.views;
  if (views) {
    for(var key in views) {
      if (!views.hasOwnProperty(key)) continue ;
      delete views[key];
    }
  }
  
} ;

SC.Event.add(window, 'unload', SC.View, SC.View.unload) ;

/* >>>>>>>>>> BEGIN source/mixins/validatable.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @namespace

  Views that include the Validatable mixin can be used with validators to 
  ensure their values are valid.  
  
*/
SC.Validatable = {
  
  /** @private */
  initMixin: function() {
    this._validatable_validatorDidChange() ;
  },
  
  /**
    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.
    
    @property {SC.Validator}
  */
  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.
    
    @property {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.
    
    @property {Boolean}
  */
  isValid: function() { 
    return SC.typeOf(this.get('value')) !== SC.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.
    
    @property {SC.View}
  */
  ownerForm: null,
  
  /**
    Attempts to validate the receiver. 
    
    Runs the validator and returns SC.VALIDATE_OK, SC.VALIDATE_NO_CHANGE,
    or an error object.  If no validator is installed, this method will
    always return SC.VALIDATE_OK.

    @param {Boolean} partialChange YES if this is a partial edit.
    @returns {String} SC.VALIDATE_OK, error, or SC.VALIDATE_NO_CHANGE
  */
  performValidate: function(partialChange) {
    var ret = SC.VALIDATE_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.VALIDATE_NO_CHANGE) && (this._validator.validateChange(form, this) == SC.VALIDATE_OK)) {
          ret = SC.VALIDATE_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.VALIDATE_OK
    
    @returns {String}
  */
  performValidateSubmit: function() {
    return this._validator ? this._validator.validateSubmit(this.get('ownerForm'), this) : SC.VALIDATE_OK;
  },
  
  /**
    Runs a keypress validation.  Returns YES if the keypress should be 
    allowed, NO otherwise.  If no validator is defined, always returns YES.
    
    @param {String} charStr the key string
    @returns {Boolean}
  */
  performValidateKeyDown: function(evt) {
    // ignore anything with ctrl or meta key press
    var charStr = evt.getCharString();
    if (!charStr) return YES ;
    return this._validator ? this._validator.validateKeyDown(this.get('ownerForm'), this, charStr) : YES;
  },
  
  /**
    Returns the validator object, if one has been created.
    
    @property {SC.Validator}
  */
  validatorObject: function() {
    return this._validator;
  }.property(),
  
  /**
    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.
    
    @property {Boolean}
  */
  validateSubmit: function() { return this.performValidateSubmit(); },
  
  /**
    Convert the field value string into an object.
    
    This method will call the validators objectForFieldValue if it exists.
    
    @param {Object} fieldValue the raw value from the field.
    @param {Boolean} partialChange
    @returns {Object}
  */
  objectForFieldValue: function(fieldValue, partialChange) {
    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 {Object} the objec to convert
    @returns {Object}
  */
  fieldValueForObject: function(object) {
    return this._validator ? this._validator.fieldValueForObject(object, this.get('ownerForm'), this) : object ;
  },
  
  _validatable_displayObserver: function() {
    this.displayDidChange();
  }.observes('isValid'),
  
  /** @private */
  updateLayerMixin: function(context) {
    context.setClass('invalid', !this.get('isValid'));
  },
  
  // invoked whenever the attached validator changes.
  _validatable_validatorDidChange: function() {
    var form = this.get('ownerForm') ;
    var val = SC.Validator.findFor(form, this, this.get('validator')) ;
    if (val != this._validator) {
      this.propertyWillChange('validatorObject');
      if (this._validator) this._validator.detachFrom(form, this) ;
      this._validator = val;
      if (this._validator) this._validator.attachTo(form, this) ;
      this.propertyDidChange('validatorObject');
    }  
  }.observes('validator', 'ownerForm')
      
};

/* >>>>>>>>>> BEGIN source/views/field.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

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

/** @class

  Base view for managing a view backed by an input element.  Since the web
  browser provides native support for editing input elements, this view
  provides basic support for listening to changes on these input elements and
  responding to them.
  
  Generally you will not work with a FieldView directly.  Instead, you should
  use one of the subclasses implemented by your target platform such as 
  SC.CheckboxView, SC.RadioView, SC.TextFieldView, and so on.
  
  @extends SC.View
  @extends SC.Control
  @extends SC.Validatable
  @since SproutCore 1.0
*/
SC.FieldView = SC.View.extend(SC.Control, SC.Validatable,
/** @scope SC.FieldView.prototype */ {
  
  /**
     If YES then we use textarea instead of input. 
     WARNING: Use only with textField** Juan
  */
  isTextArea: NO,
  
  /*
    This variable is here to make the tab focus behavior work like safari's.
    We believe that is a better experience for accesibility and the user in 
    general to be able to access all controls through you keyboard. If you
    disagree set this variable to YES.
  */
  followSafariTabFocusBehavior: NO,


  _field_isMouseDown: NO,
  
  /**
    The raw value of the field itself.  This is computed from the 'value'
    property by passing it through any validator you might have set.  This is 
    the value that will be set on the field itself when the view is updated.
    
    @property {String}
  */  
  fieldValue: function() {
    var value = this.get('value');
    if (SC.typeOf(value) === SC.T_ERROR) value = value.get('value');
    return this.fieldValueForObject(value);
  }.property('value', 'validator').cacheable(),

  // ..........................................................
  // PRIMITIVES
  // 
  
  /**
    Override to return an CoreQuery object that selects the input elements
    for the view.  If this method is defined, the field view will 
    automatically edit the attrbutes of the input element to reflect the 
    current isEnabled state among other things.
  */
  $input: function() { 
    if(this.get('isTextArea')){
      return this.$('textarea').andSelf().filter('textarea'); 
    }else{
      return this.$('input').andSelf().filter('input');
    }
  },
  
  /**
    Override to set the actual value of the field.
    
    The default implementation will simple copy the newValue to the value
    attribute of any input tags in the receiver view.  You can override this
    method to provide specific functionality needed by your view.
    
    @param {Object} newValue the value to display.
    @returns {SC.FieldView} receiver
  */
  setFieldValue: function(newValue) {
    if (SC.none(newValue)) newValue = '' ;
    var input = this.$input();
    
    // Don't needlessly set the element if it already has the value, because
    // doing so moves the cursor to the end in some browsers.
    if (input.val() !== newValue) {
      input.val(newValue);
    }
    return this ;
  },
  
  /**
    Override to retrieve the actual value of the field.
    
    The default implementation will simply retrieve the value attribute from
    the first input tag in the receiver view.
    
    @returns {String} value
  */
  getFieldValue: function() {
    return this.$input().val();
  },
  
  _field_fieldValueDidChange: function(evt) {
    SC.RunLoop.begin();
    this.fieldValueDidChange(NO);
    SC.RunLoop.end();  
  },
  
  /**
    Your class should call this method anytime you think the value of the 
    input element may have changed.  This will retrieve the value and update
    the value property of the view accordingly.
    
    If this is a partial change (i.e. the user is still editing the field and
    you expect the value to change further), then be sure to pass YES for the
    partialChange parameter.  This will change the kind of validation done on
    the value.  Otherwise, the validator may mark the field as having an error
    when the user is still in mid-edit.
  
    @param partialChange (optional) YES if this is a partial change.
    @returns {Boolean|SC.Error} result of validation.
  */
  fieldValueDidChange: function(partialChange) {
    // collect the field value and convert it back to a value
    var fieldValue = this.getFieldValue();
    var value = this.objectForFieldValue(fieldValue, partialChange);
    this.setIfChanged('value', value);
    
    // validate value if needed...
    
    // this.notifyPropertyChange('fieldValue');
    // 
    // // get the field value and set it.
    // // if ret is an error, use that instead of the field value.
    // var ret = this.performValidate ? this.performValidate(partialChange) : YES;
    // if (ret === SC.VALIDATE_NO_CHANGE) return ret ;
    // 
    // this.propertyWillChange('fieldValue');
    // 
    // // 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 ok = SC.$ok(ret);
    // var value = ok ? this._field_getFieldValue() : ret ;
    // if (!partialChange && ok) this._field_setFieldValue(value) ;
    // this.set('value',value) ;
    // 
    // this.propertyDidChange('fieldValue');
    // 
    // return ret ;
  },
  
  // ..........................................................
  // INTERNAL SUPPORT
  // 
  
  /** @private
    invoked when the value property changes.  Sets the field value...
  */
  _field_valueDidChange: function() {
    this.setFieldValue(this.get('fieldValue'));
  }.observes('value'),

  /** @private
    after the layer is created, set the field value and observe events
  */
  didCreateLayer: function() {
    this.setFieldValue(this.get('fieldValue'));
    SC.Event.add(this.$input(), 'change', this, this._field_fieldValueDidChange) ;
  },
  
  willDestroyLayer: function() {
    SC.Event.remove(this.$input(), 'change', this, this._field_fieldValueDidChange); 
  },

  /** @private
    when the layer is updated, go ahead and call render like normal.  this 
    will allow normal CSS class + style updates.  by then also update field
    value manually.
    
    Most subclasses should not regenerate their contents unless necessary.
  */
  updateLayer: function() {
    arguments.callee.base.apply(this,arguments);
    
    // only change field value if it has changed from the last time we 
    // set it.  This allows the browser-native field value to change without
    // this method interfering with it.
    //var fieldValue = this.get('fieldValue');
    //this.setFieldValue(fieldValue);
  },
  
  // 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 ? this.performValidateSubmit() : YES;
  //   // save the value if needed
  //   var value = SC.$ok(ret) ? this._field_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.
  
  /**
    Allow the browser to do its normal event handling for the mouse down
    event.  But first, set isActive to YES.
  */
  mouseDown: function(evt) {  
    this._field_isMouseDown = YES;
    evt.allowDefault(); 
    return YES; 
  },
  
  /** @private
    Remove the active class on mouseOut if mouse is down.
  */  
  mouseOut: function(evt) {
    if (this._field_isMouseDown) this.set('isActive', NO);
    evt.allowDefault();
    return YES;
  },
  
  /** @private
    If mouse was down and we renter the button area, set the active state again.
  */  
  mouseOver: function(evt) {
    this.set('isActive', this._field_isMouseDown);
    evt.allowDefault();
    return YES;
  },
  
  /** @private
    on mouse up, remove the isActive class and then allow the browser to do
    its normal thing.
  */  
  mouseUp: function(evt) {
    // track independently in case isEnabled has changed
    if (this._field_isMouseDown) this.set('isActive', NO); 
    this._field_isMouseDown = NO;
    evt.allowDefault();
    return YES ;
  },
  
  /** @private
    Simply allow keyDown & keyUp to pass through to the default web browser
    implementation.
  */
  keyDown: function(evt) {

    // handle tab key
    if (evt.which === 9) {
      var view = evt.shiftKey ? this.get('previousValidKeyView') : this.get('nextValidKeyView');
      view.becomeFirstResponder();
      return YES ; // handled
    }
    
    // validate keyDown...
    if (this.performValidateKeyDown(evt)) {
      this._isKeyDown = YES ;
      evt.allowDefault(); 
    } else {
      evt.stop();
    }
    
    return YES; 
  },
  
  /** tied to the isEnabled state */
  acceptsFirstResponder: function() {
    if(!this.get('followSafariTabFocusBehavior')){
      return this.get('isEnabled');
    }
    return NO;
  }.property('isEnabled'),
  
  willBecomeKeyResponderFrom: function(keyView) {
    // focus the text field.
    if (!this._isFocused) {
      this._isFocused = YES ;
      this.becomeFirstResponder();
      if (this.get('isVisibleInWindow')) {
        this.$input().get(0).focus();
      }
    }
  },
  
  willLoseKeyResponderTo: function(responder) {
    if (this._isFocused) this._isFocused = NO ;
  },
  
  
  // called whenever the value is set on the object.  Will set the value
  // on the field if the value is changed.
  // valueDidChange: function() {
  //   var value = this.get('value') ;
  //   var isError = SC.typeOf(value) === SC.T_ERROR ;
  //   if (!isError && (value !== this._field_getFieldValue())) {
  //     this._field_setFieldValue(value) ;
  //   } 
  // }.observes('value'),
  
  // these methods use the validator to convert the raw field value returned
  // by your subclass into an object and visa versa.
  _field_setFieldValue: function(newValue) {
    this.propertyWillChange('fieldValue');
    if (this.fieldValueForObject) {
      newValue = this.fieldValueForObject(newValue) ;
    }
    var ret = this.setFieldValue(newValue) ;
    this.propertyDidChange('fieldValue');
    return ret ;
  },
  
  _field_getFieldValue: function() {
    var ret = this.getFieldValue() ;
    if (this.objectForFieldValue) ret=this.objectForFieldValue(ret);
    return ret ;
  }
  
}) ;



/* >>>>>>>>>> BEGIN source/system/text_selection.js */
// ==========================================================================
// Project:   SproutCore Costello - Property Observing Library
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class
  
  A simple object representing the selection inside a text field.  Each
  object is frozen and contains exactly three properties:
  
    *  start
    *  end
    *  length
  
  Important note:  In Internet Explorer, newlines in textara elements are
  considered two characters.  SproutCore does not currently try to hide this from you.
  
  @extends SC.Object
  @extends SC.Copyable
  @extends SC.Freezable
  @since SproutCore 1.0
*/

SC.TextSelection = SC.Object.extend(SC.Copyable, SC.Freezable,
/** @scope SC.TextSelection.prototype */ {  

  /**
    The number of characters appearing to the left of the beginning of the
    selection, starting at 0.
    
    @type {Number}
  */
  start: -1,
  
  
  /**
    The number of characters appearing to the left of the end of the
    selection.

    This will have the same value as 'start' if there is no selection and
    instead there is only a caret.
    
    @type {Number}
  */
  end: -1,
 
   
  /**
    The length of the selection.  This is equivalent to (end - start) and
    exists mainly as a convenience.
    
    @property {Number}
  */
  length: function() {
    var start = this.get('start') ;
    var end   = this.get('end') ;
    if ((start) === -1  ||  (end === -1)) {
      return -1 ;
    }
    else {
      return end - start ;
    }
  }.property('start', 'end').cacheable(),
  
  
  
  // ..........................................................
  // INTERNAL SUPPORT
  //
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    this.freeze();
  },
  
  
  copy: function() {
    return SC.TextSelection.create({
      start: this.get('start'),
      end:   this.get('end')
    });
  },
  
  
  toString: function() {
    var length = this.get('length');
    if (length  &&  length > 0) {
      if (length === 1) {
        return "[%@ character selected: {%@, %@}]".fmt(length, this.get('start'), this.get('end'));
      }
      else {
        return "[%@ characters selected: {%@, %@}]".fmt(length, this.get('start'), this.get('end'));
      }
    }
    else {
      return "[no text selected; caret at %@]".fmt(this.get('start'));
    }
  }

}) ;

/* >>>>>>>>>> BEGIN source/mixins/static_layout.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @namespace 

  Normally, SproutCore views use absolute positioning to display themselves
  on the screen.  While this is both the fastest and most efficient way to 
  display content in the web browser, sometimes your user interface might need
  to take advantage of the more advanced "flow" layout offered by the browser
  when you use static and relative positioning.
  
  This mixin can be added to a view class to enable the use of any kind of 
  static and relative browser positionining.  In exchange for using static
  layout, you will lose a few features that are normally available on a view
  class such as the 'frame' and 'clippingFrame' properties as well as 
  notifications when your view or parentView are resized.
  
  Normally, if you are allowing the browser to manage the size and positioning
  of your view, these feature will not be useful to your code anyway.
  
  h2. Using StaticLayout
  
  To enable static layout on your view, just include this mixin on the view.
  SproutCore's builtin views that are capable of being used in static 
  layouts already incorporate this mixin.  Then set the "useStaticLayout" 
  property on your view class to YES.
  
  You can then use CSS or the render() method on your view to setup the 
  positioning on your view using any browser layout mechanism you want.
  
  h2. Example
  
  {{{
    
    // JavaScript
    
    MyApp.CommentView = SC.View.extend(SC.StaticLayout, {
    
      classNames: ['comment-view'],
      
      useStaticLayout: YES,

      ...
    });
    
    // CSS
    
    .comment-view {
      display: block;
      position: relative;
    }
    
  }}}
  
  @since SproutCore 1.0
*/
SC.StaticLayout = {

  /**
    Walk like a duck.  Used to determine that this mixin has been applied.  
    Note that a view that hasStaticLayout still may not actually use static
    layout unless useStaticLayout is also set to YES.
    
    @property {Boolean}
  */
  hasStaticLayout: YES,
  
  /**
    Activates use of brower's static layout.  You can apply this mixin and
    still use absolute positioning.  To activate static positioning, set this
    property to YES.

    @property {Boolean}
  */
  useStaticLayout: NO,
  
  /** @private - add sc-static-layout class if needed. */
  renderMixin: function(context, firstTime) {
    context.setClass('sc-static-layout', this.get('useStaticLayout'));
  },
  
  /**
    clippingFrame is not available on views with static layout.  This 
    property will always return null.
    
    @function
  */
  clippingFrame: null,
  
  /**
    This method is not supported on static layout views.
    
    @function
  */
  parentViewDidResize: null,
  
  /**
    This method is not supported on static layout views.
    
    @function
  */
  beginLiveResize: null,

  /**
    This method is not supported on static layout views.
    
    @function
  */
  endLiveResize: null,

  /**
    This method is not supported on static layout views.
    
    @function
  */
  viewDidResize: null
  
};
/* >>>>>>>>>> BEGIN source/views/text_field.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/field') ;
sc_require('system/text_selection') ;
sc_require('mixins/static_layout') ;

/**
  @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.StaticLayout, SC.Editable,
/** @scope SC.TextFieldView.prototype */ {

  tagName: 'label',
  classNames: ['sc-text-field-view'],

  // ..........................................................
  // PROPERTIES
  //

  /**
    If YES, the field will hide its text from display. The default value is NO.
  */
  isPassword: NO,

  /**
    If YES then allow multi-line input.  This will also change the default
    tag type from "input" to "textarea".  Otherwise, pressing return will
    trigger the default insertion handler.
  */
  isTextArea: NO,

  /**
    The hint to display while the field is not active.  Can be a loc key.
  */
  hint: null,

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

  /**
    An optional view instance, or view class reference, which will be visible
    on the left side of the text field.  Visually the accessory view will look
    to be inside the field but the text editing will not overlap the accessory
    view.

    The view will be rooted to the top-left of the text field.  You should use
    a layout with 'left' and/or 'top' specified if you would like to adjust
    the offset from the top-left.

    One example use would be for a web site's icon, found to the left of the
    URL field, in many popular web browsers.

    Note:  If you set a left accessory view, the left padding of the text
    field (really, the left offset of the padding element) will automatically
    be set to the width of the accessory view, overriding any CSS you may have
    defined on the "padding" element.  If you would like to customize the
    amount of left padding used when the accessory view is visible, make the
    accessory view wider, with empty space on the right.
  */
  leftAccessoryView: null,

  /**
    An optional view instance, or view class reference, which will be visible
    on the right side of the text field.  Visually the accessory view will
    look to be inside the field but the text editing will not overlap the
    accessory view.

    The view will be rooted to the top-right of the text field.  You should
    use a layout with 'right' and/or 'top' specified if you would like to
    adjust the offset from the top-right.  If 'left' is specified in the
    layout it will be cleared.

    One example use would be for a button to clear the contents of the text
    field.

    Note:  If you set a right accessory view, the right padding of the text
    field (really, the right offset of the padding element) will automatically
    be set to the width of the accessory view, overriding any CSS you may have
    defined on the "padding" element.  If you would like to customize the
    amount of right padding used when the accessory view is visible, make the
    accessory view wider, with empty space on the left.
  */
  rightAccessoryView: null,
  
  _isFocused: NO,


  /** isEditable maps to isEnabled with a TextField. */
  isEditable: function() {
    return this.get('isEnabled') ;
  }.property('isEnabled').cacheable(),


  /**
    The current selection of the text field, returned as an SC.TextSelection
    object.

    Note that if the selection changes a new object will be returned -- it is
    not the case that a previously-returned SC.TextSelection object will
    simply have its properties mutated.

    @property {SC.TextSelection}
  */
  selection: function(key, value) {
    var element = this.$input().get(0) ;
    var range, start, end;

    // Are we being asked to set the value, or return the current value?
    if (value === undefined) {
      // The client is retrieving the value.
      if (element) {
        start = null;
        end = null;

        if (!element.value) {
          start = end = 0 ;
        }
        else {
          // In IE8, input elements don't have hasOwnProperty() defined.
          if ('selectionStart' in element) {
            start = element.selectionStart ;
          }
          if ('selectionEnd' in element) {
            end = element.selectionEnd ;
          }

          // Support Internet Explorer.
          if (start === null  ||  end === null ) {
            var selection = document.selection ;
            if (selection) {
              var type = selection.type ;
              if (type  &&  (type === 'None'  ||  type === 'Text')) {
                range = selection.createRange() ;

                if (!this.get('isTextArea')) {
                  // Input tag support.  Figure out the starting position by
                  // moving the range's start position as far left as possible
                  // and seeing how many characters it actually moved over.
                  var length = range.text.length ;
                  start = Math.abs(range.moveStart('character', 0 - (element.value.length + 1))) ;
                  end = start + length ;
                }
                else {
                  // Textarea support.  Unfortunately, this case is a bit more
                  // complicated than the input tag case.  We need to create a
                  // "dummy" range to help in the calculations.
                  var dummyRange = range.duplicate() ;
                  dummyRange.moveToElementText(element) ;
                  dummyRange.setEndPoint('EndToStart', range) ;
                  start = dummyRange.text.length ;
                  end = start + range.text.length ;
                }
              }
            }
          }
        }
        return SC.TextSelection.create({ start:start, end:end }) ;
      }
      else {
        return null;
      }
    }
    else {
      // The client is setting the value.  Make sure the new value is a text
      // selection object.
      if (!value  ||  !value.kindOf  ||  !value.kindOf(SC.TextSelection)) {
        throw "When setting the selection, you must specify an SC.TextSelection instance.";
      }

      if (element) {
        var setStart, setEnd ;

        // In IE8, input elements don't have hasOwnProperty() defined.  Also,
        // in Firefox 3.5, trying to get the selectionStart / selectionEnd
        // properties at certain times can cause exceptions.
        if ('selectionStart' in element) {
         element.selectionStart = value.get('start') ;
         setStart = YES ;
        }
        if ('selectionEnd' in element) {
         element.selectionEnd = value.get('end') ;
         setEnd = YES ;
        }

        // Support Internet Explorer.
        if (!setStart  ||  !setEnd) {
         range = element.createTextRange() ;
         start = value.get('start') ;
         range.move('character', start) ;
         range.moveEnd('character', value.get('end') - start) ;
         range.select() ;
        }
      }
    }

    // Implementation note:
    // There are certain ways users can add/remove text that we can't identify
    // via our key/mouse down/up handlers (such as the user choosing Paste
    // from a menu).  So that's why we need to update our 'selection' property
    // whenever the field's value changes.
  }.property('fieldValue').cacheable(),



  // ..........................................................
  // INTERNAL SUPPORT
  //

  displayProperties: 'hint fieldValue isEditing leftAccessoryView rightAccessoryView isTextArea'.w(),


  createChildViews: function() {
    this.accessoryViewObserver() ;
  },


  accessoryViewObserver: function() {
    var classNames;
    var viewProperties = ['leftAccessoryView', 'rightAccessoryView'] ;
    var len = viewProperties.length ;
    for (var i=0; i<len; i++) {
      var viewProperty = viewProperties[i] ;

      // Is there an accessory view specified?
      var previousView = this['_'+viewProperty] ;
      var accessoryView = this.get(viewProperty) ;

      // If the view is the same, there's nothing to do.  Otherwise, remove
      // the old one (if any) and add the new one.
      if (! (previousView
             &&  accessoryView
             &&  (previousView === accessoryView) ) ) {

        // If there was a previous previous accessory view, remove it now.
        if (previousView) {
          // Remove the "sc-text-field-accessory-view" class name that we had
          // added earlier.
          classNames = previousView.get('classNames') ;
          classNames = classNames.without('sc-text-field-accessory-view') ;
          previousView.set('classNames', classNames) ;
          this.removeChild(previousView) ;
          previousView = null ;
          this['_'+viewProperty] = null ;
        }

        // If there's a new accessory view to add, do so now.
        if (accessoryView) {
          // If the user passed in a class rather than an instance, create an
          // instance now.
          if (accessoryView.isClass) {
            accessoryView = accessoryView.create({
              layoutView: this
            }) ;
          }

          // Add in the "sc-text-field-accessory-view" class name so that the
          // z-index gets set correctly.
          classNames = accessoryView.get('classNames') ;
          var className = 'sc-text-field-accessory-view' ;
          if (classNames.indexOf(className) < 0) {
            classNames.push(className) ;
          }

          // Actually add the view to our hierarchy and cache a reference.
          this.appendChild(accessoryView) ;
          this['_'+viewProperty] = accessoryView ;
        }
      }
    }
  }.observes('leftAccessoryView', 'rightAccessoryView'),


  layoutChildViewsIfNeeded: function(isVisible) {
    // For the right accessory view, adjust the positioning such that the view
    // is right-justified, unless 'right' is specified.
    if (!isVisible) isVisible = this.get('isVisibleInWindow') ;
    if (isVisible && this.get('childViewsNeedLayout')) {
      var rightAccessoryView = this.get('rightAccessoryView') ;
      if (rightAccessoryView  &&  rightAccessoryView.get) {
        var layout = rightAccessoryView.get('layout') ;
        if (layout) {
          // Clear out any 'left' value.
          layout.left = null;

          // Unless the user specified a 'right' value, specify a default to
          // right-justify the view.
          if (!layout.right) layout.right = 0 ;

          rightAccessoryView.adjust({ layout: layout }) ;
        }
      }
    }

    arguments.callee.base.apply(this,arguments) ;
  },


  render: function(context, firstTime) {
    arguments.callee.base.apply(this,arguments) ;

    var disabled = this.get('isEnabled') ? '' : 'disabled="disabled"';
    var name = SC.guidFor(this);
    var type = this.get('isPassword') ? 'password' : 'text';

    if (this.get('isTextArea')) context.addClass('text-area');

    // always have at least an empty string
    var v = this.get('fieldValue');
    if (SC.none(v)) v = '';

    // update layer classes always
    context.setClass('not-empty', v.length > 0);

    // If we have accessory views, we'll want to update the padding on the
    // hint to compensate for the width of the accessory view.  (It'd be nice
    // if we could add in the original padding, too, but there's no efficient
    // way to do that without first rendering the element somewhere on/off-
    // screen, and we don't want to take the performance hit.)
    var accessoryViewWidths = this._getAccessoryViewWidths() ;
    var leftAdjustment  = accessoryViewWidths['left'] ;
    var rightAdjustment = accessoryViewWidths['right'] ;

    if (leftAdjustment)  leftAdjustment  += 'px' ;
    if (rightAdjustment) rightAdjustment += 'px' ;

    this._renderField(context, firstTime, v, leftAdjustment, rightAdjustment) ;
    if(SC.browser.mozilla) this.invokeLast(this._applyFirefoxCursorFix);
  },


  /**
    If isTextArea is changed (this might happen in inlineeditor constantly)
    force the field render to render like the firsttime to avoid writing extra
    code. This can be useful also 
  */
  _forceRenderFirstTime: NO,
    
  _renderFieldLikeFirstTime: function(){
    this.set('_forceRenderFirstTime', YES);
  }.observes('isTextArea'),
  
  _renderField: function(context, firstTime, value, leftAdjustment, rightAdjustment) {
    // TODO:  The cleanest thing might be to create a sub- rendering context
    //        here, but currently SC.RenderContext will render sibling
    //        contexts as parent/child.
    var hint = this.get('hint') ;
    
    if (firstTime || this._forceRenderFirstTime) {
      this._forceRenderFirstTime = NO;
      var disabled = this.get('isEnabled') ? '' : 'disabled="disabled"' ;
      var name = this.get('layerId');
      
      context.push('<span class="border"></span>');

      // Render the padding element, with any necessary positioning
      // adjustments to accommodate accessory views.
      var adjustmentStyle = '' ;
      if (leftAdjustment  ||  rightAdjustment) {
        adjustmentStyle = 'style="' ;
        if (leftAdjustment)  adjustmentStyle += 'left: '  + leftAdjustment  + '; ' ;
        if (rightAdjustment) adjustmentStyle += 'right: ' + rightAdjustment + ';' ;
        adjustmentStyle += '"' ;
      }
      context.push('<span class="padding" %@>'.fmt(adjustmentStyle));
      
      // Render the hint.
      context.push('<span class="sc-hint">', hint, '</span>') ;
      
      // Render the input/textarea field itself, and close off the padding.
      if (this.get('isTextArea')) {
        context.push('<textarea name="%@" %@>%@</textarea></span>'.fmt(name, disabled, value)) ;
      }
      else {
        var type = this.get('isPassword') ? 'password' : 'text' ;
        context.push('<input type="%@" name="%@" %@ value="%@"/></span>'.fmt(type, name, disabled, value)) ;
      }

    }
    else {
      // If this is not first time rendering, update the hint itself since we
      // can't just blow away the text field like we might most other controls
      var hintElements = this.$('.sc-hint') ;
      if (hint !== this._textField_currentHint) {
        this._textField_currentHint = hint ;
        hintElements.text(hint) ;
      }
      
      // Enable/disable the actual input/textarea as appropriate.
      var element = this.$input()[0];
      if (element) {
        if (!this.get('isEnabled')) {
          element.disabled = 'true' ;
        }
        else {
          element.disabled = null ;
        }

        // Adjust the padding element to accommodate any accessory views.
        var paddingElement = element.parentNode;
        if (leftAdjustment) {
          if (paddingElement.style.left !== leftAdjustment) {
            paddingElement.style.left = leftAdjustment ;
          }
        }
        else {
          paddingElement.style.left = null ;
        }

        if (rightAdjustment) {
          if (paddingElement.style.right !== rightAdjustment) {
            paddingElement.style.right = rightAdjustment ;
          }
        }
        else {
          paddingElement.style.right = null ;
        }


        // Firefox needs a bit of help to recalculate the width of the text
        // field, if it has focus.  (Even though it's set to 100% of its
        // parent, if we adjust the parent it doesn't always adjust in kind.)
        // if (SC.browser.mozilla) {
        //           if(paddingElement.clientWidth>0){
        //             element.style.width = paddingElement.clientWidth + "px";
        //           }
        //         }
      }
    }
  },


  _getAccessoryViewWidths: function() {
    var widths = {};
    var accessoryViewPositions = ['left', 'right'] ;
    var numberOfAccessoryViewPositions = accessoryViewPositions.length ;
    for (var i = 0;  i < numberOfAccessoryViewPositions;  i++) {
      var position = accessoryViewPositions[i];
      var accessoryView = this.get(position + 'AccessoryView');
      if (accessoryView  &&  accessoryView.get) {
        var frame = accessoryView.get('frame');
        if (frame) {
          var width = frame.width;
          if (width) {
            // Also account for the accessory view's inset.
            var layout = accessoryView.get('layout');
            if (layout) {
              var offset = layout[position];
              width += offset;
            }
            widths[position] = width;
          }
        }
      }
    }
    return widths;
  },


  // ..........................................................
  // HANDLE NATIVE CONTROL EVENTS
  //

  didCreateLayer: function() {
    arguments.callee.base.apply(this,arguments);

    var input = this.$input();
    SC.Event.add(input, 'focus', this, this._textField_fieldDidFocus);
    SC.Event.add(input, 'blur',  this, this._textField_fieldDidBlur);

    // There are certain ways users can select text that we can't identify via
    // our key/mouse down/up handlers (such as the user choosing Select All
    // from a menu).
    SC.Event.add(input, 'select', this, this._textField_selectionDidChange);
    
    if(SC.browser.mozilla){
      // cache references to layer items to improve firefox hack perf
       this._cacheInputElement = this.$input();
       this._cachePaddingElement = this.$('.padding');
    }
  },

  willDestroyLayer: function() {
    arguments.callee.base.apply(this,arguments);

    var input = this.$input();
    SC.Event.remove(input, 'focus', this, this._textField_fieldDidFocus);
    SC.Event.remove(input, 'blur',  this, this._textField_fieldDidBlur);
    SC.Event.remove(input, 'select',  this, this._textField_selectionDidChange);
  },

  _textField_fieldDidFocus: function(evt) {
    SC.RunLoop.begin();
    this.fieldDidFocus();
    SC.RunLoop.end();
  },

  _textField_fieldDidBlur: function(evt) {
    SC.RunLoop.begin();
    this.fieldDidBlur();
    SC.RunLoop.end();
  },
  
  fieldDidFocus: function(evt) {
    this.beginEditing();
  },
  
  fieldDidBlur: function() {
    this.commitEditing();
  },

  /**
    Move magic number out so it can be over-written later in inline editor
  */
  _topOffsetForFirefoxCursorFix: 3,

  _applyFirefoxCursorFix: function() {
    // Be extremely careful changing this code.  !!!!!!!! 
    // Contact me if you need to change or improve the code. After several 
    // iterations the new way to apply the fix seems to be the most 
    // consistent.
    // This fixes: selection visibility, cursor visibility, and the ability 
    // to fix the cursor at any position. As of FF 3.5.3 mozilla hasn't fixed this 
    // bug, even though related bugs that I've found on their database appear
    // as fixed.  
    // 
    // Juan Pinzon
    
    if (SC.browser.mozilla) {
      var top, left, width, height, p, layer, element, textfield;
      
      // I'm caching in didCreateLayer this elements to improve perf
      element = this._cacheInputElement;
      textfield = this._cachePaddingElement;
      if(textfield && textfield[0]){
        layer = textfield[0];
        p = SC.$(layer).offset() ;
      
        // this is to take into account an styling issue.
        if(element[0].tagName.toLowerCase()==="input") {
          top = p.top+this._topOffsetForFirefoxCursorFix; 
        }
        else top = p.top;
        left = p.left;
        width = layer.offsetWidth;
        height = layer.offsetHeight ;
      
        var style = 'position: fixed; top: %@px; left: %@px; width: %@px; height: %@px;'.fmt(top, left, width, height) ;
        // if the style is the same don't re-apply
        if(!this._prevStyle || this._prevStyle!=style) element.attr('style', style) ;
        this._prevStyle = style;
      }
    }
    return this ;
  },
  
  
  _textField_selectionDidChange: function() {
    this.notifyPropertyChange('selection');
  },


  // ..........................................................
  // FIRST RESPONDER SUPPORT
  //
  // 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 */
  willBecomeKeyResponderFrom: function(keyView) {
    if(this.get('isVisibleInWindow')) {
      this.$input()[0].focus();
      
      if(!this._txtFieldMouseDown){
        if(SC.browser.mozilla) this.invokeOnce(this._selectRootElement) ;
        else if(SC.browser.safari) this.invokeLater(this._selectRootElement, 1) ; 
        else this._selectRootElement();
      }
    }
  },
  
  willLoseKeyResponderTo: function(responder) {
    //if (this._isFocused) this._isFocused = NO ;
  },

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

  // when we lose first responder, blur the text field if needed and show
  // the hint text if needed.
  /** @private */
  didLoseKeyResponderTo: function(keyView) {
    this.$input()[0].blur() ;
  },

  parentViewDidResize: function() {
    if (SC.browser.mozilla) {
      this.invokeLast(this._applyFirefoxCursorFix);
    }
    arguments.callee.base.apply(this,arguments);
  },


  /** @private
    Simply allow keyDown & keyUp to pass through to the default web browser
    implementation.
  */
  keyDown: function(evt) {
    // handle return and escape.  this way they can be passed on to the
    // responder chain.
    if ((evt.which === 13) && !this.get('isTextArea')) return NO ;
    if (evt.which === 27) return NO ;

    // handle tab key
    if (evt.which === 9 && !this.get('isTextArea')) {
      var view = evt.shiftKey ? this.get('previousValidKeyView') : this.get('nextValidKeyView');
      view.becomeFirstResponder();
      return YES ; // handled
    }
    
    // handle delete key, set dontForceDeleteKey to allow the default behavior
    // of the delete key.
    if (evt.which === 8){
      evt.dontForceDeleteKey=YES;
    }

    // validate keyDown...
    if (this.performValidateKeyDown(evt)) {
      this._isKeyDown = YES ;
      evt.allowDefault();
    } else {
      evt.stop();
    }

    return YES;
  },

  keyUp: function(evt) {
    
    // The caret/selection could have moved.  In some browsers, though, the
    // element's values won't be updated until after this event is finished
    // processing.
    this.notifyPropertyChange('selection');

    if (this._isKeyDown) {
      this.invokeLater(this.fieldValueDidChange, 1, YES); // notify change
    }
    this._isKeyDown = NO;
    evt.allowDefault();

    return YES;
  },

  mouseDown: function(evt) {
    this._txtFieldMouseDown=YES;
    if (!this.get('isEnabled')) {
      evt.stop();
      return YES;
    } else if((this.value && this.value.length===0) || !this.value) {
      return YES;
    } else {
      // This fixes the double click issue in firefox
      if(SC.browser.mozilla) this.$input()[0].focus();
      return arguments.callee.base.apply(this,arguments);
    }
  },

  mouseUp: function(evt) {
    this._txtFieldMouseDown=NO;
    // The caret/selection could have moved.  In some browsers, though, the
    // element's values won't be updated until after this event is finished
    // processing.
    this.notifyPropertyChange('selection');

    if (!this.get('isEnabled')) {
      evt.stop();
      return YES;
    } else if((this.value && this.value.length===0) || !this.value) {
      this.$input()[0].focus();
      return YES;
    } else return arguments.callee.base.apply(this,arguments);
  },

  selectStart: function(evt) {
    return YES;
  }
}) ;

/* >>>>>>>>>> BEGIN source/mixins/inline_text_field.js */
// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

sc_require('views/text_field') ;

/**
  @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.TextFieldView
  @extends SC.DelegateSupport
  @since SproutCore 1.0
*/
SC.InlineTextFieldView = SC.TextFieldView.extend(SC.DelegateSupport,
/** @scope SC.InlineTextFieldView.prototype */ {

  /**
    Over-write magic number from SC.TextFieldView
  */
  _topOffsetForFirefoxCursorFix: 0,

  /**
    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) {
    if (!options) return;
    
    var layout={}, pane, delLayout, paneElem;
    
    // end existing editing if necessary
    this.beginPropertyChanges();
    if (this.get('isEditing') && !this.blurEditor()) {
      this.endPropertyChanges();  return NO ;
    }

    this._optframe = options.frame ;
    this._optIsCollection = options.isCollection;
    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 ;
    if(this._multiline){
      this.set('isTextArea', YES);
    }else{
    this.set('isTextArea', NO);
    }
    this._commitOnBlur =  (options.commitOnBlur !== undefined) ? options.commitOnBlur : YES ;

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

    this.set('isEditing', YES) ;
    
    // add to window.
    
    pane = this._delegate.pane();

    layout.height = this._optframe.height;
    layout.width=this._optframe.width;
    delLayout = this._delegate.get('layout');
    paneElem = pane.$()[0];
    if(this._optIsCollection && delLayout.left){
      layout.left=this._optframe.x-delLayout.left-paneElem.offsetLeft-1;
      if(SC.browser.msie==7) layout.left--;
    }else{
      layout.left=this._optframe.x-paneElem.offsetLeft-1;
      if(SC.browser.msie==7) layout.left--;
    }
    if(this._optIsCollection && delLayout.top){
      layout.top=this._optframe.y-delLayout.top-paneElem.offsetTop;
      if(SC.browser.msie==7) layout.top=layout.top-2;
    }else{
      layout.top=this._optframe.y-paneElem.offsetTop;
      if(SC.browser.msie==7) layout.top=layout.top-2;  
    }

    this.set('layout', layout);
  
    this.set('parentNode', pane);
    // get style for view.
   
    pane.appendChild(this);
    
    SC.RunLoop.begin().end();
    
    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(this.getFieldValue()) ;

    // allow notifications to go
    
    
    // and become first responder
    this._previousFirstResponder = pane ? pane.get('firstResponder') : null;
   
    this.endPropertyChanges() ;

    this.invokeDelegateMethod(del, 'inlineEditorDidBeginEditing', this) ;
    //if(SC.browser.mozilla)this.invokeOnce(this.becomeFirstResponder) ;
    this.invokeLast(this.becomeFirstResponder) ;

  },
  
  
  /**
    Tries to commit the current value of the field and end editing.  
    
    Do not use this method, use the class method instead.
    
    @returns {Boolean}
  */
  commitEditing: function() {
    // try to validate field.  If it fails, return false.  
    if (!SC.$ok(this.validateSubmit())) return NO ;
    return this._endEditing(this.get('value')) ;
  },
  
  /**
    Tries to discard the current value of the field and end editing.
    
    Do not use this method, use the class method instead.

    @returns {Boolean}
  */
  discardEditing: function() {
    return this._endEditing(this._originalValue) ;
  },
  
  /**
    Invoked whenever the editor loses (or should lose) first responder 
    status to commit or discard editing.
    
    @returns {Boolean}
  */
  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.get('isFirstResponder')) {
      var pane = this.get('pane');
      if (pane && this._previousFirstResponder) {
        pane.makeFirstResponder(this._previousFirstResponder);
      } else this.resignFirstResponder();
    }
    this._previousFirstResponder = null ; // clearout no matter what
    
    if (this.get('parentNode')) this.removeFromParent() ;  
    
    return YES ;
  },
  
  /**
    YES if the editor is currently visible and editing.
  
    @property {Boolean}
  */
  isEditing: NO,
  
  // TODO: make this function work for 1.0
  // /**
  //   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((/ {2}/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 }) ;
  // },
  
  /** @private */
  mouseDown: function(e) {
    arguments.callee.base.call(this, e) ;
    return this.get('isEditing');
  },
  
  /** @private */
  keyDown: function(evt) {
    var ret = this.interpretKeyEvents(evt) ;
    this.fieldValueDidChange(true);
    return !ret ? NO : ret ;
  },
  
  /** @private */
  insertText: null,
  
  //keyUp: function() { return true; },

  // [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.
  /** @private */
  willRemoveFromParent: function() {
    this.$input()[0].blur();
  },
  
  // ask owner to end editing.
  /** @private */
  willLoseFirstResponder: function(responder) {
    if (responder !== this) return;

    // if we're about to lose first responder for any reason other than
    // ending editing, make sure we clear the previous first responder so 
    // isn't cached
    this._previousFirstResponder = null;
    
    // should have been covered by willRemoveFromParent, but this was needed 
    // too.
    this.$input()[0].blur();
    return this.blurEditor() ;
  },

  /**
    invoked when the user presses escape.  Returns true to ignore keystroke
    
    @returns {Boolean}
  */
  cancel: function() { 
    this.discardEditing(); 
    return YES;
  },
  
  // do it here instead of waiting on the binding to make sure the UI
  // updates immediately.
  /** @private */
  fieldValueDidChange: function(partialChange) {
    arguments.callee.base.call(this, partialChange) ;
    //this.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.
  /** @private */
  insertNewline: function(evt) { 
    if (this._multiline) {
      evt.allowDefault();
      return arguments.callee.base.call(this, evt) ;
    } else {
      // TODO : this is a work around. There is a bug where the 
      // last character would get dropped 
      // if the editing was completed by pressing return
      // needs to be fixed
      if (this.get('value') != this.$input().val()) {
        this.set('value', this.$input().val());
      }
      
      
      this.commitEditing() ;
      return YES ;
    }
  },
  
  // Tries to find the next key view when tabbing.  If the next view is 
  // editable, begins editing.
  /** @private */
  insertTab: function(evt) {
    this.resignFirstResponder();
    this.commitEditing() ;
    var next = this._delegate.nextValidKeyView();
    if(next) next.beginEditing();
    return YES ;
  },

  /** @private */
  insertBacktab: function(evt) {
    var prev = this._delegate.previousValidKeyView();
    this.commitEditing() ;
    if(prev) prev.beginEditing();
    return YES ;
  },
  
  /** @private */
  deleteForward: function(evt) {
    evt.allowDefault();
    return YES;
  },
  
  /** @private */
  deleteBackward: function(evt) {
    evt.allowDefault();
    return YES ;
  }
  
});


SC.InlineTextFieldView.mixin(
/** @scope 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) {
    this._exampleElement = options.exampleElement ;
    
    // If exampleInlineTextFieldView is set, load this class otherwise use
    // the default, this.
    var klass = options.exampleInlineTextFieldView 
              ? options.exampleInlineTextFieldView : this;
    
    var layout = options.delegate.get('layout');
    var s = this.updateViewStyle();
    var p = this.updateViewPaddingStyle();
    
    var str= ".inline-editor input{"+s+"} ";
    str= str+".inline-editor textarea{"+s+"} .inline-editor .padding{"+p+"}";
    var pa= document.getElementsByTagName('head')[0] ;
    var el= document.createElement('style');
    el.type= 'text/css';
    el.media= 'screen';
    if(el.styleSheet) el.styleSheet.cssText= str;// IE method
    else el.appendChild(document.createTextNode(str));// others
    pa.appendChild(el);
    
    this.editor = klass.create({ classNames: 'inline-editor', layout: layout}) ;
    return this.editor.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.editor ? this.editor.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.editor ? this.editor.discardEditing() : YES ;  
  },
  
  /** @private */
  updateViewStyle: function() {
    var el = this._exampleElement[0] ;   
    var styles = '';
    var s=SC.getStyle(el,'font-size');
    if(s && s.length>0) styles = styles + "font-size: "+ s + " !important; ";
    s=SC.getStyle(el,'font-family');
    if(s && s.length>0) styles = styles + "font-family: " + s + " !important; ";
    s=SC.getStyle(el,'font-weight');
    if(s && s.length>0) styles = styles + "font-weight: " + s + " !important; ";
    s=SC.getStyle(el,'z-index');
    if(s && s.length>0) styles = styles + "z-index: " + s + " !important; ";
    s=SC.getStyle(el,'line-height');
    if(s && s.length>0) styles = styles + "line-height: " + s + " !important; ";
    s=SC.getStyle(el,'text-align');
    if(s && s.length>0) styles = styles + "text-align: " + s + " !important; ";
    s=SC.getStyle(el,'top-margin');
    if(s && s.length>0) styles = styles + "top-margin: " + s + " !important; ";
    s=SC.getStyle(el,'bottom-margin');
    if(s && s.length>0) styles = styles + "bottom-margin: " + s + " !important; ";
    s=SC.getStyle(el,'left-margin');
    if(s && s.length>0) styles = styles + "left-margin: " + s + " !important; ";
    s=SC.getStyle(el,'right-margin');
    if(s && s.length>0) styles = styles + "right-margin: " + s + " !important; ";
    
    return styles;
  },

  /** @private */
  updateViewPaddingStyle: function() {
    var el = this._exampleElement[0] ;   
    var styles = '';
    var s=SC.getStyle(el,'padding-top');
    if(s && s.length>0) styles = styles + "top: "+ s + " !important; ";
    s=SC.getStyle(el,'padding-bottom');
    if(s && s.length>0) styles = styles + "bottom: " + s + " !important; ";
    s=SC.getStyle(el,'padding-left');
    if(s && s.length>0) styles = styles + "left: " + s + " !important; ";
    s=SC.getStyle(el,'padding-right');
    if(s && s.length>0) styles = styles + "right: " + s + " !important; ";
    
    return styles;
  },

  
  /**
    The current shared inline editor.  This property will often remain NULL
    until you actually begin editing for the first time.
    
    @property {SC.InlineTextFieldView}
  */
  editor: null
  
}) ;
/* >>>>>>>>>> BEGIN source/mixins/selection_support.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @namespace
  
  Implements common selection management properties for controllers.
  
  Selection can be managed by any controller in your applications.  This
  mixin provides some common management features you might want such as
  disabling selection, or restricting empty or multiple selections.
  
  To use this mixin, simply add it to any controller you want to manage 
  selection and call updateSelectionAfterContentChange()
  whenever your source content changes.  You can also override the properties
  defined below to configure how the selection management will treat your 
  content.
  
  This mixin assumes the arrangedObjects property will return an SC.Array of 
  content you want the selection to reflect.
  
  Add this mixin to any controller you want to manage selection.  It is 
  already applied to the CollectionController and ArrayController.
  
  @since SproutCore 1.0
*/
SC.SelectionSupport = {
  
  // ..........................................................
  // PROPERTIES
  // 
  
  /**
    Walk like a duck.
    
    @property {Boolean}
  */
  hasSelectionSupport: YES,
  
  /**
    If YES, selection is allowed. Default is YES.
    
    @property {Boolean}
  */
  allowsSelection: YES,
  
  /**
    If YES, multiple selection is allowed. Default is YES.
    
    @property {Boolean}
  */
  allowsMultipleSelection: YES,
  
  /**
    If YES, allow empty selection Default is YES.
    
    @property {Boolean}
  */
  allowsEmptySelection: YES,
  
  /**
    Override to return the first selectable object.  For example, if you 
    have groups or want to otherwise limit the kinds of objects that can be
    selected.
    
    the default imeplementation returns firstObject property.
    
    @returns {Object} first selectable object
  */
  firstSelectableObject: function() {
    return this.get('firstObject');
  }.property(),
  
  /**
    This is the current selection.  You can make this selection and another
    controller's selection work in concert by binding them together. You
    generally have a master selection that relays changes TO all the others.
    
    @property {SC.SelectionSet}
  */
  selection: function(key, value) {
        
    var old = this._scsel_selection,
        oldlen = old ? old.get('length') : 0,
        content, empty, len;

    // whenever we have to recompute selection, reapply all the conditions to
    // the selection.  This ensures that changing the conditions immediately
    // updates the selection.
    // 
    // Note also if we don't allowSelection, we don't clear the old selection;
    // we just don't allow it to be changed.
    if ((value === undefined) || !this.get('allowsSelection')) value = old ;

    len = (value && value.isEnumerable) ? value.get('length') : 0;
    
    // if we don't allow multiple selection
    if ((len>1) && !this.get('allowsMultipleSelection')) {

      if (oldlen>1) {
        value = SC.SelectionSet.create()
                  .addObject(old.get('firstObject')).freeze();
        len   = 1;
      } else {
        value = old;
        len = oldlen;
      }
    }
    
    // if we don't allow empty selection, block that also.  select first 
    // selectable item if necessary.
    if ((len===0) && !this.get('allowsEmptySelection')) {
      if (oldlen===0) {
        value = this.get('firstSelectableObject');
        if (value) value = SC.SelectionSet.create().addObject(value).freeze();
        else value = SC.SelectionSet.EMPTY;
        len = value.get('length');
        
      } else {
        value = old;
        len = oldlen;
      }
    }
    
    // if value is empty or is not enumerable, then use empty set
    if (len===0) value = SC.SelectionSet.EMPTY;
    
    // always use a frozen copy...
    value = value.frozenCopy();
    this._scsel_selection = value;
    
    return value;
    
  }.property('arrangedObjects', 'allowsEmptySelection', 
      'allowsMultipleSelection', 'allowsSelection').cacheable(),
  
  /**
    YES if the receiver currently has a non-zero selection.
    
    @property {Boolean}
  */
  hasSelection: function() {
    var sel = this.get('selection') ;
    return !!sel && (sel.get('length') > 0) ;
  }.property('selection').cacheable(),
  
  // ..........................................................
  // METHODS
  // 

  /**
    Selects the passed objects in your content.  If you set "extend" to YES,
    then this will attempt to extend your selection as well.
  
    @param {SC.Enumerable} objects objects to select
    @param {Boolean} extend optionally set to YES to extend selection
    @returns {Object} receiver
  */
  selectObjects: function(objects, extend) {

    // handle passing an empty array
    if (!objects || objects.get('length')===0) {
      if (!extend) this.set('selection', SC.SelectionSet.EMPTY);
      return this;
    }
    
    var sel = this.get('selection');
    if (extend && sel) sel = sel.copy();
    else sel = SC.SelectionSet.create();
    
    sel.addObjects(objects).freeze();
    this.set('selection', sel);
    return this ;
  },
  
  /**
    Selects a single passed object in your content.  If you set "extend" to 
    YES then this will attempt to extend your selection as well.
    
    @param {Object} object object to select
    @param {Boolean} extend optionally set to YES to extend selection
    @returns {Object} receiver
  */
  selectObject: function(object, extend) {
    if (object === null) {
      if (!extend) this.set('selection', null);
      return this ;
      
    } else return this.selectObjects([object], extend);
  },    

  /**
    Deselects the passed objects in your content.
    
    @param {SC.Enumerable} objects objects to select
    @returns {Object} receiver
  */
  deselectObjects: function(objects) {

    if (!objects || objects.get('length')===0) return this; // nothing to do
    
    var sel = this.get('selection');
    if (!sel || sel.get('length')===0) return this; // nothing to do

    // find index for each and remove it
    sel = sel.copy().removeObjects(objects).freeze();
    this.set('selection', sel.freeze());
    return this ;
  },
  
  /**
    Deselects the passed object in your content.
    
    @param {SC.Object} object single object to select
    @returns {Object} receiver
  */
  deselectObject: function(object) {
    if (!object) return this; // nothing to do
    else return this.deselectObjects([object]);
  },
  
  /**  @private
    Call this method whenever your source content changes to ensure the 
    selection always remains up-to-date and valid.
  */
  updateSelectionAfterContentChange: function() {
    
    var content = this.get('arrangedObjects'),
        sel     = this.get('selection'),
        ret     = sel,
        indexes, len, max;

    // first, make sure selection goes beyond current items...
    if (ret && content && ret.get('sources').indexOf(content)>=0) {
      indexes = ret.indexSetForSource(content);
      len     = content.get('length') ;
      max     = indexes ? indexes.get('max') : 0;
      
      // to clean this up, just copy the selection and remove the extra 
      // indexes
      if (max > len) {
        ret = ret.copy().remove(content, len, max-len).freeze();
        this.set('selection', ret);
      }
    }

    // if we didn't have to recompute the selection anyway, do a quick check
    // to make sure there are no constraints that need to be recomputed.
    if (ret === sel) {
      len = sel ? sel.get('length') : 0;
      max = content ? content.get('length') : 0;
      
      // need to recompute if the selection is empty and it shouldn't be but
      // only if we have some content; otherwise what's the point?
      if ((len===0) && !this.get('allowsEmptySelection') && max>0) {
        this.notifyPropertyChange('selection');
      }
    }

    return this ;
  }
    
};

/* >>>>>>>>>> BEGIN source/mixins/tree_item_content.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @namespace

  A tree item is a model object that acts as a node in a tree-like data 
  structure such as a hierarchy of folders or outline of items.  This mixin 
  can be applied to tree item model objects to customize the way the tree
  information is extracted from the object.

  h2. Basic Implementation
  
  If you add this mixin, you must implement the treeItemChildren property so
  that it returns the current array of child tree items for the receiver.  If
  you do not implement this property the tree item will not function.
  
  h2. Optimizing Branches
  
  The most common use of this mixin is to override the treeItemBranchIndexes
  property to return an index set of child items that are themselves branches
  in the tree.  Normally the TreeController will need to walk every item in
  your list to determine these branch items.  However by implementing this 
  method yourself, you can provide a result faster.
  
  If none of your child items are branches, override this property to return
  null or an empty index set.
  
  @since SproutCore 1.0
*/
SC.TreeItemContent = {

  /** 
    Walk like a duck. 
    
    @property {Boolean}
  */
  isTreeItemContent: YES,
  
  /**
    Property returns the children for this tree item.  The default simply 
    returns null.  If you implement this mixin, you MUST implement this 
    property to return the actual tree item children for the item.
   
    @property {SC.Array}
  */
  treeItemChildren: null,

  /**
    The default property used to determine if the tree item is expanded.  You
    can implement you model object to update this property or you can override
    treeItemDisclosureState() to compute the disclosure state however you 
    want.
    
    @property {Boolean}
  */
  treeItemIsExpanded: YES,
  
  /**
    Indicates whether the tree item should be rendered as a group or not. 
    This property is only useful on the root item in your tree.  Setting it to
    YES on any other item will be ignored.
    
    @property {Boolean}
  */
  treeItemIsGrouped: NO,
  
  /**
    Returns the disclosure state for the tree item, which appears at the 
    index of the parent's treeItemChildren array.  The response must be one of 
    SC.BRANCH_OPEN, SC.BRANCH_CLOSED or SC.LEAF_NODE.
     
    If the parent parameter is null, then this item is part of the root 
    children array.
    
    This method will only be called for tree items that have children.  Tree
    items with no children are assumed to be leaf nodes.

    The default implementation uses the treeItemIsExpanded property to 
    determine if the item should be open or closed.
    
    @param {Object} parent the parent item containing this item
    @param {Number} idx the index of the item in the parent
    @returns {Number} branch state
  */
  treeItemDisclosureState: function(parent, idx) {
    return this.get('treeItemIsExpanded') ? SC.BRANCH_OPEN : SC.BRANCH_CLOSED;
  },
  
  /**
    Collapse the tree item.  The default implementation will change the 
    treeItemIsExpanded property, but you can override this method to handle
    collapsing anyway you like.
    
    @param {Object} parent the parent item containing this item
    @param {Number} idx the index of the item in the parent
    @returns {void}
  */
  treeItemCollapse: function(parent, idx) {
    this.setIfChanged('treeItemIsExpanded', NO);    
  },

  /**
    Expand the tree item.  The default implementation will change the 
    treeItemIsExpanded property, but you can override this method to handle
    collapsing anyway you like.
    
    @param {Object} parent the parent item containing this item
    @param {Number} idx the index of the item in the parent
    @returns {void}
  */
  treeItemExpand: function(parent, idx) {
    this.setIfChanged('treeItemIsExpanded', YES);    
  },
  
  /**
    Returns an index set containing the child indexes of the item that are 
    themselves branches.  This will only be called on tree items with a branch
    disclosure state.

    If the passed parent and index are both null, then the receiver is the 
    root node in the tree.
    
    The default implementation iterates over the item's children to get the
    disclosure state of each one.  Child items with a branch disclosure state
    will have their index added to the return index set.  
    
    You may want to override this method to provide a more efficient 
    implementation if you are working with large data sets and can infer which
    children are branches without iterating over each one.

    If you know for sure that all of the child items for this item are leaf
    nodes and not branches, simply override this method to return null.
    
    @param {Object} parent the parent item containing this item
    @param {Number} index the index of the item in the parent
    @returns {SC.IndexSet} branch indexes
  */
  treeItemBranchIndexes: function(parent, index) {
    var children = this.get('treeItemChildren'),
        ret, lim, idx, item;
        
    if (!children) return null ; // nothing to do
    
    ret = SC.IndexSet.create();
    lim = children.get('length');
    for(idx=0;idx<lim;idx++) {
      if (!(item = children.objectAt(idx))) continue;
      if (!item.get('treeItemChildren')) continue;
      if (item.treeItemDisclosureState(this,idx)!==SC.LEAF_NODE) ret.add(idx);
    }

    return ret.get('length')>0 ? ret : null;
  }
  
};
/* >>>>>>>>>> BEGIN source/panes/pane.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/view');

/** @class
  A Pane is like a regular view except that it does not need to live within a 
  parent view.  You usually use a Pane to form the root of a view hierarchy in 
  your application, such as your main application view or for floating 
  palettes, popups, menus, etc.
  
  Usually you will not work directly with the SC.Pane class, but with one of 
  its subclasses such as SC.MainPane, SC.Panel, or SC.PopupPane.

  h1. Showing a Pane
  
  To make a pane visible, you need to add it to your HTML document.  The 
  simplest way to do this is to call the append() method:
  
  {{{
     myPane = SC.Pane.create();
     myPane.append(); // adds the pane to the document
  }}}
  
  This will insert your pane into the end of your HTML document body, causing 
  it to display on screen.  It will also register your pane with the 
  SC.RootResponder for the document so you can start to receive keyboard, 
  mouse, and touch events.
  
  If you need more specific control for where you pane appears in the 
  document, you can use several other insertion methods such as appendTo(), 
  prependTo(), before() and after().  These methods all take a an element to 
  indicate where in your HTML document you would like you pane to be inserted.
  
  Once a pane is inserted into the document, it will be sized and positioned 
  according to the layout you have specified.  It will then automatically 
  resize with the window if needed, relaying resize notifications to children 
  as well.
  
  h1. Hiding a Pane
  
  When you are finished with a pane, you can hide the pane by calling the 
  remove() method.  This method will actually remove the Pane from the 
  document body, as well as deregistering it from the RootResponder so that it 
  no longer receives events.
  
  The isVisibleInWindow method will also change to NO for the Pane and all of 
  its childViews and the views will no longer have their updateDisplay methods 
  called.  
  
  You can readd a pane to the document again any time in the future by using 
  any of the insertion methods defined in the previous section.
  
  h1. Receiving Events
  
  Your pane and its child views will automatically receive any mouse or touch 
  events as long as it is on the screen.  To receive keyboard events, however, 
  you must focus the keyboard on your pane by calling makeKeyPane() on the 
  pane itself.  This will cause the RootResponder to route keyboard events to 
  your pane.  The pane, in turn, will route those events to its current 
  keyView, if there is any.
  
  Note that all SC.Views (anything that implements SC.ClassicResponder, 
  really) will be notified when it is about or gain or lose keyboard focus.  
  These notifications are sent both when the view is made keyView of a 
  particular pane and when the pane is made keyPane for the entire 
  application.
  
  You can prevent your Pane from becoming key by setting the acceptsKeyPane 
  to NO on the pane.  This is useful when creating palettes and other popups 
  that should not steal keyboard control from another view.

  @extends SC.View
  @extends SC.ResponderContext
  @since SproutCore 1.0
*/
SC.Pane = SC.View.extend( /** @scope SC.Pane.prototype */ {

  /** 
    Returns YES for easy detection of when you reached the pane. 
    @property {Boolean}
  */
  isPane: YES,
  
  /** 
    Set to the current page when the pane is instantiated from a page object.
    @property {SC.Page}
  */
  page: null,
  
  // .......................................................
  // ROOT RESPONDER SUPPORT
  //

  /**
    The rootResponder for this pane.  Whenever you add a pane to a document, 
    this property will be set to the rootResponder that is now forwarding 
    events to the pane.
    
    @property {SC.Responder}
  */
  rootResponder: null,  
  
  /** 
    Last known window size. 
    
    @property {Rect}
  */
  currentWindowSize: null,
  
  /** 
    The parent dimensions are always the last known window size. 
    
    @returns {Rect} current window size 
  */
  computeParentDimensions: function(frame) {
    var wframe = this.get('currentWindowSize');
    var wDim = {x: 0, y: 0, width: 1000, height: 1000};
    if (wframe){
      wDim.width = wframe.width;
      wDim.height = wframe.height;
    }
    // Call the RootResponder instance...
    else if (SC.RootResponder.responder) {
      var wSize = SC.RootResponder.responder.get('currentWindowSize');
      if (wSize){
        wDim.width = wSize.width;
        wDim.height = wSize.height;
      }
    }
    // If all else fails then we need to Calculate it from the window size and DOM
    else {
      if (window.innerHeight) {
        wDim.width = window.innerWidth;
        wDim.height = window.innerHeight;
      } else if (document.documentElement && document.documentElement.clientHeight) {
        wDim.width = document.documentElement.clientWidth;
        wDim.height = document.documentElement.clientHeight; 
      } else if (document.body) {
        wDim.width = document.body.clientWidth;
        wDim.height = document.body.clientHeight;
      }
      this.windowSizeDidChange(null, wDim);
    }    
    return wDim;
  },
    
  /** @private Disable caching due to an known bug in SC. */
  frame: function() {
    return this.computeFrameWithParentFrame(null) ;
  }.property(),
  
  /** 
    Invoked by the root responder whenever the window resizes.  This should
    simply begin the process of notifying children that the view size has
    changed, if needed.
    
    @param {Rect} oldSize the old window size
    @param {Rect} newSize the new window size
    @returns {SC.Pane} receiver
  */
  windowSizeDidChange: function(oldSize, newSize) {
    this.set('currentWindowSize', newSize) ;
    this.parentViewDidResize(); // start notifications.
    return this ;
  },
  
  /**
    Attempts to send the event down the responder chain for this pane.  If you 
    pass a target, this method will begin with the target and work up the 
    responder chain.  Otherwise, it will begin with the current firstResponder 
    and walk up the chain looking for any responder that implements a handler 
    for the passed method and returns YES when executed.
    
    @param {String} action
    @param {SC.Event} evt
    @param {Object} target
    @returns {Object} object that handled the event
  */
  sendEvent: function(action, evt, target) {
    var handler ;
    
    // walk up the responder chain looking for a method to handle the event
    if (!target) target = this.get('firstResponder') ;
    while(target && !target.tryToPerform(action, evt)) {

      // even if someone tries to fill in the nextResponder on the pane, stop
      // searching when we hit the pane.
      target = (target === this) ? null : target.get('nextResponder') ;
    }
    
    // if no handler was found in the responder chain, try the default
    if (!target && (target = this.get('defaultResponder'))) {
      if (typeof target === SC.T_STRING) {
        target = SC.objectForPropertyPath(target);
      }

      if (!target) target = null;
      else if (target.isResponderContext) {
        target = target.sendAction(action, this, evt);
      } else target = target.tryToPerform(action, evt) ? target : null ;
    }
        
    return evt.mouseHandler || target ;
  },

  performKeyEquivalent: function(keystring, evt) {
    var ret = arguments.callee.base.apply(this,arguments) ; // try normal view behavior first
    if (!ret) {
      var defaultResponder = this.get('defaultResponder') ;
      if (defaultResponder) {
        // try default responder's own performKeyEquivalent method,
        // if it has one...
        if (defaultResponder.performKeyEquivalent) {
          ret = defaultResponder.performKeyEquivalent(keystring, evt) ;
        }
        
        // even if it does have one, if it doesn't handle the event, give
        // methodName-style key equivalent handling a try
        if (!ret) {
          ret = defaultResponder.tryToPerform(keystring, evt) ;
        }
      }
    }
    return ret ;
  },

  // .......................................................
  // HANDLE FIRST RESPONDER AND KEY RESPONDER STATUS
  //

  /**
    The default responder.  Set this to point to a responder object that can 
    respond to events when no other view in the hierarchy handles them.
    
    @property {SC.Responder}
  */
  defaultResponder: null,
  
  /**
    Pane's never have a next responder
    
    @property {SC.Responder}
    @readOnly
  */
  nextResponder: function() {
    return null;
  }.property().cacheable(),
  
  /**
    The first responder.  This is the first view that should receive action 
    events.  Whenever you click on a view, it will usually become 
    firstResponder. 
    
    @property {SC.Responder}
  */
  firstResponder: null,
  
  /** 
    If YES, this pane can become the key pane.  You may want to set this to NO 
    for certain types of panes.  For example, a palette may never want to 
    become key.  The default value is YES.
    
    @property {Boolean}
  */
  acceptsKeyPane: YES,
  
  /**
    This is set to YES when your pane is currently the target of key events. 
    
    @property {Boolean}
  */
  isKeyPane: NO,

  /**
    Make the pane receive key events.  Until you call this method, the 
    keyView set for this pane will not receive key events. 
  
    @returns {SC.Pane} receiver
  */
  becomeKeyPane: function() {
    if (this.get('isKeyPane')) return this ;
    if (this.rootResponder) this.rootResponder.makeKeyPane(this) ;
    return this ;
  },
  
  /**
    Remove the pane view status from the pane.  This will simply set the 
    keyPane on the rootResponder to null.
    
    @returns {SC.Pane} receiver
  */
  resignKeyPane: function() {
    if (!this.get('isKeyPane')) return this ;
    if (this.rootResponder) this.rootResponder.makeKeyPane(null);
    return this ;
  },
  
  /**
    Makes the passed view (or any object that implements SC.Responder) into 
    the new firstResponder for this pane.  This will cause the current first
    responder to lose its responder status and possibly keyResponder status as
    well.
    
    @param {SC.View} view
    @returns {SC.Pane} receiver
  */
  makeFirstResponder: function(view) {
    var current=this.get('firstResponder'), isKeyPane=this.get('isKeyPane');
    if (current === view) return this ; // nothing to do
    
    // notify current of firstResponder change
    if (current) current.willLoseFirstResponder(current);
    
    // if we are currently key pane, then notify key views of change also
    if (isKeyPane) {
      if (current) current.willLoseKeyResponderTo(view) ;
      if (view) view.willBecomeKeyResponderFrom(current) ;
    }
    
    // change setting
    if (current) {
      current.beginPropertyChanges()
        .set('isFirstResponder', NO).set('isKeyResponder', NO)
      .endPropertyChanges();
    }

    this.set('firstResponder', view) ;
    
    if (view) {
      view.beginPropertyChanges()
        .set('isFirstResponder', YES).set('isKeyResponder', isKeyPane)
      .endPropertyChanges();
    }
    
    // and notify again if needed.
    if (isKeyPane) {
      if (view) view.didBecomeKeyResponderFrom(current) ; 
      if (current) current.didLoseKeyResponderTo(view) ;
    }
    
    if (view) view.didBecomeFirstResponder(view);
    return this ;
  },
  
  /** @private method forwards status changes in a generic way. */
  _forwardKeyChange: function(shouldForward, methodName, pane, isKey) {
    var keyView, responder, newKeyView;
    if (shouldForward && (responder = this.get('firstResponder'))) {
      newKeyView = (pane) ? pane.get('firstResponder') : null ;
      keyView = this.get('firstResponder') ;
      if (keyView) keyView[methodName](newKeyView);
      
      if ((isKey !== undefined) && responder) {
        responder.set('isKeyResponder', isKey);
      }
    } 
  },
  
  /**
    Called just before the pane loses it's keyPane status.  This will notify 
    the current keyView, if there is one, that it is about to lose focus, 
    giving it one last opportunity to save its state. 
    
    @param {SC.Pane} pane
    @returns {SC.Pane} reciever
  */
  willLoseKeyPaneTo: function(pane) {
    this._forwardKeyChange(this.get('isKeyPane'), 'willLoseKeyResponderTo', pane, NO);
    return this ;
  },
  
  /**
    Called just before the pane becomes keyPane.  Notifies the current keyView 
    that it is about to gain focus.  The keyView can use this opportunity to 
    prepare itself, possibly stealing any value it might need to steal from 
    the current key view.
    
    @param {SC.Pane} pane
    @returns {SC.Pane} receiver
  */
  willBecomeKeyPaneFrom: function(pane) {
    this._forwardKeyChange(!this.get('isKeyPane'), 'willBecomeKeyResponderFrom', pane, YES);
    return this ;
  },


  /**
    Called just after the pane has lost its keyPane status.  Notifies the 
    current keyView of the change.  The keyView can use this method to do any 
    final cleanup and changes its own display value if needed.
    
    @param {SC.Pane} pane
    @returns {SC.Pane} reciever
  */
  didLoseKeyPaneTo: function(pane) {
    var isKeyPane = this.get('isKeyPane');
    this.set('isKeyPane', NO);
    this._forwardKeyChange(isKeyPane, 'didLoseKeyResponderTo', pane);
    return this ;
  },
  
  /**
    Called just after the keyPane focus has changed to the receiver.  Notifies 
    the keyView of its new status.  The keyView should use this method to 
    update its display and actually set focus on itself at the browser level 
    if needed.
    
    @param {SC.Pane} pane
    @returns {SC.Pane} receiver

  */
  didBecomeKeyPaneFrom: function(pane) {
    var isKeyPane = this.get('isKeyPane');
    this.set('isKeyPane', YES);
    this._forwardKeyChange(!isKeyPane, 'didBecomeKeyResponderFrom', pane, YES);
    return this ;
  },
  
  // .......................................................
  // MAIN PANE SUPPORT
  //
  
  /**
    Returns YES whenever the pane has been set as the main pane for the 
    application.
    
    @property {Boolean}
  */
  isMainPane: NO,
  
  /**
    Invoked when the pane is about to become the focused pane.  Override to
    implement your own custom handling.
    
    @param {SC.Pane} pane the pane that currently have focus
    @returns {void}
  */
  focusFrom: function(pane) {},
  
  /**
    Invoked when the the pane is about to lose its focused pane status.  
    Override to implement your own custom handling
    
    @param {SC.Pane} pane the pane that will receive focus next
    @returns {void}
  */
  blurTo: function(pane) {},
  
  /**
    Invoked when the view is about to lose its mainPane status.  The default 
    implementation will also remove the pane from the document since you can't 
    have more than one mainPane in the document at a time.
    
    @param {SC.Pane} pane
    @returns {void}
  */
  blurMainTo: function(pane) {
    this.set('isMainPane', NO) ;
  },
  
  /** 
    Invokes when the view is about to become the new mainPane.  The default 
    implementation simply updates the isMainPane property.  In your subclass, 
    you should make sure your pane has been added to the document before 
    trying to make it the mainPane.  See SC.MainPane for more information.
    
    @param {SC.Pane} pane
    @returns {void}
  */
  focusMainFrom: function(pane) {
    this.set('isMainPane', YES);
  },
  
  // .......................................................
  // ADDING/REMOVE PANES TO SCREEN
  //  
  
  /**
    Inserts the pane at the end of the document.  This will also add the pane 
    to the rootResponder.
    
    @param {SC.RootResponder} rootResponder
    @returns {SC.Pane} receiver
  */
  append: function() {
    return this.appendTo(document.body) ;
  },
  
  /**
    Removes the pane from the document.  This will remove the
    DOM node and deregister you from the document window.
    
    @returns {SC.Pane} receiver
  */
  remove: function() {
    if (!this.get('isVisibleInWindow')) return this ; // nothing to do
    if (!this.get('isPaneAttached')) return this ; // nothing to do
    
    // remove layer...
    var dom = this.get('layer') ;
    if (dom.parentNode) dom.parentNode.removeChild(dom) ;
    dom = null ;
    
    // resign keyPane status, if we had it
    this.resignKeyPane();
    
    // remove the pane
    var rootResponder = this.rootResponder ;
    if (this.get('isMainPane')) rootResponder.makeMainPane(null) ;
    rootResponder.panes.remove(this) ;
    this.rootResponder = null ;
    
    // clean up some of my own properties
    this.set('isPaneAttached', NO) ;
    this.parentViewDidChange() ;
    return this ;
  },
  
  /**
    Inserts the pane into the DOM as the last child of the passed DOM element. 
    You can pass in either a CoreQuery object or a selector, which will be 
    converted to a CQ object.  You can optionally pass in the rootResponder 
    to use for this operation.  Normally you will not need to pass this as 
    the default responder is suitable.
    
    @param {DOMElement} elem the element to append to
    @returns {SC.Pane} receiver
  */
  appendTo: function(elem) {
    var layer = this.get('layer');
    if (!layer) layer =this.createLayer().get('layer'); 
    
    if (this.get('isPaneAttached') && (layer.parentNode === elem)) {
      return this; // nothing to do
    }
    
    elem.insertBefore(layer, null); // add to DOM
    elem = layer = null ;

    return this.paneDidAttach(); // do the rest of the setup
  },

  /** 
    inserts the pane's rootElement into the top of the passed DOM element.
    
    @param {DOMElement} elem the element to append to
    @returns {SC.Pane} receiver
  */
  prependTo: function(elem) {
    if (this.get('isPaneAttached')) return this;
    
    var layer = this.get('layer');
    if (!layer) layer =this.createLayer().get('layer'); 
    
    if (this.get('isPaneAttached') && (layer.parentNode === elem)) {
      return this; // nothing to do
    }
    
    elem.insertBefore(layer, elem.firstChild); // add to DOM
    elem = layer = null ;

    return this.paneDidAttach(); // do the rest of the setup
  },

  /** 
    inserts the pane's rootElement into the hierarchy before the passed 
    element.
    
    @param {DOMElement} elem the element to append to
    @returns {SC.Pane} receiver
  */
  before: function(elem) {
    if (this.get('isPaneAttached')) return this;
    
    var layer = this.get('layer');
    if (!layer) layer =this.createLayer().get('layer');
    
    var parent = elem.parentNode ; 

    if (this.get('isPaneAttached') && (layer.parentNode === parent)) {
      return this; // nothing to do
    }
    
    parent.insertBefore(layer, elem); // add to DOM
    parent = elem = layer = null ;

    return this.paneDidAttach(); // do the rest of the setup
  },

  /** 
    inserts the pane's rootElement into the hierarchy after the passed 
    element.
    
    @param {DOMElement} elem the element to append to
    @returns {SC.Pane} receiver
  */
  after: function(elem) {
    
    var layer = this.get('layer');
    if (!layer) layer =this.createLayer().get('layer'); 
    
    var parent = elem.parentNode ;
  
    if (this.get('isPaneAttached') && (layer.parentNode === parent)) {
      return this; // nothing to do
    }
    
    parent.insertBefore(layer, elem.nextSibling); // add to DOM
    parent = elem = layer = null ;

    return this.paneDidAttach(); // do the rest of the setup
  },
  
  /**
    This method has no effect in the pane.  Instead use remove().
    
    @returns {void}
  */
  removeFromParent: function() { },
  
  /** @private
    Called when the pane is attached to a DOM element in a window, this will 
    change the view status to be visible in the window and also register 
    with the rootResponder.
  */
  paneDidAttach: function() {

    // hook into root responder
    var responder = (this.rootResponder = SC.RootResponder.responder);
    responder.panes.add(this);
  
    // set currentWindowSize
    this.set('currentWindowSize', responder.computeWindowSize()) ;
    
    // update my own location
    this.set('isPaneAttached', YES) ;
    this.parentViewDidChange() ;
    
    return this ;
  },
  
  /**
    YES when the pane is currently attached to a document DOM.  Read only.
    
    @property {Boolean}
    @readOnly
  */
  isPaneAttached: NO,

  /**
    Updates the isVisibleInWindow state on the pane and its childViews if 
    necessary.  This works much like SC.View's default implementation, but it
    does not need a parentView to function.
    
    @param {Boolean} parentViewIsVisible (ignored)
    @returns {SC.Pane} receiver
  */
  recomputeIsVisibleInWindow: function(parentViewIsVisible) {
    var last = this.get('isVisibleInWindow') ;
    var cur = this.get('isPaneAttached') && this.get('isVisible') ;

    // if the state has changed, update it and notify children
    if (last !== cur) {
      this.set('isVisibleInWindow', cur) ;
      
      // if we just became visible, update layer + layout if needed...
      if (cur && this.get('layerNeedsUpdate')) this.updateLayerIfNeeded();
      if (cur && this.get('childViewsNeedLayout')) this.layoutChildViewsIfNeeded();
      
      var childViews = this.get('childViews'), len = childViews.length, idx;
      for(idx=0;idx<len;idx++) {
        childViews[idx].recomputeIsVisibleInWindow(cur);
      }
      
      // if we were firstResponder, resign firstResponder also if no longer
      // visible.
      if (!cur && this.get('isFirstResponder')) this.resignFirstResponder();
    }
    
    // if we just became visible, update layer + layout if needed...
    if (cur) {
      if (this.parentViewDidResize) this.parentViewDidResize();
      
      if (this.get('childViewsNeedLayout')) {
        this.invokeOnce(this.layoutChildViewsIfNeeded);
      }
    }
    
    return this ;
  },
  
  /** @private */
  updateLayerLocation: function() {
    // note: the normal code here to update node location is removed 
    // because we don't need it for panes.
    return this ; 
  },

  /** @private */
  init: function() {
    // if a layer was set manually then we will just attach to existing 
    // HTML.
    var hasLayer = !!this.get('layer') ;
    arguments.callee.base.apply(this,arguments) ;
    if (hasLayer) this.paneDidAttach();
  },

  /** @private */
  classNames: 'sc-pane'.w()
  
}) ;


/* >>>>>>>>>> BEGIN source/private/tree_item_observer.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('mixins/tree_item_content');
sc_require('mixins/collection_content');

/** 
  @ignore
  @class
  
  A TreeNode is an internal class that will manage a single item in a tree
  when trying to display the item in a hierarchy. 
  
  When displaying a tree of objects, a tree item object will be nested to 
  cover every object that might have child views.
  
  TreeNode stores an array which contains either a number pointing to the 
  next place in the array there is a child item or it contains a child item.
  
  @extends SC.Object
  @extends SC.Array
  @extends SC.CollectionContent
  @since SproutCore 1.0
*/
SC.TreeItemObserver = SC.Object.extend(SC.Array, SC.CollectionContent, {

  /**
    The node in the tree this observer will manage.  Set when creating the
    object.  If you are creating an observer manually, you must set this to
    a non-null value.
  */
  item: null,

  /**
    The controller delegate.  If the item does not implement the 
    TreeItemContent method, delegate properties will be used to determine how
    to access the content.  Set automatically when a tree item is created.
    
    If you are creating an observer manually, you must set this to a non-null
    value.
  */
  delegate: null,
  
  // ..........................................................
  // FOR NESTED OBSERVERS
  // 
  
  /**
    The parent TreeItemObserver for this observer.  Must be set on create.
  */
  parentObserver: null,

  /**
    The parent item for the observer item.  Computed automatically from the 
    parent.  If the value of this is null, then this is the root of the tree.
  */
  parentItem: function() {
    var p = this.get('parentObserver');
    return p ? p.get('item') : null;
  }.property('parentObserver').cacheable(),
  
  /**
    Index location in parent's children array.  If this is the root item
    in the tree, should be null.
  */
  index: null,
  
  outlineLevel: 0, 
  
  // ..........................................................
  // EXTRACTED FROM ITEM
  // 
  
  /**
    Array of child tree items.  Extracted from the item automatically on init.
  */
  children: null,
  
  /**
    Disclosure state of this item.  Must be SC.BRANCH_OPEN or SC.BRANCH_CLOSED
    If this is the root of a item tree, the observer will have children but
    no parent or parent item.  IN this case the disclosure state is always
    SC.BRANCH_OPEN.
    
    @property
    @type Number
  */
  disclosureState: SC.BRANCH_OPEN,

  /**
    IndexSet of children with branches.  This will ask the delegate to name 
    these indexes.  The default implementation will iterate over the children
    of the item but a more optimized version could avoid touching each item.
    
    @property
    @type SC.IndexSet
  */
  branchIndexes: function() {
    var item = this.get('item'), 
        len, pitem, idx, children, ret;
    
    // no item - no branches
    if (!item) return SC.IndexSet.EMPTY;
    
    // if item is treeItemContent then ask it directly
    else if (item.isTreeItemContent) {
      pitem  = this.get('parentItem');
      idx    = this.get('index') ;
      return item.treeItemBranchIndexes(pitem, idx);
      
    // otherwise, loop over children and determine disclosure state for each
    } else {
      children = this.get('children');
      if (!children) return null; // no children - no branches
      ret = SC.IndexSet.create();
      len = children.get('length');
      pitem = item ; // save parent
      
      for(idx=0;idx<len;idx++) {
        if (!(item = children.objectAt(idx))) continue ;
        if (!this._computeChildren(item, pitem, idx)) continue; // no chil'en
        if (this._computeDisclosureState(item, pitem, idx) !== SC.LEAF_NODE) {
          ret.add(idx);
        }
      }

      return ret.get('length')>0 ? ret : null;
    }
  }.property('children').cacheable(),
  
  /**
    Returns YES if the item itself should be shown, NO if only its children
    should be shown.  Normally returns YES unless the parentObject is null.
  */
  isHeaderVisible: function() {
    return !!this.get('parentObserver');
  }.property('parentObserver').cacheable(),
  
  /**
    Get the current length of the tree item including any of its children.
  */
  length: 0,
  
  // ..........................................................
  // SC.ARRAY SUPPORT
  // 
  
  /**
    Get the object at the specified index.  This will talk the tree info
    to determine the proper place.  The offset should be relative to the 
    start of this tree item.  Calls recursively down the tree.
    
    This should only be called with an index you know is in the range of item
    or its children based on looking at the length.
  */
  objectAt: function(index) {
    var len   = this.get('length'),
        item  = this.get('item'), 
        cache = this._objectAtCache,
        cur   = index,
        loc   = 0,
        indexes, children;
     
    if (index >= len) return undefined;
    if (this.get('isHeaderVisible')) {
      if (index === 0) return item;
      else cur--;
    }
    item = null; 

    if (!cache) cache = this._objectAtCache = [];
    if ((item = cache[index]) !== undefined) return item ;

    children = this.get('children');
    if (!children) return undefined; // no children - nothing to get
    
    // loop through branch indexes, reducing the offset until it matches 
    // something we might actually return.
    if (indexes = this.get('branchIndexes')) {
      indexes.forEach(function(i) {
        if (item || (i > cur)) return ; // past end - nothing to do

        var observer = this.branchObserverAt(i), len;
        if (!observer) return ; // nothing to do

        // if cur lands inside of this observer's length, use objectAt to get
        // otherwise, just remove len from cur.
        len = observer.get('length') ;
        if (i+len > cur) {
          item = observer.objectAt(cur-i);
          cur  = -1;
        } else cur -= len-1 ;
        
      },this);
    }
    
    if (cur>=0) item = children.objectAt(cur); // get internal if needed
    cache[index] = item ; // save in cache 
    
    return item ;
  },

  /**
    Implements SC.Array.replace() primitive.  For this method to succeed, the
    range you replace must lie entirely within the same parent item, otherwise
    this will raise an exception.
    
    h3. The Operation Parameter
    
    Note that this replace method accepts an additional parameter "operation"
    which is used when you try to insert an item on a boundary between 
    branches whether it should be inserted at the end of the previous group
    after the group.  If you don't pass operation, the default is 
    SC.DROP_BEFORE, which is the expected behavior.
    
    Even if the operation is SC.DROP_AFTER, you should still pass the actual
    index where you expect the item to be inserted.  For example, if you want
    to insert AFTER the last index of an 3-item array, you would still call:
    
    {{{
      observer.replace(3, 0, [object1 .. objectN], SC.DROP_AFTER)
    }}}
    
    The operation is simply used to disambiguate whether the insertion is
    intended to be AFTER the previous item or BEFORE the items you are
    replacing.
    
    @param {Number} start the starting index
    @param {Number} amt the number of items to replace
    @param {SC.Array} objects array of objects to insert
    @param {Number} operation either SC.DROP_BEFORE or SC.DROP_AFTER
    @returns {SC.TreeItemObserver} receiver
  */
  replace: function(start, amt, objects, operation) {

    var cur      = start,
        observer = null,
        indexes, len, max;
        
    if (operation === undefined) operation = SC.DROP_BEFORE;
    
    // adjust the start location based on branches, possibly passing on to an
    // observer.
    if (this.get('isHeaderVisible')) cur--; // exclude my own header item 
    if (cur < 0) throw "Tree Item cannot replace itself";

    // remove branch lengths.  If the adjusted start location lands inside of
    // another branch, then just let that observer handle it.
    if (indexes = this.get('branchIndexes')) {
      indexes.forEach(function(i) {
        if (observer || (i>=cur)) return ; // nothing to do
        if (!(observer = this.branchObserverAt(i))) return; // nothing to do
        len = observer.get('length');
        
        // if this branch range is before the start loc, just remove it and 
        // go on.  If cur is somewhere inside of the range, then save to pass
        // on.  Note use of operation to determine the abiguous end op.
        if ((i+len === cur) && operation === SC.DROP_AFTER) cur -= i;
        else if (i+len > cur) cur -= i; // put inside of nested range
        else {
          cur -= len-1; observer = null ;
        }
      }, this);      
    }
      
    // if an observer was saved, pass on call.
    if (observer) {
      observer.replace(cur, amt, objects, operation);
      return this;
    }
    
    // no observer was saved, which means cur points to an index inside of
    // our own range.  Now amt just needs to be adjusted to remove any
    // visible branches as well.
    max = cur + amt;
    if (amt>1 && indexes) { // if amt is 1 no need...
      indexes.forEachIn(cur, indexes.get('max')-cur, function(i) {
        if (i > max) return; // nothing to do
        if (!(observer = this.branchObserverAt(i))) return; // nothing to do
        len = observer.get('length');
        max -= len-1;
      }, this);
    }
    
    // get amt back out.  if amt is negative, it means that the range passed
    // was not cleanly inside of this range.  raise an exception.
    amt = max-cur; 
    
    // ok, now that we are adjusted, get the children and forward the replace
    // call on.  if there are no children, bad news...
    var children = this.get('children');
    if (!children) throw "cannot replace() tree item with no children";

    if ((amt < 0) || (max>children.get('length'))) {
      throw "replace() range must lie within a single tree item";
    }

    children.replace(cur, amt, objects, operation);
    
    // don't call enumerableContentDidChange() here because, as an observer,
    // we should be notified by the children array itself.
    
    return this;
  },
  
  /**
    Called whenever the content for the passed observer has changed.  Default
    version notifies the parent if it exists and updates the length.
    
    The start, amt and delta params should reflect changes to the children
    array, not to the expanded range for the wrapper.
  */
  observerContentDidChange: function(start, amt, delta) {
    
    // clear caches
    this.invalidateBranchObserversAt(start);
    this._objectAtCache = this._outlineLevelCache = null;
    this._disclosureStateCache = null;
    this._contentGroupIndexes = NO;
    this.notifyPropertyChange('branchIndexes');
    
    var oldlen = this.get('length'),
        newlen = this._computeLength(),
        parent = this.get('parentObserver'), set;
    
    // update length if needed
    if (oldlen !== newlen) this.set('length', newlen);
    
    // if we have a parent, notify that parent that we have changed.
    if (!this._notifyParent) return this; // nothing more to do
    
    if (parent) {
      set = SC.IndexSet.create(this.get('index'));
      parent._childrenRangeDidChange(parent.get('children'), null, '[]', set);
      
    // otherwise, note the enumerable content has changed.  note that we need
    // to convert the passed change to reflect the computed range
    } else {
      if (oldlen === newlen) {
        amt = this.expandChildIndex(start+amt);
        start = this.expandChildIndex(start);
        amt = amt - start ;
        delta = 0 ;
        
      } else {
        start = this.expandChildIndex(start);
        amt   = newlen - start;
        delta = newlen - oldlen ;
      }

      this.enumerableContentDidChange(start, amt, delta);
    }
  },

  /**
    Accepts a child index and expands it to reflect any nested groups.
  */
  expandChildIndex: function(index) {
    
    var ret = index;
    if (this.get('isHeaderVisible')) index++;

    // fast path
    var branches = this.get('branchIndexes');
    if (!branches || branches.get('length')===0) return ret;
    
    // we have branches, adjust for their length
    branches.forEachIn(0, index, function(idx) {
      ret += this.branchObserverAt(idx).get('length')-1;
    }, this);
    
    return ret; // add 1 for item header
  },
  
  // ..........................................................
  // SC.COLLECTION CONTENT SUPPORT
  // 

  _contentGroupIndexes: NO,
  
  /**
    Called by the collection view to return any group indexes.  The default 
    implementation will compute the indexes one time based on the delegate 
    treeItemIsGrouped
  */
  contentGroupIndexes: function(view, content) {
    if (content !== this) return null; // only care about receiver

    var ret = this._contentGroupIndexes;
    if (ret !== NO) return ret ;
    
    // if this is not the root item, never do grouping
    if (this.get('parentObserver')) return null;
    
    var item = this.get('item'), group, indexes, len, cur, loc, children;
    
    if (item && item.isTreeItemContent) group = item.get('treeItemIsGrouped');
    else group = !!this.delegate.get('treeItemIsGrouped');
    
    // if grouping is enabled, build an index set with all of our local 
    // groups.
    if (group) {
      ret      = SC.IndexSet.create();
      indexes  = this.get('branchIndexes');
      children = this.get('children');
      len      = children ? children.get('length') : 0;
      cur = loc = 0;
      
      if (indexes) {
        indexes.forEach(function(i) {
          ret.add(cur, (i+1)-loc); // add loc -> i to set
          cur += (i+1)-loc;
          loc = i+1 ;
          
          var observer = this.branchObserverAt(i);
          if (observer) cur += observer.get('length')-1;
        }, this);
      }

      if (loc<len) ret.add(cur, len-loc);
    } else ret = null;
    
    this._contentGroupIndexes = ret ;
    return ret;
  },
  
  contentIndexIsGroup: function(view, content, idx) {
    var indexes = this.contentGroupIndexes(view, content);
    return indexes ? indexes.contains(idx) : NO ;
  },
  
  /**
    Returns the outline level for the specified index.
  */
  contentIndexOutlineLevel: function(view, content, index) {
    if (content !== this) return -1; // only care about us
    
    var cache = this._outlineLevelCache;
    if (cache && (cache[index] !== undefined)) return cache[index];
    if (!cache) cache = this._outlineLevelCache = [];
    
    var len   = this.get('length'),
        cur   = index,
        loc   = 0,
        ret   = null,
        indexes, children, observer;
    
    if (index >= len) return -1;
     
    if (this.get('isHeaderVisible')) {
      if (index === 0) return cache[0] = this.get('outlineLevel')-1;
      else cur--;
    }

    // loop through branch indexes, reducing the offset until it matches 
    // something we might actually return.
    if (indexes = this.get('branchIndexes')) {
      indexes.forEach(function(i) {
        if ((ret!==null) || (i > cur)) return ; // past end - nothing to do

        var observer = this.branchObserverAt(i), len;
        if (!observer) return ; // nothing to do

        // if cur lands inside of this observer's length, use objectAt to get
        // otherwise, just remove len from cur.
        len = observer.get('length') ;
        if (i+len > cur) {
          ret  = observer.contentIndexOutlineLevel(view, observer, cur-i);
          cur  = -1;
        } else cur -= len-1 ;
        
      },this);
    }
    
    if (cur>=0) ret = this.get('outlineLevel'); // get internal if needed
    cache[index] = ret ; // save in cache 
    return ret ;
  },

  /**
    Returns the disclosure state for the specified index.
  */
  contentIndexDisclosureState: function(view, content, index) {
    if (content !== this) return -1; // only care about us
    
    var cache = this._disclosureStateCache;
    if (cache && (cache[index] !== undefined)) return cache[index];
    if (!cache) cache = this._disclosureStateCache = [];
    
    var len   = this.get('length'),
        cur   = index,
        loc   = 0,
        ret   = null,
        indexes, children, observer;
    
    if (index >= len) return SC.LEAF_NODE;
     
    if (this.get('isHeaderVisible')) {
      if (index === 0) return cache[0] = this.get('disclosureState');
      else cur--;
    }

    // loop through branch indexes, reducing the offset until it matches 
    // something we might actually return.
    if (indexes = this.get('branchIndexes')) {
      indexes.forEach(function(i) {
        if ((ret!==null) || (i > cur)) return ; // past end - nothing to do

        var observer = this.branchObserverAt(i), len;
        if (!observer) return ; // nothing to do

        // if cur lands inside of this observer's length, use objectAt to get
        // otherwise, just remove len from cur.
        len = observer.get('length') ;
        if (i+len > cur) {
          ret  = observer.contentIndexDisclosureState(view, observer, cur-i);
          cur  = -1;
        } else cur -= len-1 ;
        
      },this);
    }
    
    if (cur>=0) ret = SC.LEAF_NODE; // otherwise its a leaf node
    cache[index] = ret ; // save in cache 
    return ret ;
  },

  /**
    Expands the specified content index.  This will search down until it finds
    the branchObserver responsible for this item and then calls _collapse on
    it.
  */
  contentIndexExpand: function(view, content, idx) {

    var indexes, cur = idx, children, item;
    
    if (content !== this) return; // only care about us
    if (this.get('isHeaderVisible')) {
      if (idx===0) {
        this._expand(this.get('item'));
        return;
      } else cur--;
    } 
    
    if (indexes = this.get('branchIndexes')) {
      indexes.forEach(function(i) {
        if (i >= cur) return; // past end - nothing to do
        var observer = this.branchObserverAt(i), len;
        if (!observer) return ; 
        
        len = observer.get('length');
        if (i+len > cur) {
          observer.contentIndexExpand(view, observer, cur-i);
          cur = -1 ; //done
        } else cur -= len-1;
        
      }, this);  
    }
    
    // if we are still inside of the range then maybe pass on to a child item
    if (cur>=0) {
      children = this.get('children');  
      item     = children ? children.objectAt(cur) : null;
      if (item) this._expand(item, this.get('item'), cur);
    }
  },
  
  /**
    Called to collapse a content index item if it is currently in an open 
    disclosure state.  The default implementation does nothing.  
    
    @param {SC.CollectionView} view the collection view
    @param {SC.Array} content the content object
    @param {Number} idx the content index
    @returns {void}
  */
  contentIndexCollapse: function(view, content, idx) {

    var indexes, children, item, cur = idx;
        
    if (content !== this) return; // only care about us
    if (this.get('isHeaderVisible')) {
      if (idx===0) {
        this._collapse(this.get('item'));
        return;
      } else cur--;
    } 
    
    
    if (indexes = this.get('branchIndexes')) {
      indexes.forEach(function(i) {
        if (i >= cur) return; // past end - nothing to do
        var observer = this.branchObserverAt(i), len;
        if (!observer) return ; 
        
        len = observer.get('length');
        if (i+len > cur) {
          observer.contentIndexCollapse(view, observer, cur-i);
          cur = -1 ; //done
        } else cur -= len-1;
        
      }, this);  
    }

    // if we are still inside of the range then maybe pass on to a child item
    if (cur>=0) {
      children = this.get('children');  
      item     = children ? children.objectAt(cur) : null;
      if (item) this._collapse(item, this.get('item'), cur);
    }
  },
  
  // ..........................................................
  // BRANCH NODES
  //   

  /**
    Returns the branch item for the specified index.  If none exists yet, it
    will be created.
  */
  branchObserverAt: function(index) {
    var byIndex = this._branchObserversByIndex,
        indexes = this._branchObserverIndexes,
        ret, parent, pitem, item, children, guid, del ;
        
    if (!byIndex) byIndex = this._branchObserversByIndex = [];
    if (!indexes) {
      indexes = this._branchObserverIndexes = SC.IndexSet.create();
    }

    if (ret = byIndex[index]) return ret ; // use cache

    // no observer for this content exists, create one
    children = this.get('children');
    item   = children ? children.objectAt(index) : null ;
    if (!item) return null ; // can't create an observer for a null item
    
    byIndex[index] = ret = SC.TreeItemObserver.create({
      item:     item,
      delegate: this.get('delegate'),
      parentObserver:   this,
      index:  index,
      outlineLevel: this.get('outlineLevel')+1
    });

    indexes.add(index); // save for later invalidation
    return ret ;
  },
  
  /**
    Invalidates any branch observers on or after the specified index range.
  */
  invalidateBranchObserversAt: function(index) {
    var byIndex = this._branchObserversByIndex,
        indexes = this._branchObserverIndexes;

    if (!byIndex || byIndex.length<=index) return this ; // nothing to do
    if (index < 0) index = 0 ;
    
    // destroy any observer on or after the range
    indexes.forEachIn(index, indexes.get('max')-index, function(i) {
      var observer = byIndex[i];
      if (observer) observer.destroy();
    }, this);
    
    byIndex.length = index; // truncate to dump extra indexes
    
    return this;
  },
  
  // ..........................................................
  // INTERNAL METHODS
  // 
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    
    // begin all properties on item if there is one.  This will allow us to
    // track important property changes.
    var item = this.get('item');
    if (!item) throw "SC.TreeItemObserver.item cannot be null";
    
    item.addObserver('*', this, this._itemPropertyDidChange);
    this._itemPropertyDidChange(item, '*');
    this._notifyParent = YES ; // avoid infinite loops
  },
  
  /**
    Called just before a branch observer is removed.  Should stop any 
    observering and invalidate any child observers.
  */
  destroy: function() {
    this.invalidateBranchObserversAt(0);
    this._objectAtCache = null ;
    
    // cleanup observing
    var item = this.get('item');
    if (item) item.removeObserver('*', this, this._itemPropertyDidChange);
    
    var children = this._children,
        ro = this._childrenRangeObserver;
    if (children && ro) children.removeRangeObserver(ro);
    
    arguments.callee.base.apply(this,arguments);
  },
  
  /**
    Called whenever a property changes on the item.  Determines if either the
    children array or the disclosure state has changed and then notifies as 
    necessary..
  */
  _itemPropertyDidChange: function(target, key) {
    var children = this.get('children'),
        state    = this.get('disclosureState'),
        item     = this.get('item'),
        next ;
        
    this.beginPropertyChanges();
    
    next = this._computeDisclosureState(item);
    if (state !== next) this.set('disclosureState', next);
    
    next = this._computeChildren(item);
    if (children !== next) this.set('children', next);
    
    this.endPropertyChanges();
  },
  
  /**
    Called whenever the children or disclosure state changes.  Begins or ends
    observing on the children array so that changes can propogate outward.
  */
  _childrenDidChange: function() {
    var state = this.get('disclosureState'),
        cur   = state === SC.BRANCH_OPEN ? this.get('children') : null,
        last  = this._children,
        ro    = this._childrenRangeObserver;
        
    if (last === cur) return this; //nothing to do
    if (ro) last.removeRangeObserver(ro);
    if (cur) {
      this._childrenRangeObserver = 
          cur.addRangeObserver(null, this, this._childrenRangeDidChange);
    } else this._childrenRangeObserver = null;
    
    this._children = cur ;
    this._childrenRangeDidChange(cur, null, '[]', null);
    
  }.observes("children", "disclosureState"),

  /**
    Called anytime the actual content of the children has changed.  If this 
    changes the length property, then notifies the parent that the content
    might have changed.
  */
  _childrenRangeDidChange: function(array, objects, key, indexes) {
    var children = this.get('children'),
        len = children ? children.get('length') : 0,
        min = indexes ? indexes.get('min') : 0,
        max = indexes ? indexes.get('max') : len,
        old = this._childrenLen || 0;
        
    this._childrenLen = len; // save for future calls
    this.observerContentDidChange(min, max-min, len-old);
  },
  
  /**
    Computes the current disclosure state of the item by asking the item or 
    the delegate.  If no pitem or index is passed, the parentItem and idex 
    will be used.
  */
  _computeDisclosureState: function(item, pitem, index) {
    var key, del;

    // no item - assume leaf node
    if (!item || !this._computeChildren(item)) return SC.LEAF_NODE;
    
    // item implement TreeItemContent - call directly
    else if (item.isTreeItemContent) {
      if (pitem === undefined) pitem = this.get('parentItem');
      if (index === undefined) index = this.get('index');
      return item.treeItemDisclosureState(pitem, index);
      
    // otherwise get treeItemDisclosureStateKey from delegate
    } else {
      key = this._treeItemIsExpandedKey ;
      if (!key) {
        del = this.get('delegate');
        key = del ? del.get('treeItemIsExpandedKey') : 'treeItemIsExpanded';
        this._treeItemIsExpandedKey = key ;
      }
      return item.get(key) ? SC.BRANCH_OPEN : SC.BRANCH_CLOSED;
    }
  },
  
  /**
    Collapse the item at the specified index.  This will either directly 
    modify the property on the item or call the treeItemCollapse() method.
  */
  _collapse: function(item, pitem, index) {
    var key, del;

    // no item - assume leaf node
    if (!item || !this._computeChildren(item)) return this;
    
    // item implement TreeItemContent - call directly
    else if (item.isTreeItemContent) {
      if (pitem === undefined) pitem = this.get('parentItem');
      if (index === undefined) index = this.get('index');
      item.treeItemCollapse(pitem, index);
      
    // otherwise get treeItemDisclosureStateKey from delegate
    } else {
      key = this._treeItemIsExpandedKey ;
      if (!key) {
        del = this.get('delegate');
        key = del ? del.get('treeItemIsExpandedKey') : 'treeItemIsExpanded';
        this._treeItemIsExpandedKey = key ;
      }
      item.setIfChanged(key, NO);
    }
    
    return this ;
  },

  /**
    Expand the item at the specified index.  This will either directly 
    modify the property on the item or call the treeItemExpand() method.
  */
  _expand: function(item, pitem, index) {
    var key, del;

    // no item - assume leaf node
    if (!item || !this._computeChildren(item)) return this;
    
    // item implement TreeItemContent - call directly
    else if (item.isTreeItemContent) {
      if (pitem === undefined) pitem = this.get('parentItem');
      if (index === undefined) index = this.get('index');
      item.treeItemExpand(pitem, index);
      
    // otherwise get treeItemDisclosureStateKey from delegate
    } else {
      key = this._treeItemIsExpandedKey ;
      if (!key) {
        del = this.get('delegate');
        key = del ? del.get('treeItemIsExpandedKey') : 'treeItemIsExpanded';
        this._treeItemIsExpandedKey = key ;
      }
      item.setIfChanged(key, YES);
    }
    
    return this ;
  },
  
  /**
    Computes the children for the passed item.
  */
  _computeChildren: function(item) {
    var del, key;
    
    // no item - no children
    if (!item) return null;
    
    // item implement TreeItemContent - call directly
    else if (item.isTreeItemContent) return item.get('treeItemChildren');
          
    // otherwise get treeItemChildrenKey from delegate
    else {
      key = this._treeItemChildrenKey ;
      if (!key) {
        del = this.get('delegate');
        key = del ? del.get('treeItemChildrenKey') : 'treeItemChildren';
        this._treeItemChildrenKey = key ;
      }
      return item.get(key);
    }
  },
  
  /**
    Computes the length of the array by looking at children.
  */
  _computeLength: function() {
    var ret = this.get('isHeaderVisible') ? 1 : 0,
        state = this.get('disclosureState'),
        children = this.get('children'),
        indexes ;

    // if disclosure is open, add children count + length of branch observers.
    if ((state === SC.BRANCH_OPEN) && children) {
      ret += children.get('length');
      if (indexes = this.get('branchIndexes')) {
        indexes.forEach(function(idx) {
          var observer = this.branchObserverAt(idx);
          ret += observer.get('length')-1;
        }, this);
      }
    } 
    return ret ;
  }
    
});


/* >>>>>>>>>> BEGIN source/system/responder_context.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/responder');

/** @class

  The root object for a responder chain.  A responder context can dispatch
  actions directly to a first responder; walking up the responder chain until
  it finds a responder that can handle the action.  
  
  If no responder can be found to handle the action, it will attempt to send
  the action to the defaultResponder.
  
  You can have as many ResponderContext's as you want within your application.
  Every SC.Pane and SC.Application automatically implements this mixin.
  
  Note that to implement this you must be a responder yourself.
  
  @extends SC.Responder
  @since SproutCore 1.0
*/
SC.ResponderContext = SC.Responder.extend({

  // ..........................................................
  // PROPERTIES
  // 
  
  isResponderContext: YES,
  
  /** @property
  
    When set to YES, logs tracing information about all actions sent and 
    responder changes.
  */
  trace: NO,
  
  /** @property
    The default responder.  Set this to point to a responder object that can 
    respond to events when no other view in the hierarchy handles them.
    
    You can also implement actions directly on the application object if you
    prefer.
  */
  defaultResponder: null,
  
  /** @property
    The next responder for an app is always its defaultResponder.
  */
  nextResponder: function() {
    return this.get('defaultResponder');
  }.property('defaultResponder').cacheable(),
  
  /** @property
    The first responder.  This is the first responder that should receive 
    actions.
  */
  firstResponder: null,

  // ..........................................................
  // METHODS
  // 

  /**
    Finds the next responder for the passed responder based on the responder's
    nextResponder property.  If the property is a string, then lookup the path
    in the receiver.
  */
  nextResponderFor: function(responder) {
    var next = responder.get('nextResponder');
    if (typeof next === SC.T_STRING) {
      next = SC.objectForPropertyPath(next, this);
    } else if (!next && (responder !== this)) next = this ;
    return next ;  
  },

  /**
    Finds the responder name by searching the responders one time.
  */
  responderNameFor: function(responder) {
    if (!responder) return "(No Responder)";
    else if (responder._scrc_name) return responder._scrc_name;
    
    // none found, let's go hunting...look three levels deep
    var n = this.NAMESPACE;
    this._findResponderNamesFor(this, 3, n ? [this.NAMESPACE] : []);
    
    return responder._scrc_name || responder.toString(); // try again
  },
  
  _findResponderNamesFor: function(responder, level, path) {
    var key, value;
    
    for(key in responder) {
      if (key === 'nextResponder') continue ;
      value = responder[key];
      if (value && value.isResponder) {
        if (value._scrc_name) continue ;
        path.push(key);
        value._scrc_name = path.join('.');
        if (level>0) this._findResponderNamesFor(value, level-1, path);
        path.pop();
      }
    }
  },
  
  /**
    Makes the passed responder into the new firstResponder for this 
    responder context.  This will cause the current first responder to lose 
    its responder status and possibly keyResponder status as well.
    
    When you change the first responder, this will send callbacks to 
    responders up the chain until you reach a shared responder, at which point
    it will stop notifying.
    
    @param {SC.Responder} responder
    @returns {SC.ResponderContext} receiver
  */
  makeFirstResponder: function(responder) {
    var current = this.get('firstResponder'), 
        last    = this.get('nextResponder'),
        trace   = this.get('trace'),
        common ;

    if (this._locked) {
      if (trace) {
        console.log('%@: AFTER ACTION: makeFirstResponder => %@'.fmt(this, this.responderNameFor(responder)));
      }

      this._pendingResponder = responder;
      return ;
    }
    
    if (trace) {
      console.log('%@: makeFirstResponder => %@'.fmt(this, this.responderNameFor(responder)));
    }
    
    this._locked = YES;
    this._pendingResponder = null;
    
    // Find the nearest common responder in the responder chain for the new
    // responder.  If there are no common responders, use last responder.
    common = responder ? this.nextResponderFor(responder) : null;
    while (common) {
      if (common.get('hasFirstResponder')) break;
      common = (common===last) ? null : this.nextResponderFor(common);
    }
    if (!common) common = last;
    
    // Cleanup old first responder
    this._notifyWillLoseFirstResponder(current, current, common);
    if (current) current.set('isFirstResponder', NO);

    // Set new first responder.  If new firstResponder does not have its 
    // responderContext property set, then set it.
    this.set('firstResponder', responder) ;
    if (responder) responder.set('isFirstResponder', YES);
    
    this._notifyDidBecomeFirstResponder(responder, responder, common);
    
    this._locked = NO ;
    if (this._pendingResponder) {
      this.makeFirstResponder(this._pendingResponder);
      this._pendingResponder = null;
    }
    
    return this ;
  },

  _notifyWillLoseFirstResponder: function(responder, cur, root) {
    if (cur === root) return ; // nothing to do

    cur.willLoseFirstResponder(responder);  
    cur.set('hasFirstResponder', NO);

    var next = this.nextResponderFor(cur);
    if (next) this._notifyWillLoseFirstResponder(responder, next, root);
  },
  
  _notifyDidBecomeFirstResponder: function(responder, cur, root) {
    if (cur === root) return ; // nothing to do

    var next = this.nextResponderFor(cur);
    if (next) this._notifyDidBecomeFirstResponder(responder, next, root);
    
    cur.set('hasFirstResponder', YES);
    cur.didBecomeFirstResponder(responder);  
  },
  
  /**
    Send the passed action down the responder chain, starting with the 
    current first responder.  This will look for the first responder that 
    actually implements the action method and returns YES or no value when 
    called.
    
    @param {String} action name of action
    @param {Object} sender object sending the action
    @param {Object} context optional additonal context info
    @returns {SC.Responder} the responder that handled it or null
  */
  sendAction: function(action, sender, context) {
    var working = this.get('firstResponder'),
        last    = this.get('nextResponder'),
        trace   = this.get('trace'),
        handled = NO,
        responder;

    this._locked = YES;
    if (trace) {
      console.log("%@: begin action '%@' (%@, %@)".fmt(this, action, sender, context));
    }
    
    while (!handled && working) {
      if (working.tryToPerform) {
        handled = working.tryToPerform(action, sender, context);
      }
      
      if (!handled) {
        working = (working===last) ? null : this.nextResponderFor(working);
      }
    }

    if (trace) {
      if (!handled) console.log("%@:  action '%@' NOT HANDLED".fmt(this,action));
      else console.log("%@: action '%@' handled by %@".fmt(this, action, this.responderNameFor(working)));
    }
    
    this._locked = NO ;
    
    if (responder = this._pendingResponder) {
      this._pendingResponder= null ;
      this.makeFirstResponder(responder);
    }
    
    
    return working ;
  }

});

/* >>>>>>>>>> BEGIN source/system/application.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/responder_context');

/** @class

  The root object for a SproutCore application.  Usually you will create a 
  single SC.Application instance as your root namespace.  SC.Application is
  required if you intend to use SC.Responder to route events.
  
  h2. Example
  
  {{{
    Contacts = SC.Application.create({
      store: SC.Store.create(SC.Record.fixtures),
      
      // add other useful properties here
    });
  }}}

  h2. Sending Events
  
  You can send actions and events down an application-level responder chain
  by 
  
  @extends SC.ResponderContext
  @since SproutCore 1.0
*/
SC.Application = SC.ResponderContext.extend(/** SC.Application.prototype */ {

});

/* >>>>>>>>>> BEGIN source/system/benchmark.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================
/*globals $A*/

sc_require('core') ;
 
/** @namespace

  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.
  
  The benchmark has three types of reports.
  
  report(): Returns an abbreviated list with just the durations of the bench. 
            Also, it averages multiple runs. Everything is reported on the top
            level only.
            
  timelineReport(): Returns an list of benchmarks and sub-benchmarks. If the
                    the globalStartTime is set, then it will show relative
                    time from that time.
  
  timelineChart(): Displays a chart of all the benchmarks (not sub-benchmarks)
                   relative to the first time capture or to the globalStartTime.
                   Hide this by calling hideChart()
*/
SC.Benchmark = {

  /**
    If true, then benchmarks will be logged to the console as they are 
    recorded.
  
    @property {Boolean}
  */
  verbose: NO,
  
  /**
    If false, benchmarking will be disabled.  You might want to disable this
    during production to maximize performance.
  
    @property {Boolean}
  */
  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.
    * * *_times*: this array is used internally.
    
    @property {Object}
  */
  stats: {},

  /**
    If set, one can tell when the benchmark is started relatively to the global start time.
  
    @property {Number}
  */
  globalStartTime: null,

   /**
    Call this method at the start of whatever you want to collect.
    If a parentKey is passed, then you will attach the stat to the parent, 
    otherwise it will be on the top level. If topLevelOnly is passed, then 
    recursive calls to the start will be ignored and only the top level call 
    will be benchmarked.
    
    @param {String} key 
      A unique key that identifies this benchmark.  All calls to start/end 
      with the same key will be groups together.
    
    @param {String} parentKey
      A unique key that identifies the parent benchmark.  All calls to 
      start/end with the same key will be groups together.
    
    @param {Boolean} topLevelOnly
      If true then recursive calls to this method with the same key will be 
      ignored.  
    
    @param {Number} time
      Only pass if you want to explicitly set the start time.  Otherwise the 
      start time is now.
      
    @returns {String} the passed key
  */
  start: function(key, parentKey, time, topLevelOnly) {
    if (!this.enabled) return ;

    var start = (time || Date.now()), stat;

    if (parentKey) stat = this._subStatFor(key, parentKey) ;
    else stat = this._statFor(key) ;
    
    if (topLevelOnly && stat._starts.length > 0) stat._starts.push('ignore');
    else stat._starts.push(start) ;

    stat._times.push({start: start, _subStats: {}});
    return key;
  },

  /**
    Call this method at the end of whatever you want to collect.  This will
    save the collected benchmark.
    
    @param {String} key
      The benchmark key you used when you called start()
    
    @param {String} parentKey
      The benchmark parent key you used when you called start()
    
    @param {Number} time
      Only pass if you want to explicitly set the end time.  Otherwise start 
      time is now.
  */
  end: function(key, parentKey, time) {
    var stat;
    if (!this.enabled) return ;
    if(parentKey)
    {
      stat = this._subStatFor(key, parentKey) ;
    }
    else
    {
      stat = this._statFor(key) ;
    }
    var start = stat._starts.pop() ;
    if (!start) {
      console.log('SC.Benchmark "%@" ended without a matching start.  No information was saved.'.fmt(key));
      return ;
    }

    // top level only.
    if (start == 'ignore') return ; 
    
    var end = (time || Date.now()) ;
    var dur = end - start;

    stat._times[stat._times.length-1].end = end;
    stat._times[stat._times.length-1].dur = dur;

    stat.amt += dur ;
    stat.runs++ ;
    
    if (this.verbose) this.log(key) ;
  },
  
  /* 
    Set the inital global start time.
  */
  setGlobalStartTime: function(time)
  {
    this.globalStartTime = time;
  },

  /**
    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) {
      var timeKey = SC.Benchmark.start(key) ;
      ret = func();
      SC.Benchmark.end(timeKey) ; 
    }
    
    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.
    object['b__' + method] = object[method] ;
    var __func = object['b__' + 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 k in this.stats) {
      if (!this.stats.hasOwnProperty(k)) continue ;
      ret.push(this._genReport(k)) ;
    }
    return ret.join("\n") ;
  },

  /**
    Generate a human readable benchmark report. Pass in appName if you desire.

    @param {string} application name.
  */
  timelineReport: function(appName) 
  {
    appName = (appName) ? 'SproutCore Application' : appName;
    var ret = [appName, 'User-Agent: %@'.fmt(navigator.userAgent), 'Report Generated: %@ (%@)'.fmt(new Date().toString(), Date.now()), ''] ;

    var chart = this._compileChartData(true);
    for(var i=0; i<chart.length; i++)
    {
      if(chart[i][4])
      {
        ret.push(this._timelineGenSubReport(chart[i]));
      }
      else
      {
        ret.push(this._timelineGenReport(chart[i]));
      }
    }
    return ret.join("\n") ;
  },

  /**
    Generate a human readable benchmark chart. Pass in appName if you desire.

  */
  timelineChart: function(appName) {
    var i=0;
    // Hide the chart if there is an existing one.
    this.hideChart();
    
    // Compile the data.
    var chart = this._compileChartData(false);
    var chartLen = chart.length;
    
    // Return if there is nothing to draw.
    if(chartLen === 0) return;
    
    // Get the global start of the graph.
    var gStart = this.globalStartTime ? this.globalStartTime : chart[0][1];
    var maxDur = chart[chartLen-1][2]-gStart;
    var maxHeight = 50+chartLen*30;
    var incr = Math.ceil(maxDur/200)+1;
    var maxWidth = incr*50;
    
    // Create the basic graph element.
    var graph = document.createElement('div');
    graph.className = 'sc-benchmark-graph';
    document.body.appendChild(graph);

    // Set the title.
    var title = document.createElement('div');
    title.innerHTML = ((appName) ? appName : 'SproutCore Application') + (' - Total Captured Time: ' + maxDur +' ms - Points Captured: ' + chartLen) + ' [<a href="javascript:SC.Benchmark.hideChart();">Hide Chart</a>]';
    title.className = 'sc-benchmark-title'; 
    graph.appendChild(title);


    var topBox = document.createElement('div');
    topBox.className = 'sc-benchmark-top'; 
    topBox.style.width = maxWidth + 'px';
    graph.appendChild(topBox);

    // Draw the tick marks.
    for(i=0;i<incr; i++)
    {
      var tick = document.createElement('div');
      tick.className = 'sc-benchmark-tick';
      tick.style.left = (i*50)+'px';
      tick.style.height = maxHeight+'px';
      var tickLabel = document.createElement('div');
      tickLabel.className = 'sc-benchmark-tick-label';
      tickLabel.style.left = (i*50)+'px';
      tickLabel.innerHTML = i*200+" ms";
      graph.appendChild(tick);
      graph.appendChild(tickLabel);
    }
    
    // For each item in the chart, print it out on the screen.
    for(i=0;i<chartLen; i++)
    {
    	var row = document.createElement('div');
    	row.style.top = (75+(i*30))+'px';
    	row.style.width = maxWidth+'px';
    	row.className = (i%2===0) ? 'sc-benchmark-row even' : 'sc-benchmark-row';
    	graph.appendChild(row);

      var div = document.createElement('div');
      var start = chart[i][1];
      var end = chart[i][2];
      var duration = chart[i][3];
      
      div.innerHTML = '&nbsp;' + (chart[i][0] + " <span class='sc-benchmark-emphasis'>" + duration + 'ms</span>');
      
      div.className = 'sc-benchmark-bar';
      div.style.cssText = 'left:'+ (((start-gStart)/4))+'px; width: '+((duration/4))+
                          'px; top: '+(53+(i*30))+'px;';
      div.title = "start: " + (start-gStart) + " ms, end: " + (end-gStart) + ' ms, duration: ' + duration + ' ms';
      graph.appendChild(div);
    }

    // Save the graph.
    this._graph = graph;
  },
  
  /*
    Hide chart.
    
  */
  hideChart: function()
  {
    if(this._graph) {
      try{ 
        document.body.removeChild(this._graph);
      }catch(e){}
    }
  },
  

  /**
    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

  // @private
  
  // Generates, sorts, and returns the array of all the data that has been captured.
  _compileChartData: function(showSub)
  {
    var chart = [], dispKey;
    for(var key in this.stats) 
    {
      var stat = this.stats[key];
      for(var i=0; i<stat._times.length; i++)
      {
        var st = stat._times[i];
        dispKey = (stat._times.length > 1) ? (i+1)+' - '+key : key;
        chart.push([dispKey, st.start, st.end, st.dur, false]);
        if(showSub)
        {
          var subStats = st._subStats;
          for(var k in subStats) 
          {
           
            var subStat = subStats[k];
            for(var j=0; j<subStat._times.length; j++)
            {
              var s = subStat._times[j];
              dispKey = (subStat._times.length > 1) ? (j+1)+' - '+k : k;
              chart.push([dispKey, s.start, s.end, s.dur, true]);
         
            }
          }
        }
      }
    }
    
    chart.sort(function(a,b)
    {
      if(a[1] < b[1]) 
      {
        return -1;
      }
      else if(a[1] == b[1])
      {
        if(a[3] && !b[3]) return -1;
        if(!a[3] && b[3]) return 1;
        return 0;
      }
      return 1;
    });

    return chart;
  },
  
  // Generate the traditional report show multiple runs averaged.
  _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) ;  
  },

  // Generate the report in the form of at time line. This returns the parent.
  _timelineGenReport: function(val) 
  {
    if(this.globalStartTime)
    {
      return 'BENCH start: %@ msec, duration: %@ msec,  %@'.fmt((val[1]-this.globalStartTime), val[3], val[0]) ;  
    } 
    else
    {
      return 'BENCH duration: %@ msec, %@'.fmt( val[3],  val[0]) ;  
    }
  },
  
  // Generate the report in the form of at time line. This returns the children.
  _timelineGenSubReport: function(val) 
  {
    if(this.globalStartTime)
    {
      return '   CHECKPOINT BENCH start: %@ msec, duration: %@ msec,  %@'.fmt((val[1]-this.globalStartTime), val[3], val[0]) ;  
    } 
    else
    {
      return '   CHECKPOINT BENCH duration: %@ msec, %@'.fmt( val[3], val[0]) ;  
    }
  },
  
  // returns a stats hash for the named key and parent key.  If the hash does not exist yet,
  // creates it.
  _subStatFor: function(key, parentKey) {
    var parentTimeLen = this.stats[parentKey]._times.length;
    if(parentTimeLen === 0) return;
    var parentSubStats = this.stats[parentKey]._times[this.stats[parentKey]._times.length-1]._subStats;
    var ret = parentSubStats[key] ;
    if (!ret) {
      parentSubStats[key] = {
        runs: 0, amt: 0, name: key, _starts: [], _times: []      
      };
      ret = parentSubStats[key];
    }
    return ret ;
  },

  // 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: [], _times: []      
      };
      ret = this.stats[key];
    }
    return ret ;
  },
  
  reset: function() { this.stats = {} ; },
  
  // 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
  
} ;

SC.Benchmark = SC.Benchmark;
/* >>>>>>>>>> BEGIN source/system/bundle.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  The global bundle methods. See also: lib/boostrap.rhtml
*/
SC.mixin(/** @scope SC */ {
  
  
  /**
    @property
    @default NO
    @type {Boolean}
    
    If YES, log bundle loading.
  */
  logBundleLoading: NO,
  
  /**
    Returns YES is bundleName is loaded; NO if bundleName is not loaded or
    no information is available.
    
    @param bundleName {String}
    @returns {Boolean}
  */
  bundleIsLoaded: function(bundleName) {
    var bundleInfo = SC.BUNDLE_INFO[bundleName] ;
    return bundleInfo ? !!bundleInfo.loaded : NO ;
  },
  
  /**
    @private
    
    Execute callback function.
  */
  _scb_bundleDidLoad: function(bundleName, target, method, args) {
    var m = method, t = target ;

    if(SC.typeOf(target) === SC.T_STRING) {
      t = SC.objectForPropertyPath(target);
    }

    if(SC.typeOf(method) === SC.T_STRING) {
      m = SC.objectForPropertyPath(method, t);
    }
    
    if(!m) {

      if(SC.LAZY_INSTANTIATION[bundleName]) {
        var lazyInfo = SC.LAZY_INSTANTIATION[bundleName];

      if(SC.logBundleLoading) console.log("SC.loadBundle(): Bundle '%@' is marked for lazy instantiation, instantiating it now…".fmt(bundleName));            
        
        for(var i=0, iLen = lazyInfo.length; i<iLen; i++) {
          try { 
            lazyInfo[i]();
          }catch(e) {
            console.log("SC.loadBundle(): Failted to lazily instatiate entry for  '%@'".fmt(bundleName));  
          }
        }
        delete SC.LAZY_INSTANTIATION[bundleName];

        if(SC.typeOf(target) === SC.T_STRING) {
          t = SC.objectForPropertyPath(target);
        }
        if(SC.typeOf(method) === SC.T_STRING) {
          m = SC.objectForPropertyPath(method, t);
        }

        if(!method) {
          throw "SC.loadBundle(): could not find callback for lazily instantiated bundle '%@'".fmt(bundleName);

        }
      } else {
        throw "SC.loadBundle(): could not find callback for '%@'".fmt(bundleName);
      }
    }

    if(!args) {
      args = [];
    }

    args.push(bundleName);
    
    var needsRunLoop = !!SC.RunLoop.currentRunLoop;
    if (needsRunLoop) SC.RunLoop.begin() ;
    m.apply(t, args) ;
    if (needsRunLoop) SC.RunLoop.end() 
  },
  
  tryToLoadBundle: function(bundleName, target, method, args) {
    var m, t;
    
    // First see if it is already defined.
    if(SC.typeOf(target) === SC.T_STRING) {
      t = SC.objectForPropertyPath(target);
    }
    if(SC.typeOf(method) === SC.T_STRING) {
      m = SC.objectForPropertyPath(method, t);
    }

    // If the method exists, try to call it. It could have been loaded 
    // through other means but the SC.BUNDLE_INFO entry doesn't exist.
    if(m || SC.LAZY_INSTANTIATION[bundleName]) {
      if(SC.logBundleLoading) console.log("SC.loadBundle(): Bundle '%@' found through other means, will attempt to load…".fmt(bundleName));
      SC.BUNDLE_INFO[bundleName] = {loaded: YES};
      return SC.BUNDLE_INFO[bundleName]; 
    }
    return NO;
  },
    
  /**
    Dynamically load bundleName if not already loaded. Call the target and 
    method with any given arguments.
    
    @param bundleName {String}
    @param target {Function} 
    @param method {Function}
  */
  loadBundle: function(bundleName, target, method) {
    var idx, len;
    if(method === undefined && SC.typeOf(target) === SC.T_FUNCTION) {
      method = target;
      target = null;
    }

    var bundleInfo = SC.BUNDLE_INFO[bundleName], callbacks, targets ;
    var args = SC.A(arguments).slice(3);

    if(SC.logBundleLoading) {
      console.log("SC.loadBundle(): Attempting to load '%@'".fmt(bundleName));
    }
    
    if(!bundleInfo) {
      if(SC.logBundleLoading) console.log("SC.loadBundle(): Attemping to load %@ without SC.BUNDLE_INFO entry… could be loaded through other means.".fmt(bundleName));
      bundleInfo = this.tryToLoadBundle(bundleName, target, method, args);
    }
    

    if (!bundleInfo) {        
      throw "SC.loadBundle(): could not find bundle '%@'".fmt(bundleName) ;
    } else if (bundleInfo.loaded) {

      if(SC.logBundleLoading) console.log("SC.loadBundle(): Bundle '%@' already loaded, skipping.".fmt(bundleName));

      if(method) {
        // call callback immediately if we're already loaded and SC.isReady
        if (SC.isReady) {
          SC._scb_bundleDidLoad(bundleName, target, method, args);
        } else {
          // queue callback for when SC is ready
          SC.ready(SC, function() {
            SC._scb_bundleDidLoad(bundleName, target, method, args);        
          });
        }
      }
    } else {

      if(SC.logBundleLoading) console.log("SC.loadBundle(): Bundle '%@' is not loaded, loading now.".fmt(bundleName));

      // queue callback for later
      callbacks = bundleInfo.callbacks || [] ;

      if (method) {
        callbacks.push(function() {
          SC._scb_bundleDidLoad(bundleName, target, method, args);        
        });
        bundleInfo.callbacks = callbacks ;
      }

      if (!bundleInfo.loading) {
        // load bundle's dependencies first
        var requires = bundleInfo.requires || [] ;
        var dependenciesMet = YES ;
        for (idx=0, len=requires.length; idx<len; ++idx) {
          var targetName = requires[idx] ;
          var targetInfo = SC.BUNDLE_INFO[targetName] ;
          if (!targetInfo) {
            throw "SC.loadBundle(): could not find required bundle '%@' for bundle '%@'".fmt(targetName, bundleName) ;
          } else {
            if (targetInfo.loading) {
              dependenciesMet = NO ;
              break ;
            } else if (targetInfo.loaded) {
              continue ;
            } else {
              dependenciesMet = NO ;
              
              // register ourself as a dependent bundle (used by 
              // SC.bundleDidLoad()...)
              var dependents = targetInfo.dependents;
              if(!dependents) targetInfo.dependents = dependents = [];

              dependents.push(bundleName) ;

              if(SC.logBundleLoading) console.log("SC.loadBundle(): '%@' depends on '%@', loading dependency…".fmt(bundleName, targetName));
              
              // recursively load targetName so it's own dependencies are
              // loaded first.
              SC.loadBundle(targetName) ;
              break ;
            }
          }
        }
        
        if (dependenciesMet) {
          // add <script> and <link> tags to DOM for bundle's resources
          var styles, scripts, url, el, head, body;
          head = document.getElementsByTagName('head')[0] ;
          if (!head) head = document.documentElement ; // fix for Opera
          styles = bundleInfo.styles || [] ;
          for (idx=0, len=styles.length; idx<len; ++idx) {
            url = styles[idx] ;
            if (url.length > 0) {
              el = document.createElement('link') ;
              el.setAttribute('href', url) ;
              el.setAttribute('rel', "stylesheet") ;
              el.setAttribute('type', "text/css") ;
              head.appendChild(el) ;
            }
          }

          // Push the URLs on the the queue and then start the loading.
          var jsBundleLoadQueue = this._jsBundleLoadQueue;
          if(!jsBundleLoadQueue) this._jsBundleLoadQueue = jsBundleLoadQueue = {};
          jsBundleLoadQueue[bundleName] = [];
          var q = jsBundleLoadQueue[bundleName] ;
          scripts = bundleInfo.scripts || [] ;
          
          for (idx=0, len=scripts.length; idx<len; ++idx) {
            url = scripts[idx] ;
            if (url.length > 0) {
              q.push(url);
            }
          }
          
          // and remember that we're loading
          bundleInfo.loading = YES ;
          
          // Start the load process.
          this.scriptDidLoad(bundleName);
        }
      }
    }
  },

  /**
    Load the next script in the queue now that the caller of this function
    is complete.
    
    @param {String} bundleName The name of the bundle.
  */
  scriptDidLoad: function(bundleName) {
    var jsBundleLoadQueue = this._jsBundleLoadQueue;
    if(jsBundleLoadQueue) {
      var q = jsBundleLoadQueue[bundleName];
      if(q) {
        var url = q.shift();
        
        if(SC.logBundleLoading) console.log("SC.scriptDidLoad(): Loading next file in '%@' -> '%@'".fmt(bundleName, url));

        var el = document.createElement('script') ;
        el.setAttribute('type', "text/javascript") ;
        el.setAttribute('src', url) ;
        document.body.appendChild(el) ;
      }
    }
  },
  
  /** @private
    Called by bundle_loaded.js immediately after a framework/bundle is loaded.
    Any pending callbacks are called (if SC.isReady), and any dependent 
    bundles which were waiting for this bundle to load are notified so they 
    can continue loading.
    
    @param bundleName {String} the name of the bundle that just loaded
  */
  bundleDidLoad: function(bundleName) {
    var bundleInfo = SC.BUNDLE_INFO[bundleName], callbacks, targets ;
    if (!bundleInfo) {
      bundleInfo = SC.BUNDLE_INFO[bundleName] = { loaded: YES} ;
      return;
    }
    if (bundleInfo.loaded && SC.logBundleLoading) {
      console.log("SC.bundleDidLoad() called more than once for bundle '%@'. Skipping.".fmt(bundleName));
      return ;
    }
    
    // remember that we're loaded
    delete bundleInfo.loading ;
    bundleInfo.loaded = YES ;
    
    // call our callbacks (if SC.isReady), otherwise queue them for later
    if (SC.isReady) {
      SC._invokeCallbacksForBundle(bundleName) ;
    } else {
      SC.ready(SC, function() {
        SC._invokeCallbacksForBundle(bundleName) ;
      });
    }
    
    // for each dependent bundle, try and load them again...
    var dependents = bundleInfo.dependents || [] ;
    for (var idx=0, len=dependents.length; idx<len; ++idx) {
      if(SC.logBundleLoading) console.log("SC.loadBundle(): Bundle '%@' has completed loading, loading '%@' that depended on it.".fmt(bundleName, dependents[idx]));
      SC.loadBundle(dependents[idx]) ;
    }
  },
  
  /** @private Invoke queued callbacks for bundleName. */
  _invokeCallbacksForBundle: function(bundleName) {
    var bundleInfo = SC.BUNDLE_INFO[bundleName], callbacks ;
    if (!bundleInfo) return ; // shouldn't happen, but recover anyway
    
    if(SC.logBundleLoading) console.log("SC.loadBundle(): Bundle '%@' has completed loading, invoking callbacks.".fmt(bundleName));

    callbacks = bundleInfo.callbacks || [] ;
    
    SC.RunLoop.begin() ;
    for (var idx=0, len=callbacks.length; idx<len; ++idx) {
      callbacks[idx]() ;
    }
    SC.RunLoop.end() ;
  }
  
});

/* >>>>>>>>>> BEGIN source/system/image_cache.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/locale');

SC.IMAGE_ABORTED_ERROR = SC.$error("SC.Image.AbortedError", "Image", -100) ;

SC.IMAGE_FAILED_ERROR = SC.$error("SC.Image.FailedError", "Image", -101) ;

/**
  @class
  
  The image cache can be used to control the order of loading images into the
   browser cache.
  
  Images queues are necessary because browsers impose strict limits on the 
  number of concurrent connections that can be open at any one time to any one 
  host. By controlling the order and timing of your loads using this image 
  queue, you can improve the percieved performance of your application by 
  ensuring the images you need most load first.
  
  Note that if you use the SC.ImageView class, it will use this image cache 
  for you automatically.
  
  h1. Loading Images
  
  When you need to display an image, simply call the loadImage() method with 
  the URL of the image, along with a target/method callback. The signature of 
  your callback should be:
  
  {{{
    imageDidLoad: function(imageUrl, imageOrError) {
      //...
    }
  }}}

  The "imageOrError" parameter will contain either an image object or an error 
  object if the image could not be loaded for some reason.  If you receive an 
  error object, it will be one of SC.IMAGE_ABORTED_ERROR or 
  SC.IMAGE_FAILED_ERROR.
  
  You can also optionally specify that the image should be loaded in the 
  background.  Background images are loaded with a lower priority than 
  foreground images.
  
  h1. Aborting Image Loads
  
  If you request an image load but then no longer require the image for some 
  reason, you should notify the imageCache by calling the releaseImage() 
  method.  Pass the URL, target and method that you included in your original 
  loadImage() request.  
  
  If you have requested an image before, you should always call releaseImage() 
  when you are finished with it, even if the image has already loaded.  This 
  will allow the imageCache to properly manage its own internal resources.
  
  This method may remove the image from the queue of images that need or load 
  or it may abort an image load in progress to make room for other images.  If 
  the image is already loaded, this method will have no effect.
  
  h1. Reloading an Image
  
  If you have already loaded an image, the imageCache will avoid loading the 
  image again.  However, if you need to force the imageCache to reload the 
  image for some reason, you can do so by calling reloadImage(), passing the 
  URL. 
  
  This will cause the image cache to attempt to load the image again the next 
  time you call loadImage on it.
  
  @extends SC.Object
  @since SproutCore 1.0
*/
SC.imageCache = SC.Object.create(/** @scope SC.imageCache.prototype */ {

  /**
    The maximum number of images that can load from a single hostname at any
    one time.  For most browsers 4 is a reasonable number, though you may 
    tweak this on a browser-by-browser basis.
  */
  loadLimit: 4,
  
  /**
    The number of currently active requests on the cache. 
  */
  activeRequests: 0,
  
  /**
    Loads an image from the server, calling your target/method when complete.
    
    You should always pass at least a URL and optionally a target/method.  If 
    you do not pass the target/method, the image will be loaded in background 
    priority.  Usually, however, you will want to pass a callback to be 
    notified when the image has loaded.  Your callback should have a signature 
    like:

    {{{
      imageDidLoad: function(imageUrl, imageOrError) { .. }
    }}}
    
    If you do pass a target/method you can optionally also choose to load the 
    image either in the foreground or in the background.  The image cache 
    prioritizes foreground images over background images.  This does not impact 
    how many images load at one time.
    
    @param {String} url
    @param {Object} target
    @param {String|Function} method
    @param {Boolean} isBackgroundFlag
    @returns {SC.imageCache} receiver
  */
  loadImage: function(url, target, method, isBackgroundFlag) {
    // normalize params
    var type = SC.typeOf(target);
    if (SC.none(method) && SC.typeOf(target)===SC.T_FUNCTION) {
      target = null; method = target ;
    }
    if (SC.typeOf(method) === SC.T_STRING) {
      method = target[method];      
    }
    // if no callback is passed, assume background image.  otherwise, assume
    // foreground image.
    if (SC.none(isBackgroundFlag)) {
      isBackgroundFlag = SC.none(target) && SC.none(method);
    }
    
    // get image entry in cache.  If entry is loaded, just invoke callback
    // and quit.
    var entry = this._imageEntryFor(url) ;
    if (entry.status === this.IMAGE_LOADED) {
      if (method) method.call(target || entry.image, entry.url, entry.image);
      
    // otherwise, add to list of callbacks and queue image.
    } else {
      if (target || method) this._addCallback(entry, target, method);
      entry.retainCount++; // increment retain count, regardless of callback
      this._scheduleImageEntry(entry, isBackgroundFlag);
    }
  },
  
  /**
    Invoke this method when you are finished with an image URL.  If you 
    passed a target/method, you should also pass it here to remove it from
    the list of callbacks.
    
    @param {String} url
    @param {Object} target
    @param {String|Function} method
    @returns {SC.imageCache} receiver
  */
  releaseImage: function(url, target, method) {
    
    // get entry.  if there is no entry, just return as there is nothing to 
    // do.
    var entry = this._imageEntryFor(url, NO) ;
    if (!entry) return this ;
    
    // there is an entry, decrement the retain count.  If <=0, delete!
    if (--entry.retainCount <= 0) {
      this._deleteEntry(entry); 
    
    // if >0, just remove target/method if passed
    } else if (target || method) {
      // normalize
      var type = SC.typeOf(target);
      if (SC.none(method) && SC.typeOf(target)===SC.T_FUNCTION) {
        target = null; method = target ;
      }
      if (SC.typeOf(method) === SC.T_STRING) {
        method = target[method];      
      }

      // and remove
      this._removeCallback(entry, target, method) ;
    }
  },

  /** 
    Forces the image to reload the next time you try to load it.
  */
  reloadImage: function(url) {
    var entry = this._imageEntryFor(url, NO); 
    if (entry && entry.status===this.IMAGE_LOADED) {
      entry.status = this.IMAGE_WAITING;
    }
  },
  
  /**
    Initiates a load of the next image in the image queue.  Normally you will
    not need to call this method yourself as it will be initiated 
    automatically when the queue becomes active.
  */
  loadNextImage: function() {
    var entry = null, queue;

    // only run if we don't have too many active request...
    if (this.get('activeRequests')>=this.get('loadLimit')) return; 
    
    // first look in foreground queue
    queue = this._foregroundQueue ;
    while(queue.length>0 && !entry) entry = queue.shift();
    
    // then look in background queue
    if (!entry) {
      queue = this._backgroundQueue ;
      while(queue.length>0 && !entry) entry = queue.shift();
    }
    this.set('isLoading', !!entry); // update isLoading...
    
    // if we have an entry, then initiate an image load with the proper 
    // callbacks.
    if (entry) {
      // var img = (entry.image = new Image()) ;
      var img = entry.image ;
      img.onabort = this._imageDidAbort ;
      img.onerror = this._imageDidError ;
      img.onload = this._imageDidLoad ;
      img.src = entry.url ;

      // add to loading queue.
      this._loading.push(entry) ;
    
      // increment active requests and start next request until queue is empty
      // or until load limit is reached.
      this.incrementProperty('activeRequests');
      this.loadNextImage();
    } 
  },
  
  // ..........................................................
  // SUPPORT METHODS
  // 

  /** @private Find or create an entry for the URL. */
  _imageEntryFor: function(url, createIfNeeded) {
    if (createIfNeeded === undefined) createIfNeeded = YES;
    var entry = this._images[url] ;
    if (!entry && createIfNeeded) {
      var img = new Image() ;
      entry = this._images[url] = { 
        url: url, status: this.IMAGE_WAITING, callbacks: [], retainCount: 0, image: img
      };
      img.entry = entry ; // provide a link back to the image
    }
    return entry ;
  },
  
  /** @private deletes an entry from the image queue, descheduling also */
  _deleteEntry: function(entry) {
    this._unscheduleEntry(entry) ;
    delete this._images[entry.url];    
  },
  
  /** @private 
    Add a callback to the image entry.  First search the callbacks to make
    sure this is only added once.
  */
  _addCallback: function(entry, target, method) {
    var callbacks = entry.callbacks;

    // try to find in existing array
    var handler = callbacks.find(function(x) {
      return x[0]===target && x[1]===method;
    }, this);
    
    // not found, add...
    if (!handler) callbacks.push([target, method]);
    callbacks = null; // avoid memory leaks
    return this ;
  },
  
  /** @private
    Removes a callback from the image entry.  Removing a callback just nulls
    out that position in the array.  It will be skipped when executing.
  */
  _removeCallback: function(entry, target, method) {
    var callbacks = entry.callbacks ;
    callbacks.forEach(function(x, idx) {
      if (x[0]===target && x[1]===method) callbacks[idx] = null;
    }, this);
    callbacks = null; // avoid memory leaks
    return this ;
  },
  
  /** @private 
    Adds an entry to the foreground or background queue to load.  If the 
    loader is not already running, start it as well.  If the entry is in the
    queue, but it is in the background queue, possibly move it to the
    foreground queue.
  */
  _scheduleImageEntry: function(entry, isBackgroundFlag) {

    var background = this._backgroundQueue ;
    var foreground = this._foregroundQueue ;
    
    // if entry is loaded, nothing to do...
    if (entry.status === this.IMAGE_LOADED) return this;

    // if image is already in background queue, but now needs to be
    // foreground, simply remove from background queue....
    if ((entry.status===this.IMAGE_QUEUE) && !isBackgroundFlag && entry.isBackground) {
      background[background.indexOf(entry)] = null ;
      entry.status = this.IMAGE_WAITING ;
    }
    
    // if image is not in queue already, add to queue.
    if (entry.status!==this.IMAGE_QUEUE) {
      var queue = (isBackgroundFlag) ? background : foreground ;
      queue.push(entry);
      entry.status = this.IMAGE_QUEUE ;
      entry.isBackground = isBackgroundFlag ;
    }
    
    // if the image loader is not already running, start it...
    if (!this.isLoading) this.invokeLater(this.loadNextImage, 100);
    this.set('isLoading', YES);
    
    return this ; // done!
  },
  
  /** @private
    Removes an entry from the foreground or background queue.  
  */
  _unscheduleImageEntry: function(entry) {
    // if entry is not queued, do nothing
    if (entry.status !== this.IMAGE_QUEUE) return this ;
    
    var queue = entry.isBackground ? this._backgroundQueue : this._foregroundQueue ;
    queue[queue.indexOf(entry)] = null; 
    
    // if entry is loading, abort it also.  Call local abort method in-case
    // browser decides not to follow up.
    if (this._loading.indexOf(entry) >= 0) {
      queue.image.abort();
      // this.imageStatusDidChange(entry.url, this.ABORTED);
      this.imageStatusDidChange(entry, this.ABORTED);
    }
    
    return this ;
  },
  
  /** @private invoked by Image().  Note that this is the image instance */
  _imageDidAbort: function() {
    // SC.imageCache.imageStatusDidChange(this.src, SC.imageCache.ABORTED);
    SC.imageCache.imageStatusDidChange(this.entry, SC.imageCache.ABORTED);
  },
  
  _imageDidError: function() {
    // SC.imageCache.imageStatusDidChange(this.src, SC.imageCache.ERROR);
    SC.imageCache.imageStatusDidChange(this.entry, SC.imageCache.ERROR);
  },
  
  _imageDidLoad: function() {
    // SC.imageCache.imageStatusDidChange(this.src, SC.imageCache.LOADED);
    SC.imageCache.imageStatusDidChange(this.entry, SC.imageCache.LOADED);
  },

  /** @private called whenever the image loading status changes.  Notifies
    items in the queue and then cleans up the entry.
  */
  // imageStatusDidChange: function(url, status) {
  imageStatusDidChange: function(entry, status) {
    // var entry = this._imageEntryFor(url, NO);
    if (!entry) return; // nothing to do...
    
    var url = entry.url ;
    
    // notify handlers.
    var value ;
    switch(status) {
      case this.LOADED:
        value = entry.image;
        break;
      case this.ABORTED:
        value = SC.IMAGE_ABORTED_ERROR;
        break;
      case this.ERROR:
        value = SC.IMAGE_FAILED_ERROR ;
        break;
      default:
        value = SC.IMAGE_FAILED_ERROR ;
        break;
    }
    entry.callbacks.forEach(function(x){
      var target = x[0], method = x[1];
      method.call(target, url, value);
    },this);
    
    // now clear callbacks so they aren't called again.
    entry.callbacks = [];
    
    // finally, if the image loaded OK, then set the status.  Otherwise
    // set it to waiting so that further attempts will load again
    entry.status = (status === this.LOADED) ? this.IMAGE_LOADED : this.IMAGE_WAITING ;
    
    // now cleanup image...
    var image = entry.image ;
    if (image) {
      image.onload = image.onerror = image.onabort = null ; // no more notices
      if (status !== this.LOADED) entry.image = null;
    }

    // remove from loading queue and periodically compact
    this._loading[this._loading.indexOf(entry)]=null;
    if (this._loading.length > this.loadLimit*2) {
      this._loading = this._loading.compact();
    }
    
    this.decrementProperty('activeRequests');
    this.loadNextImage() ;
  },
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    this._images = {};
    this._loading = [] ;
    this._foregroundQueue = [];
    this._backgroundQueue = [];
  },
  
  IMAGE_LOADED: "loaded",
  IMAGE_QUEUED: "queued",
  IMAGE_WAITING: "waiting",
  
  ABORTED: 'aborted',
  ERROR: 'error',
  LOADED: 'loaded'
});
/* >>>>>>>>>> BEGIN source/system/page.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class SC.Page

  A Page object is used to store a set of views that can be lazily configured
  as needed.  The page object works by overloading the get() method.  The
  first time you try to get the page
  
  @extends SC.Object
*/
SC.Page = SC.Object.extend(
/** @scope SC.Page.prototype */ {
  
  /**
    When you create a page, you can set it's "owner" property to an
    object outside the page definition. This allows views in the page
    to use the owner object as a target, (as well as other objects
    accessible through the owner object). E.g.
    
    {{{
      myButton: SC.ButtonView.design({
        title: 'Click me',
        target: SC.outlet('page.owner'),
        action: 'buttonClicked'
      })
    }}}
    
    Usually, you'll set 'owner' to the object defined in core.js.
  */
  owner: null,
  
  get: function(key) {
    var value = this[key] ;
    if (value && value.isClass) {
      this[key] = value = value.create({ page: this }) ;
      if (!this.get('inDesignMode')) value.awake() ;
      return value ;
    } else return arguments.callee.base.apply(this,arguments) ;
  },
  
  /**
    Finds all views defined on this page instances and builds them.  This is 
    a quick, brute force way to wake up all of the views in a page object.  It
    is not generally recommended. Instead, you should use get() or getPath() 
    to retrieve views and rely on the lazy creation process to set them up.
    
    @return {SC.Page} receiver
  */
  awake: function() {
    // step through all views and build them
    for(var key in this) {
      if (!this.hasOwnProperty(key)) continue ;
      var value = this[key] ;
      if (value && value.isViewClass) {
        this[key] = value = value.create({ page: this }) ;
      }
    }
    return this;
  },

  /**
    Returns the named property unless the property is a view that has not yet
    been configured.  In that case it will return undefined.  You can use this
    method to safely get a view without waking it up.
  */
  getIfConfigured: function(key) {
    var ret = this[key] ;
    return (ret && ret.isViewClass) ? null : this.get(key);
  },

  /**
    Applies a localization to every view builder defined on the page.  You must call this before you construct a view to apply the localization.
  */
  loc: function(locs) {
    for(var key in locs) {
      if (!locs.hasOwnProperty(key)) continue ;
      var view = this[key] ;
      if (!view || !view.isViewClass) continue ;
      view.loc(locs[key]);
    }
    return this ;
  }
  
  //needsDesigner: YES,
  
  //inDesignMode: YES
    
}) ;

// ..........................................................
// SUPPORT FOR LOADING PAGE DESIGNS
// 

/** Calling design() on a page is the same as calling create() */
SC.Page.design = SC.Page.create ;

/** Calling localization returns passed attrs. */
SC.Page.localization = function(attrs) { return attrs; };



/* >>>>>>>>>> BEGIN source/system/ready.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/*global main */

sc_require('system/event') ;

SC.mixin({
  _isReadyBound: NO,
  
  /** @private configures the ready event handler if needed */
  _bindReady: function() {
    if (this._isReadyBound) return;
    this._isReadyBound = YES ;
    tiki.require('system').ready(this, this._didBecomeReady); 
  },

  /** @private handlers scheduled to execute on ready. */
  _readyQueue: [],
  
  _afterReadyQueue: [],

  isReady: NO,
  
  /** @private invoked when the document becomes ready. */
  _didBecomeReady: function() {
    // Only call once
    if (SC.isReady) return ;
    if (typeof SC.mapDisplayNames === SC.T_FUNCTION) SC.mapDisplayNames();
     
    // setup locale
    SC.Locale.createCurrentLocale();
    
    // if there is a body tag on the document, set the language
    if (document && document.getElementsByTagName) {
      var body = document.getElementsByTagName('body')[0];
      if (body) {
        var className = body.className ;
        var language = SC.Locale.currentLanguage.toLowerCase() ;
        body.className = (className && className.length>0) ? [className, language].join(' ') : language ;
      }
    }

    SC.Benchmark.start('ready') ;
    
    // Begin runloop
    SC.RunLoop.begin();
    
    var handler, ary, idx, len ;

    // correctly handle queueing new SC.ready() calls
    do {
      ary = SC._readyQueue ;
      SC._readyQueue = [] ; // reset
      for (idx=0, len=ary.length; idx<len; idx++) {
        handler = ary[idx] ;
        var target = handler[0] || document ;
        var method = handler[1] ;
        if (method) method.call(target) ;
      }
    } while (SC._readyQueue.length > 0) ;

    // okay, now we're ready (any SC.ready() calls will now be called immediately)
    SC.isReady = YES ;
    
    // clear the queue
    SC._readyQueue = null ;
    
    // trigger any bound ready events
    SC.Event.trigger("ready", null, document, NO) ;
    
    // Remove any loading div
    if (SC.removeLoading) SC.$('#loading').remove();
    
    // Now execute main, if defined
    if ((SC.mode === SC.APP_MODE) && (typeof main != "undefined") && (main instanceof Function) && !SC.suppressMain) main();
    
    // handle routes, if modules is installed.
    if (SC.routes && SC.routes.ping) SC.routes.ping() ; 
    
    // end run loop.  This is probably where a lot of bindings will trigger
    SC.RunLoop.end() ; 
    
    SC.Benchmark.end('ready') ;
    SC.Benchmark.log();
  },
  
  /** 
    Add the passed target and method to the queue of methods to invoke when
    the document is ready.  These methods will be called after the document
    has loaded and parsed, but before the main() function is called.
    
    Methods are called in the order they are added.
  
    If you add a ready handler when the main document is already ready, then
    your handler will be called immediately.
    
    @param target {Object} optional target object
    @param method {Function} method name or function to execute
    @returns {SC}
  */
  ready: function(target, method) {
    var queue = this._readyQueue;
    
    // normalize
    if (method === undefined) {
      method = target; target = null ;
    } else if (SC.typeOf(method) === SC.T_STRING) {
      method = target[method] ;
    }
    
    if (!method) return this; // nothing to do.
    
    // if isReady, execute now.
    if (this.isReady) return method.call(target || document) ;
    
    // otherwise, add to queue.
    queue.push([target, method]) ;
    return this ; 
  }
  
}) ;

SC._bindReady() ;
SC.removeLoading = YES;

// default to app mode.  When loading unit tests, this will run in test mode
SC.APP_MODE = "APP_MODE";
SC.TEST_MODE = "TEST_MODE";
SC.mode = SC.APP_MODE;

/* >>>>>>>>>> BEGIN source/system/render_context.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/builder');

/** set update mode on context to replace content (preferred) */
SC.MODE_REPLACE = 'replace';

/** set update mode on context to append content */
SC.MODE_APPEND = 'append';

/** set update mode on context to prepend content */
SC.MODE_PREPEND = 'prepend';

/**
  @namespace
  
  A RenderContext is a builder that can be used to generate HTML for views or
  to update an existing element.  Rather than making changes to an element
  directly, you use a RenderContext to queue up changes to the element, 
  finally applying those changes or rendering the new element when you are
  finished.
  
  You will not usually create a render context yourself but you will be passed
  a render context as the first parameter of your render() method on custom
  views.
  
  Render contexts are essentially arrays of strings.  You can add a string to
  the context by calling push().  You can retrieve the entire array as a 
  single string using join().  This is basically the way the context is used 
  for views.  You are passed a render context and expected to add strings of
  HTML to the context like a normal array.  Later, the context will be joined
  into a single string and converted into real HTML for display on screen.
  
  In addition to the core push and join methods, the render context also 
  supports some extra methods that make it easy to build tags.  
  
  context.begin() <-- begins a new tag context
  context.end() <-- ends the tag context...
*/
SC.RenderContext = SC.Builder.create(/** SC.RenderContext.fn */ {
  
  SELF_CLOSING: SC.CoreSet.create().addEach('area base basefront br hr input img link meta'.w()),
  
  /** 
    When you create a context you should pass either a tag name or an element
    that should be used as the basis for building the context.  If you pass
    an element, then the element will be inspected for class names, styles
    and other attributes.  You can also call update() or replace() to 
    modify the element with you context contents.
    
    If you do not pass any parameters, then we assume the tag name is 'div'.
    
    A second parameter, parentContext, is used internally for chaining.  You
    should never pass a second argument.
    
    @param {String|DOMElement} tagNameOrElement 
    @returns {SC.RenderContext} receiver
  */
  init: function(tagNameOrElement, prevContext) {
    if (tagNameOrElement === undefined) tagNameOrElement = 'div';
    
    // if a prevContext was passed, setup with that first...
    if (prevContext) {
      this.prevObject = prevContext ;
      this.strings = prevContext.strings ;
      this.offset = prevContext.length + prevContext.offset ;
    } 

    if (!this.strings) this.strings = [] ;
    
    // if tagName is string, just setup for rendering new tagName
    this.needsContent = YES ;
    if (SC.typeOf(tagNameOrElement) === SC.T_STRING) {
      this._tagName = tagNameOrElement.toLowerCase();
      this._needsTag = YES ; // used to determine if end() needs to wrap tag
      
      // increase length of all contexts to leave space for opening tag
      var c = this;
      while(c) { c.length++; c = c.prevObject; }
      
      this.strings.push(null);
      this._selfClosing = this.SELF_CLOSING.contains(this._tagName);
    } else {
      this._elem = tagNameOrElement ;
      this._needsTag = NO ;
      this.length = 0 ;
      this.needsContent = NO ;
    }
    
    return this ;
  },
  
  // ..........................................................
  // PROPERTIES
  // 
  
  // NOTE: We store this as an actual array of strings so that browsers that
  // support dense arrays will use them.
  /** 
    The current working array of strings.
    
    @property {Array}
  */
  strings: null,
  
  /** 
    this initial offset into the strings array where this context instance
    has its opening tag.
    
    @property {Number}
  */
  offset: 0,
  
  /**  
    the current number of strings owned by the context, including the opening
    tag.
    
    @property {Number}
  */
  length: 0,
  
  /**
    Specify the method that should be used to update content on the element.
    In almost all cases you want to replace the content.  Very carefully 
    managed code (such as in CollectionView) can append or prepend content
    instead.
    
    You probably do not want to change this propery unless you know what you
    are doing.
    
    @property {String}
  */
  updateMode: SC.MODE_REPLACE,

  /**
    YES if the context needs its content filled in, not just its outer 
    attributes edited.  This will be set to YES anytime you push strings into
    the context or if you don't create it with an element to start with.
  */
  needsContent: NO,
  
  // ..........................................................
  // CORE STRING API
  // 
  
  /**
    Returns the string at the designated index.  If you do not pass anything
    returns the string array.  This index is an offset from the start of the
    strings owned by this context.
    
    @param {Number} idx the index
    @returns {String|Array}
  */
  get: function(idx) {
    var strings = this.strings || [];
    return (idx === undefined) ? strings.slice(this.offset, this.length) : strings[idx+this.offset];
  },
  
  /**
    Adds a string to the render context for later joining.  Note that you can
    pass multiple arguments to this method and each item will be pushed.
    
    @param {String} line the liene to add to the string.
    @returns {SC.RenderContext} receiver
  */
  push: function(line) {
    var strings = this.strings, len = arguments.length;
    if (!strings) this.strings = strings = []; // create array lazily
    
    if (len > 1) {
      strings.push.apply(strings, arguments) ;
    } else strings.push(line);
    
    // adjust string length for context and all parents...
    var c = this;
    while(c) { c.length += len; c = c.prevObject; }
    
    this.needsContent = YES; 
    
    return this;
  },
  
  /**
    Pushes the passed string onto the array, but first escapes the string
    to ensure that no user-entered HTML is processed as HTML.
    
    @param {String} line one or mroe lines of text to add
    @returns {SC.RenderContext} receiver
  */
  text: function(line) {
    var len = arguments.length, idx=0;
    for(idx=0;idx<len;idx++) {
      this.push(SC.RenderContext.escapeHTML(arguments[idx]));
    }
    return this ;
  },
  
  /**
    Joins the strings together, returning the result.  But first, this will
    end any open tags.
    
    @param {String} joinChar optional string to use in joins. def empty string
    @returns {String} joined string
  */
  join: function(joinChar) {

    // generate tag if needed...
    if (this._needsTag) this.end();
    
    var strings = this.strings;
    return strings ? strings.join(joinChar || '') : '' ;
  },
  
  // ..........................................................
  // GENERATING
  // 
  
  /**
    Begins a new render context based on the passed tagName or element.
    Generate said context using end().
    
    @returns {SC.RenderContext} new context
  */
  begin: function(tagNameOrElement) {
    // console.log('%@.begin(%@) called'.fmt(this, tagNameOrElement));
    return SC.RenderContext(tagNameOrElement, this);
  },
  
  /**
    If the current context targets an element, this method returns the 
    element.  If the context does not target an element, this method will 
    render the context into an offscreen element and return it.
    
    @returns {DOMElement} the element
  */
  element: function() {  
    if (this._elem) return this._elem;
    // create factory div if needed
    var ret, child;
    if (!SC.RenderContext.factory) {
      SC.RenderContext.factory = document.createElement('div');
    }
    SC.RenderContext.factory.innerHTML = this.join();
    
    // In IE something weird happens when reusing the same element.
    // After setting innerHTML, the innerHTML of the element in the previous view
    // turns blank. Seems that there is something weird with their garbage 
    // collection algorithm. I tried just removing the nodes after keeping a 
    // reference to the first child, but it didnt work. 
    // Ended up cloning the first child.
    
    if(SC.RenderContext.factory.innerHTML.length>0){
      child = SC.RenderContext.factory.firstChild.cloneNode(true);
      SC.RenderContext.factory.innerHTML = '';
    } else {
      child = null;
    }
    return child ;
  },
  
  /**
    Removes an element with the passed id in the currently managed element.
  */
  remove: function(elementId) {
    // console.log('remove('+elementId+')');
    if (!elementId) return ;
    
    var el, elem = this._elem ;
    if (!elem || !elem.removeChild) return ;
    
    el = document.getElementById(elementId) ;
    if (el) {
      el = elem.removeChild(el) ;
      el = null;
    }
  },
  
  /**
    If an element was set on this context when it was created, this method 
    will actually apply any changes to the element itself.  If you have not
    written any inner html into the context, then the innerHTML of the 
    element will not be changed, otherwise it will be replaced with the new
    innerHTML.
    
    Also, any attributes, id, classNames or styles you've set will be 
    updated as well.  This also ends the editing context session and cleans
    up.
    
    @returns {SC.RenderContext} previous context or null if top 
  */
  update: function() {
    var elem = this._elem, 
        mode = this.updateMode,
        key, value, styles, factory, cur, next, before;
    
    if (!elem) {
      // throw "Cannot update context because there is no source element";
      return ;
    }
    
    // console.log('%@#update() called'.fmt(this));
    // if (this.length>0) console.log(this.join());
    // else console.log('<no length>');
    
    // replace innerHTML
    if (this.length>0) {
      if (mode === SC.MODE_REPLACE) {
        elem.innerHTML = this.join();
      } else {
        factory = elem.cloneNode(false);
        factory.innerHTML = this.join() ;
        before = (mode === SC.MODE_APPEND) ? null : elem.firstChild;
        cur = factory.firstChild ;
        while(cur) {
          next = cur.nextSibling ;
          elem.insertBefore(cur, next);
          cur = next ;
        }
        cur = next = factory = before = null ; // cleanup 
      }
    }
    
    // note: each of the items below only apply if the private variable has
    // been set to something other than null (indicating they were used at
    // some point during the build)
    
    // if we have attrs, apply them
    if (this._attrsDidChange && (value = this._attrs)) {
      for(key in value) {
        if (!value.hasOwnProperty(key)) continue;
        if (value[key] === null) { // remove empty attrs
          elem.removeAttribute(key);
        } else {
          SC.$(elem).attr(key, value[key]);
        }
      }
    }
    
    // class="foo bar"
    if (this._classNamesDidChange && (value = this._classNames)) {
      SC.$(elem).attr('class', value.join(' '));
    }
    
    // id="foo"
    if (this._idDidChange && (value = this._id)) {
      SC.$(elem).attr('id', value);
    }
    
    // style="a:b; c:d;"
    if (this._stylesDidChange && (styles = this._styles)) {
      var pair = this._STYLE_PAIR_ARRAY, joined = this._JOIN_ARRAY;
      for(key in styles) {
        if (!styles.hasOwnProperty(key)) continue ;
        value = styles[key];
        if (value === null) continue; // skip empty styles
        if (typeof value === SC.T_NUMBER) value = value.toString() + "px";
        pair[0] = key.dasherize(); pair[1] = value;
        joined.push(pair.join(': '));
      }
      
      SC.$(elem).attr('style', joined.join('; '));
      joined.length = 0; // reset temporary object
    }
    
    // now cleanup element...
    elem = this._elem = null ;
    return this.prevObject || this ; 
  },
  
  // these are temporary objects are reused by end() to avoid memory allocs.
  _DEFAULT_ATTRS: {},
  _TAG_ARRAY: [],
  _JOIN_ARRAY: [],
  _STYLE_PAIR_ARRAY: [],
  
  /**
    Ends the current tag editing context.  This will generate the tag string
    including any attributes you might have set along with a closing tag.
    
    The generated HTML will be added to the render context strings.  This will
    also return the previous context if there is one or the receiver.
    
    If you do not have a current tag, this does nothing.
     
    @returns {SC.RenderContext} 
  */
  end: function() {
    // console.log('%@.end() called'.fmt(this));
    // NOTE: If you modify this method, be careful to consider memory usage
    // and performance here.  This method is called frequently during renders
    // and we want it to be as fast as possible.

    // generate opening tag.
    
    // get attributes first.  Copy in className + styles...
    var tag = this._TAG_ARRAY, pair, joined, key ;
    var attrs = this._attrs, className = this._classNames ;
    var id = this._id, styles = this._styles;
    
    // add tag to tag array
    tag[0] = '<';  tag[1] = this._tagName ;
    
    // add any attributes...
    if (attrs || className || styles || id) {
      if (!attrs) attrs = this._DEFAULT_ATTRS ;
      if (id) attrs.id = id ;
      if (className) attrs['class'] = className.join(' ');
    
      // add in styles.  note how we avoid memory allocs here to keep things 
      // fast...
      if (styles) {
        joined = this._JOIN_ARRAY ;
        pair = this._STYLE_PAIR_ARRAY;
        for(key in styles) {
          if(!styles.hasOwnProperty(key)) continue ;
          pair[0] = key.dasherize() ;
          pair[1] = styles[key];
          if (pair[1] === null) continue; // skip empty styles
          
          if(typeof pair[1] === SC.T_NUMBER) pair[1] = "%@px".fmt(pair[1]);
          joined.push(pair.join(': '));
        }
        attrs.style = joined.join('; ') ;
      
        // reset temporary object.  pair does not need to be reset since it 
        // is always overwritten
        joined.length = 0;
      }
      
      // now convert attrs hash to tag array...
      tag.push(' '); // add space for joining0
      for(key in attrs) {
        if (!attrs.hasOwnProperty(key)) continue ;
        if (attrs[key] === null) continue ; // skip empty attrs
        tag.push(key);
        tag.push('="');
        tag.push(attrs[key]) ;
        tag.push('" ') ;
      }
      
      // if we are using the DEFAULT_ATTRS temporary object, make sure we 
      // reset.
      if (attrs === this._DEFAULT_ATTRS) {
        delete attrs.style;  delete attrs['class']; delete attrs.id;
      }
      
    }
    
    // this is self closing if there is no content in between and selfClosing
    // is not set to false.
    var strings = this.strings;
    var selfClosing = (this._selfClosing === NO) ? NO : (this.length === 1) ;
    tag.push(selfClosing ? ' />' : '>') ;
    
    // console.log('selfClosing == %@'.fmt(selfClosing));
    
    strings[this.offset] = tag.join('');
    tag.length = 0 ; // reset temporary object
    
    // now generate closing tag if needed...
    if (!selfClosing) {
      tag[0] = '</' ;
      tag[1] = this._tagName;
      tag[2] = '>';
      strings.push(tag.join(''));
      
      // increase length of receiver and all parents
      var c = this;
      while(c) { c.length++; c = c.prevObject; }
      tag.length = 0; // reset temporary object again
    }
    
    // if there was a source element, cleanup to avoid memory leaks
    this._elem = null;
    
    return this.prevObject || this ;
  },
  
  /**
    Generates a with the passed options.  Like calling context.begin().end().
    
    @param {String} tagName optional tag name.  default 'div'
    @param {Hash} opts optional tag options.  defaults to empty options.
    @returns {SC.RenderContext} receiver 
  */
  tag: function(tagName, opts) {
    return this.begin(tagName, opts).end();
  },
    
  // ..........................................................
  // BASIC HELPERS
  // 
  
  /**
    Reads outer tagName if no param is passed, sets tagName otherwise.
    
    @param {String} tagName pass to set tag name.
    @returns {String|SC.RenderContext} tag name or receiver
  */
  tagName: function(tagName) {
    if (tagName === undefined) {
      if (!this._tagName && this._elem) this._tagName = this._elem.tagName;
      return this._tagName;
    } else {
      this._tagName = tagName;
      this._tagNameDidChange = YES;
      return this ;
    }
  },
  
  /**
    Reads the outer tag id if no param is passed, sets the id otherwise.
    
    @param {String} idName the id or set
    @returns {String|SC.RenderContext} id or receiver
  */
  id: function(idName) {
    if (idName === undefined) {
      if (!this._id && this._elem) this._id = this._elem.id;
      return this._id ;
    } else {
      this._id = idName;
      this._idDidChange = YES;
      return this;
    }
  },
  
  // ..........................................................
  // CSS CLASS NAMES SUPPORT
  // 
  
  /**
    Reads the current classNames array or sets the array if a param is passed.
    Note that if you get the classNames array and then modify it, you MUST 
    call this method again to set the array or else it may not be copied to
    the element.

    If you do pass a classNames array, you can also pass YES for the 
    cloneOnModify param.  This will cause the context to clone the class names
    before making any further edits.  This is useful is you have a shared 
    array of class names you want to start with but edits should not change
    the shared array.
    
    @param {Array} classNames array 
    @param {Boolean} cloneOnModifiy
    @returns {Array|SC.RenderContext} classNames array or receiver
  */
  classNames: function(classNames, cloneOnModify) {
    if (classNames === undefined) {
      if (!this._classNames && this._elem) {
        this._classNames = (SC.$(this._elem).attr('class')||'').split(' ');
      }
      
      if (this._cloneClassNames) {
        this._classNames = (this._classNames || []).slice();
        this._cloneClassNames = NO ;
      }

      // if there are no class names, create an empty array but don't modify.
      if (!this._classNames) this._classNames = [];
      
      return this._classNames ;
    } else {
      this._classNames = classNames ;
      this._cloneClassNames = cloneOnModify || NO ;
      this._classNamesDidChange = YES ;
      return this ;
    }
  },
  
  /**
    Returns YES if the outer tag current has the passed class name, NO 
    otherwise.
    
    @param {String} className the class name
    @returns {Boolean}
  */
  hasClass: function(className) {
    return this.classNames().indexOf(className) >= 0;  
  },
  
  /**
    Adds the specified className to the current tag, if it does not already
    exist.  This method has no effect if there is no open tag.
    
    @param {String} className the class to add
    @returns {SC.RenderContext} receiver
  */
  addClass: function(className) {
    var classNames = this.classNames() ; // handles cloning ,etc.
    if (classNames.indexOf(className)<0) {
      classNames.push(className);
      this._classNamesDidChange = YES ;
    }
    return this;
  },
  
  /**
    Removes the specified className from the current tag.  This method has 
    no effect if there is not an open tag.
    
    @param {String} className the class to add
    @returns {SC.RenderContext} receiver
  */
  removeClass: function(className) {
    var classNames = this._classNames, idx;
    if (!classNames && this._elem) {
      classNames = this._classNames = 
        (SC.$(this._elem).attr('class')||'').split(' ');
    }

    if (classNames && (idx=classNames.indexOf(className))>=0) {
      if (this._cloneClassNames) {
        classNames = this._classNames = classNames.slice();
        this._cloneClassNames = NO ;
      }

      // if className is found, just null it out.  This will end up adding an
      // extra space to the generated HTML but it is faster than trying to 
      // recompact the array.
      classNames[idx] = null;
      this._classNamesDidChange = YES ;
    }
    
    return this;
  },
  
  /**
    Removes all classnames from the currentContext.  
    
    @returns {SC.RenderContext} receiver
  */
  resetClassNames: function() {
    this._classNames = [];
    this._classNamesDidChange = YES ;
    return this;
  },
  
  /**
    You can either pass a single class name and a boolean indicating whether
    the value should be added or removed, or you can pass a hash with all
    the class names you want to add or remove with a boolean indicating 
    whether they should be there or not.
    
    This is far more efficient than using addClass/removeClass.
    
    @param {String|Hash} className class name or hash of classNames + bools
    @param {Boolean} shouldAdd for class name if a string was passed
    @returns {SC.RenderContext} receiver
  */
  setClass: function(className, shouldAdd) {
    var classNames, idx, key, didChange;
    
    // simple form
    if (shouldAdd !== undefined) {
      return shouldAdd ? this.addClass(className) : this.removeClass(className);
      
    // bulk form
    } else {
      
      classNames = this._classNames ;
      if (!classNames && this._elem) {
        classNames = this._classNames = 
          (SC.$(this._elem).attr('class')||'').split(' ');
      }
      if (!classNames) classNames = this._classNames = [];
    
      if (this._cloneClassNames) {
        classNames = this._classNames = classNames.slice();
        this._cloneClassNames = NO ;
      }

      didChange = NO;
      for(key in className) {
        if (!className.hasOwnProperty(key)) continue ;
        idx = classNames.indexOf(key);
        if (className[key]) {
          if (idx<0) { classNames.push(key); didChange = YES; }
        } else {
          if (idx>=0) { classNames[idx] = null; didChange = YES; }
        }
      }
      if (didChange) this._classNamesDidChange = YES;
    }
    
    return this ;
  },
  
  // ..........................................................
  // CSS Styles Support
  // 
    
  _STYLE_REGEX: /\s*([^:\s]+)\s*:\s*([^;]+)\s*;?/g,
  
  /**
    Retrieves or sets the current styles for the outer tag.  If you retrieve
    the styles hash to edit it, you must set the hash again in order for it 
    to be applied to the element on rendering.
    
    Optionally you can also pass YES to the cloneOnModify param to cause the
    styles has to be cloned before it is edited.  This is useful if you want
    to start with a shared style hash and then optionally modify it for each
    context.
    
    @param {Hash} styles styles hash
    @param {Boolean} cloneOnModify
    @returns {Hash|SC.RenderContext} styles hash or receiver
  */
  styles: function(styles, cloneOnModify) {
    var attr, regex, match;
    if (styles === undefined) {
      
      // no styles are defined yet but we do have a source element.  Lazily
      // extract styles from element.
      if (!this._styles && this._elem) {
        // parse style...
        attr = SC.$(this._elem).attr('style');
        
        if (attr && (attr = attr.toString()).length>0) {
          if(SC.browser.msie){ 
            attr = attr.toLowerCase();
          }
          styles = {};
          
          regex = this._STYLE_REGEX ;
          regex.lastIndex = 0;
          
          while(match = regex.exec(attr)) styles[match[1].camelize()] = match[2];
          
          this._styles = styles;
          this._cloneStyles = NO;
          
        } else {
          this._styles = {};
        }
        
      // if there is no element or we do have styles, possibly clone them
      // before returning.
      } else {
        if (!this._styles) {
          this._styles = {};
        } else {
          if (this._cloneStyles) {
            this._styles = SC.beget(this._styles);
            this._cloneStyles = NO ;
          }
        }
      }
      
      return this._styles ;
      
    // set the styles if passed.
    } else {
      this._styles = styles ;
      this._cloneStyles = cloneOnModify || NO ;
      this._stylesDidChange = YES ;
      return this ;
    }
  },
  
  /**
    Apply the passed styles to the tag.  You can pass either a single key
    value pair or a hash of styles.  Note that if you set a style on an 
    existing element, it will replace any existing styles on the element.
    
    @param {String|Hash} nameOrStyles the style name or a hash of styles
    @param {String|Number} value style value if string name was passed
    @returns {SC.RenderContext} receiver
  */
  addStyle: function(nameOrStyles, value) {
    
    // get the current hash of styles.  This will extract the styles and 
    // clone them if needed.  This will get the actual styles hash so we can
    // edit it directly.
    var key, didChange = NO, styles = this.styles();
    
    // simple form
    if (typeof nameOrStyles === SC.T_STRING) {
      if (value === undefined) { // reader
        return styles[nameOrStyles];
      } else { // writer
        if (styles[nameOrStyles] !== value) {
          styles[nameOrStyles] = value ;
          this._stylesDidChange = YES ;
        }
      }
      
    // bulk form
    } else {
      for(key in nameOrStyles) {
        if (!nameOrStyles.hasOwnProperty(key)) continue ;
        value = nameOrStyles[key];
        if (styles[key] !== value) {
          styles[key] = value;
          didChange = YES;
        }
      }
      if (didChange) this._stylesDidChange = YES ;
    }
    
    return this ;
  },
  
  /**
    Removes the named style from the style hash.
    
    Note that if you delete a style, the style will not actually be removed
    from the style hash.  Instead, its value will be set to null.
    
    @param {String} styleName
    @returns {SC.RenderContext} receiver
  */
  removeStyle: function(styleName) {
    // avoid case where no styles have been defined
    if (!this._styles && !this._elem) return this;
    
    // get styles hash.  this will clone if needed.
    var styles = this.styles();
    if (styles[styleName]) {
      styles[styleName] = null;
      this._stylesDidChange = YES ;
    }
  },
  
  // ..........................................................
  // ARBITRARY ATTRIBUTES SUPPORT
  // 
  
  /**
    Sets the named attribute on the tag.  Note that if you set the 'class'
    attribute or the 'styles' attribute, it will be ignored.  Use the 
    relevant class name and style methods instead.
    
    @param {String|Hash} nameOrAttrs the attr name or hash of attrs.
    @param {String} value attribute value if attribute name was passed
    @returns {SC.RenderContext} receiver
  */
  attr: function(nameOrAttrs, value) {
    var key, attrs = this._attrs, didChange = NO ;
    if (!attrs) this._attrs = attrs = {} ;
    
    // simple form
    if (typeof nameOrAttrs === SC.T_STRING) {
      if (value === undefined) { // getter
        return attrs[nameOrAttrs];
      } else { // setter
        if (attrs[nameOrAttrs] !== value) {
          attrs[nameOrAttrs] = value ;
          this._attrsDidChange = YES ;
        }
      }
      
    // bulk form
    } else {
      for(key in nameOrAttrs) {
        if (!nameOrAttrs.hasOwnProperty(key)) continue ;
        value = nameOrAttrs[key];
        if (attrs[key] !== value) {
          attrs[key] = value ;
          didChange = YES ;
        }
      }
      if (didChange) this._attrsDidChange = YES ;
    }
    
    return this ;
  }
  
});

/**
  html is an alias for push().  Makes thie object more CoreQuery like
*/
SC.RenderContext.fn.html = SC.RenderContext.fn.push;

/**
  css is an alias for addStyle().  This this object more CoreQuery like.
*/
SC.RenderContext.fn.css = SC.RenderContext.fn.addStyle;

/** 
  Helper method escapes the passed string to ensure HTML is displayed as 
  plain text.  You should make sure you pass all user-entered data through
  this method to avoid errors.  You can also do this with the text() helper
  method on a render context.
*/


if (!SC.browser.isSafari || parseInt(SC.browser.version, 10) < 526) {
  SC.RenderContext._safari3 = YES;
}

SC.RenderContext.escapeHTML = function(text) {
  var elem, node, ret ;
  
  if (SC.none(text)) return text; // ignore empty
  
  elem = this.escapeHTMLElement;
  if (!elem) elem = this.escapeHTMLElement = document.createElement('div');
  
  node = this.escapeTextNode;
  if (!node) {
    node = this.escapeTextNode = document.createTextNode('');
    elem.appendChild(node);
  }
  
  node.data = text ;
  ret = elem.innerHTML ;
  
  // Safari 3 does not escape the '>' character
  if (SC.RenderContext._safari3) { ret = ret.replace(/>/g, '&gt;'); }

  node = elem = null;
  return ret ;
};

/* >>>>>>>>>> BEGIN source/system/response.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================
/*global ActiveXObject */

/**
  A response represents a single response from a server request.  An instance
  of this class is returned whenever you call SC.Request.send().
  
  TODO: Add more info
  
  @extend SC.Object
  @since SproutCore 1.0
*/
SC.Response = SC.Object.extend(
/** @scope SC.Response.prototype */ {
  
  /**
    Becomes true if there was a failure.  Makes this into an error object.
    
    @property {Boolean}
  */
  isError: NO,
  
  /**
    Always the current response
    
    @property {SC.Response}
  */
  errorValue: function() {
    return this;
  }.property().cacheable(),
  
  /**
    The error object generated when this becomes an error
    
    @property {SC.Error}
  */
  errorObject: null,
  
  /** 
    Request used to generate this response.  This is a copy of the original
    request object as you may have modified the original request object since
    then.
   
    To retrieve the original request object use originalRequest.
    
    @property {SC.Request}
  */
  request: null,
  
  /**
    The request object that originated this request series.  Mostly this is
    useful if you are looking for a reference to the original request.  To
    inspect actual properties you should use request instead.
    
    @property {SC.Request}
  */
  originalRequest: function() {
    var ret = this.get('request');
    while (ret.get('source')) ret = ret.get('source');
    return ret ;
  }.property('request').cacheable(),

  /** 
    Type of request.  Must be an HTTP method.  Based on the request
  
    @property {String}
  */
  type: function() {
    return this.getPath('request.type');
  }.property('request').cacheable(),
  
  /**
    URL of request. 
    
    @property {String}
  */
  address: function() {
    return this.getPath('request.address');
  }.property('request').cacheable(),
  
  /**
    If set then will attempt to automatically parse response as JSON 
    regardless of headers.
    
    @property {Boolean}
  */
  isJSON: function() {
    return this.getPath('request.isJSON') || NO;
  }.property('request').cacheable(),

  /**
    If set, then will attempt to automatically parse response as XML
    regarldess of headers.
    
    @property {Boolean}
  */
  isXML: function() {
    return this.getPath('request.isXML') || NO ;
  }.property('request').cacheable(),
  
  /** 
    Returns the hash of listeners set on the request.
    
    @property {Hash}
  */
  listeners: function() {
    return this.getPath('request.listeners');
  }.property('request').cacheable(),
  
  /**
    The response status code.
    
    @property {Number}
  */
  status: -100, // READY

  /**
    Headers from the response.  Computed on-demand
    
    @property {Hash}
  */
  headers: null,
  
  /**
    The raw body retrieved from the transport.  This may be further parsed
    by the body property, which is probably what you want.
    
    @property {String}
  */
  encodedBody: null,
  
  /**
    Response body. If isJSON was set, will be parsed automatically.
    
    @response {Hash|String|SC.Error} the response body or the parsed JSON.
      Returns a SC.Error instance if there is a JSON parsing error.
  */
  body: function() {
    // TODO: support XML
    var ret = this.get('encodedBody');
    if (ret && this.get('isJSON')) {
      try {
        ret = SC.json.decode(ret);
      } catch(e) {
        return SC.Error.create({
          message: e.name + ': ' + e.message,
          label: 'Response',
          errorValue: this });
      }
    }
    return ret;
  }.property('encodedBody').cacheable(),
  
  /** 
    @private
    @deprecated
  
    Alias for body.  Provides compatibility with older code.
    
    @property {Hash|String}
  */
  response: function() {
    return this.get('body');
  }.property('body').cacheable(),
  
  /**
    Set to YES if response is cancelled
  */
  isCancelled: NO,
  
  // ..........................................................
  // METHODS
  // 

  /**
    Called by the request manager when its time to actually run.  This will
    invoke any callbacks on the source request then invoke transport() to 
    begin the actual request.
  */
  fire: function() {
    
    var req = this.get('request'),
        source = req ? req.get('source') : null;
    
    
    // first give the source a chance to fixup the request and response
    // then freeze req so no more changes can happen.
    if (source && source.willSend) source.willSend(req, this);
    req.freeze();

    // if the source did not cancel the request, then invoke the transport
    // to actually trigger the request.  This might receive a response 
    // immediately if it is synchronous.
    if (!this.get('isCancelled')) this.invokeTransport();

    // if the transport did not cancel the request for some reason, let the
    // source know that the request was sent
    if (!this.get('isCancelled') && source && source.didSend) {
      source.didSend(req, this);
    }
  },

  invokeTransport: function() {
    this.receive(function(proceed) { this.set('status', 200); }, this);
  },
  
  /**
    Invoked by the transport when it receives a response.  The passed 
    callback will be invoked to actually process the response.  If cancelled
    we will pass NO.  You sould cleanup instead.
    
    Invokes callbacks on the source request also.
    
    @param {Function} callback the function to receive
    @param {Object} context context to execute the callback in
    @returns {SC.Response} receiver
  */
  receive: function(callback, context) {
    var req = this.get('request');
    var source = req ? req.get('source') : null;
    
    // invoke the source, giving a chance to fixup the reponse or (more 
    // likely) cancel the request.
    if (source && source.willReceive) source.willReceive(req, this);
    
    // invoke the callback.  note if the response was cancelled or not
    callback.call(context, !this.get('isCancelled'));
    
    // if we weren't cancelled, then give the source first crack at handling
    // the response.  if the source doesn't want listeners to be notified,
    // it will cancel the response.
    if (!this.get('isCancelled') && source && source.didReceive) {
      source.didReceive(req, this);
    }

    // notify listeners if we weren't cancelled.
    if (!this.get('isCancelled')) this.notify();
    
    // no matter what, remove from inflight queue
    SC.Request.manager.transportDidClose(this) ;
    return this;
  },
  
  /**
    Default method just closes the connection.  It will also mark the request
    as cancelled, which will not call any listeners.
  */
  cancel: function() {
    if (!this.get('isCancelled')) {
      this.set('isCancelled', YES);
      this.cancelTransport();
      SC.Request.manager.transportDidClose(this) ;
    }
  },
  
  /**
    Override with concrete implementation to actually cancel the transport.
  */
  cancelTransport: function() {},
  
  
  /** @private
    Will notify each listener.
  */
  _notifyListener: function(listeners, status) {
    var info = listeners[status], params, target, action;
    if (!info) return NO ;
    
    params = (info.params || []).copy();
    params.unshift(this);
    
    target = info.target;
    action = info.action;
    if (SC.typeOf(action) === SC.T_STRING) action = target[action];
    
    return action.apply(target, params);
  },
  
  /**
    Notifies any saved target/action.  Call whenever you cancel, or end.
    
    @returns {SC.Response} receiver
  */
  notify: function() {
    var listeners = this.get('listeners'), 
        status    = this.get('status'),
        baseStat  = Math.floor(status / 100) * 100,
        handled   = NO ;
        
    if (!listeners) return this ; // nothing to do
    
    SC.RunLoop.begin();
    handled = this._notifyListener(listeners, status);
    if (!handled) handled = this._notifyListener(listeners, baseStat);
    if (!handled) handled = this._notifyListener(listeners, 0);
    SC.RunLoop.end();
    
    return this ;
  },
  
  /**
    String representation of the response object
  */
  toString: function() {
    var ret = arguments.callee.base.apply(this,arguments);
    return "%@<%@ %@, status=%@".fmt(ret, this.get('type'), this.get('address'), this.get('status'));
  }
  
});

/**
  Concrete implementation of SC.Response that implements support for using 
  XHR requests.
  
  @extends SC.Response
  @since SproutCore 1.0
*/
SC.XHRResponse = SC.Response.extend({

  /**
    Implement transport-specific support for fetching all headers
  */
  headers: function() {
    var xhr = this.get('rawRequest'),
        str = xhr ? xhr.getAllResponseHeaders() : null,
        ret = {};
        
    if (!str) return ret;
    
    str.split("\n").forEach(function(header) {
      var idx = header.indexOf(':'),
          key, value;
      if (idx>=0) {
        key = header.slice(0,idx);
        value = header.slice(idx+1).trim();
        ret[key] = value ;
      }
    }, this);
    
    return ret ;
  }.property('status').cacheable(),
  
  // returns a header value if found...
  header: function(key) {
    var xhr = this.get('rawRequest');
    return xhr ? xhr.getResponseHeader(key) : null;    
  },
  
  /**
    Implement transport-specific support for fetching tasks
  */
  encodedBody: function() {
    var xhr = this.get('rawRequest'), ret ;
    if (!xhr) ret = null;
    else if (this.get('isXML')) ret = xhr.responseXML;
    else ret = xhr.responseText;
    return ret ;
  }.property('status').cacheable(),
  

  cancelTransport: function() {
    var rawRequest = this.get('rawRequest');
    if (rawRequest) rawRequest.abort();
    this.set('rawRequest', null);
  },
  
  invokeTransport: function() {
    
    var rawRequest, transport, handleReadyStateChange, async, headers;
    
    // Get an XHR object
    function tryThese() {
      for (var i=0; i < arguments.length; i++) {
        try {
          var item = arguments[i]() ;
          return item ;
        } catch (e) {}
      }
      return NO;
    }
    
    rawRequest = tryThese(
      function() { return new XMLHttpRequest(); },
      function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
      function() { return new ActiveXObject('Microsoft.XMLHTTP'); }
    );
    
    // save it 
    this.set('rawRequest', rawRequest);
    
    // configure async callback - differs per browser...
    async = !!this.getPath('request.isAsynchronous') ;
    if (async) {
      if (!SC.browser.msie) {
        SC.Event.add(rawRequest, 'readystatechange', this, 
                     this.finishRequest, rawRequest) ;
      } else {
        transport=this;
        handleReadyStateChange = function() {
          if (!transport) return null ;
          var ret = transport.finishRequest();
          if (ret) transport = null ; // cleanup memory
          return ret ;
        };
        rawRequest.onreadystatechange = handleReadyStateChange;
      }
    }
    
    // initiate request.  
    rawRequest.open(this.get('type'), this.get('address'), async ) ;
    
    // headers need to be set *after* the open call.
    headers = this.getPath('request.headers') ;
    for (var headerKey in headers) {
      rawRequest.setRequestHeader(headerKey, headers[headerKey]) ;
    }

    // now send the actual request body - for sync requests browser will
    // block here
    rawRequest.send(this.getPath('request.encodedBody')) ;
    if (!async) this.finishRequest() ; // not async
    
    return rawRequest ;
  },
  
  /**  @private
  
    Called by the XHR when it responds with some final results.
    
    @param {XMLHttpRequest} rawRequest the actual request
    @returns {SC.XHRRequestTransport} receiver
  */
  finishRequest: function(evt) {
    var rawRequest = this.get('rawRequest'),
        readyState = rawRequest.readyState,
        error, status, msg;

    if (readyState === 4) {
      this.receive(function(proceed) {

        if (!proceed) return ; // skip receiving...
      
        // collect the status and decide if we're in an error state or not
        status = -1 ;
        try {
          status = rawRequest.status || 0;
        } catch (e) {}

        // if there was an error - setup error and save it
        if ((status < 200) || (status >= 300)) {
          msg = rawRequest.statusText;
          error = SC.$error(msg || "HTTP Request failed", "Request", status) ;
          error.set("errorValue", this) ;
          this.set('isError', YES);
          this.set('errorObject', error);
        }

        // set the status - this will trigger changes on relatedp properties
        this.set('status', status);
      
      }, this);
      // avoid memory leak in MSIE: clean up
      rawRequest.onreadystatechange = function() {} ;
      return YES;
    }
    return NO; 
  }

  
});

/* >>>>>>>>>> BEGIN source/system/request.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/response');

/**
  @class
  
  Implements support for Ajax requests using XHR, JSON-P and other prototcols.
  
  SC.Request is much like an inverted version of the request/response objects
  you receive when implement HTTP servers.  
  
  To send a request, you just need to create your request object, configure
  your options, and call send() to initiate the request.
  
  @extends SC.Object
  @extends SC.Copyable
  @extends SC.Freezable
  @since SproutCore 1.0
*/

SC.Request = SC.Object.extend(SC.Copyable, SC.Freezable,
  /** @scope SC.Request.prototype */ {
  
  // ..........................................................
  // PROPERTIES
  // 
  
  /**
    Sends the request asynchronously instead of blocking the browser.  You
    should almost always make requests asynchronous.  You can change this 
    options with the async() helper option (or simply set it directly).
    
    Defaults to YES. 
    
    @property {Boolean}
  */
  isAsynchronous: YES,

  /**
    Processes the request and response as JSON if possible.  You can change
    this option with the json() helper method.

    Defaults to NO 
    
    @property {Boolean}
  */
  isJSON: NO,

  /**
    Process the request and response as XML if possible.  You can change this
    option with the xml() helper method.
    
    Defaults to NO
  
    @property {Boolean}
  */
  isXML: NO,
  
  /**
    Current set of headers for the request
  */
  headers: function() {
    var ret = this._headers ;
    if (!ret) ret = this._headers = {} ;
    return ret ;  
  }.property().cacheable(),

  /**
    Underlying response class to actually handle this request.  Currently the
    only supported option is SC.XHRResponse which uses a traditional
    XHR transport.
    
    @property {SC.Response}
  */
  responseClass: SC.XHRResponse,

  /**
    The original request for copied requests.
    
    @property {SC.Request}
  */
  source: null,
  
  /**
    The URL this request to go to.
    
    @param {String}
  */
  address: null,
  
  /**
    The HTTP method to use.
    
    @param {String}
  */
  type: 'GET',
  
  /**
    The body of the request.  May be an object is isJSON or isXML is set,
    otherwise should be a string.
  */
  body: null,
  
  /**
    The body, encoded as JSON or XML if needed.
  */
  encodedBody: function() {
    // TODO: support XML
    var ret = this.get('body');
    if (ret && this.get('isJSON')) ret = SC.json.encode(ret);
    return ret ;
  }.property('isJSON', 'isXML', 'body').cacheable(),

  // ..........................................................
  // CALLBACKS
  // 
  
  /**
    Invoked on the original request object just before a copied request is 
    frozen and then sent to the server.  This gives you one last change to 
    fixup the request; possibly adding headers and other options.
    
    If you do not want the request to actually send, call cancel().
    
    @param {SC.Request} request a copy of the request, not frozen
    @returns {void}
  */
  willSend: function(request, response) {},
  
  /**
    Invoked on the original request object just after the request is sent to
    the server.  You might use this callback to update some state in your 
    application.
    
    The passed request is a frozen copy of the request, indicating the 
    options set at the time of the request.
    
    @param {SC.Request} request a copy of the request, frozen
    @param {SC.Response} response the object that will carry the response
    @returns {void}
  */
  didSend: function(request, response) {},
  
  /**
    Invoked when a response has been received but not yet processed.  This is
    your chance to fixup the response based on the results.  If you don't want
    to continue processing the response call response.cancel().
    
    @param {SC.Response} response the response
    @returns {void}
  */
  willReceive: function(request, response) {},
  
  /**
    Invoked after a response has been processed but before any listeners are
    notified.  You can do any standard processing on the request at this 
    point.  If you don't want to allow notifications to continue, call
    response.cancel()
    
    @param {SC.Response} response reponse
    @returns {void}
  */
  didReceive: function(request, response) {},
  
  // ..........................................................
  // HELPER METHODS
  // 

  COPY_KEYS: 'isAsynchronous isJSON isXML address type body responseClass willSend didSend willReceive didReceive'.w(),
  
  /**
    Returns a copy of the current request.  This will only copy certain
    properties so if you want to add additional properties to the copy you
    will need to override copy() in a subclass.
    
    @returns {SC.Request} new request
  */
  copy: function() {
    var ret = {},
        keys = this.COPY_KEYS,
        loc  = keys.length, 
        key, listeners, headers;
        
    while(--loc>=0) {
      key = keys[loc];
      if (this.hasOwnProperty(key)) ret[key] = this.get(key);
    }
    
    if (this.hasOwnProperty('listeners')) {
      ret.listeners = SC.copy(this.get('listeners'));
    }
    
    if (this.hasOwnProperty('_headers')) {
      ret._headers = SC.copy(this._headers);
    }
    
    ret.source = this.get('source') || this ;
    
    return this.constructor.create(ret);
  },
  
  /**
    To set headers on the request object.  Pass either a single key/value 
    pair or a hash of key/value pairs.  If you pass only a header name, this
    will return the current value of the header.
    
    @param {String|Hash} key
    @param {String} value
    @returns {SC.Request|Object} receiver
  */
  header: function(key, value) {
    var headers;
    
    if (SC.typeOf(key) === SC.T_STRING) {
      headers = this._headers ;
      if (arguments.length===1) {
        return headers ? headers[key] : null;
      } else {
        this.propertyWillChange('headers');
        if (!headers) headers = this._headers = {};
        headers[key] = value;
        this.propertyDidChange('headers');
        return this;
      }
    
    // handle parsing hash of parameters
    } else if (value === undefined) {
      headers = key;
      this.beginPropertyChanges();
      for(key in headers) {
        if (!headers.hasOwnProperty(key)) continue ;
        this.header(key, headers[key]);
      }
      this.endPropertyChanges();
      return this;
    }

    return this ;
  },
  
  /**
    Converts the current request to use JSON.
    
    @property {Boolean} flag YES to make JSON, NO or undefined
    @returns {SC.Request} receiver
  */
  json: function(flag) {
    if (flag === undefined) flag = YES;
    if (flag) this.set('isXML', NO);
    return this.set('isJSON', flag);
  },
  
  /**
    Converts the current request to use XML.
    
    @property {Boolean} flag YES to make XML, NO or undefined
    @returns {SC.Request} recevier
  */
  xml: function(flag) {
    if (flag === undefined) flag = YES ;
    if (flag) this.set('isJSON', NO);
    return this.set('isXML', flag);
  },
  
  /** 
    Called just before a request is enqueued.  This will encode the body 
    into JSON if it is not already encoded.
  */
  _prep: function() {
    var hasContentType = !!this.header('Content-Type');
    if (this.get('isJSON') && !hasContentType) {
      this.header('Content-Type', 'application/json');
    } else if (this.get('isXML') && !hasContentType) {
      this.header('Content-Type', 'text/xml');
    }
    return this ;
  },
  
  /**
    Will fire the actual request.  If you have set the request to use JSON 
    mode then you can pass any object that can be converted to JSON as the 
    body.  Otherwise you should pass a string body.
    
    @param {String|Object} body (optional)
    @returns {SC.Response} new response object
  */  
  send: function(body) {
    if (body) this.set('body', body);
    return SC.Request.manager.sendRequest(this.copy()._prep());
  },

  /**
    Resends the current request.  This is more efficient than calling send()
    for requests that have already been used in a send.  Otherwise acts just
    like send().  Does not take a body argument.
    
    @returns {SC.Response} new response object
  */
  resend: function() {
    var req = this.get('source') ? this : this.copy()._prep();
    return SC.Request.manager.sendRequest(req);
  },
  
  /**
    Configures a callback to execute when a request completes.  You must pass
    at least a target and action/method to this and optionally a status code.
    You may also pass additional parameters which will be passed along to your
    callback.
    
    h2. Scoping With Status Codes
    
    If you pass a status code as the first option to this method, then your 
    notification callback will only be called if the response status matches
    the code.  For example, if you pass 201 (or SC.Request.CREATED) then 
    your method will only be called if the response status from the server
    is 201.
    
    You can also pass "generic" status codes such as 200, 300, or 400, which
    will be invoked anytime the status code is the range if a more specific 
    notifier was not registered first and returned YES.  
    
    Finally, passing a status code of 0 or no status at all will cause your
    method to be executed no matter what the resulting status is unless a 
    more specific notifier was registered and returned YES.
    
    h2. Callback Format
    
    Your notification callback should expect to receive the Response object
    as the first parameter plus any additional parameters that you pass.  
    
    @param {Number} status
    @param {Object} target
    @param {String|function} action
    @param {Hash} params
    @returns {SC.Request} receiver
  */
  notify: function(status, target, action, params) {
    
    // normalize status
    var hasStatus = YES, params ;
    if (SC.typeOf(status) !== SC.T_NUMBER) {
      params = SC.A(arguments).slice(2);
      action = target;
      target = status;
      status = 0 ;
      hasStatus = NO ;
    } else params = SC.A(arguments).slice(3);
    
    var listeners = this.get('listeners');
    if (!listeners) this.set('listeners', listeners = {});
    listeners[status] = { target: target, action: action, params: params };

    return this;
  }
    
});

SC.Request.mixin(/** @scope SC.Request */ {
  
  /**
    Helper method for quickly setting up a GET request.

    @param {String} address url of request
    @returns {SC.Request} receiver
  */
  getUrl: function(address) {
    return this.create().set('address', address).set('type', 'GET');
  },

  /**
    Helper method for quickly setting up a POST request.

    @param {String} address url of request
    @param {String} body
    @returns {SC.Request} receiver
  */
  postUrl: function(address, body) {
    var req = this.create().set('address', address).set('type', 'POST');
    if(body) req.set('body', body) ;
    return req ;
  },

  /**
    Helper method for quickly setting up a DELETE request.

    @param {String} address url of request
    @returns {SC.Request} receiver
  */
  deleteUrl: function(address) {
    return this.create().set('address', address).set('type', 'DELETE');
  },

  /**
    Helper method for quickly setting up a PUT request.

    @param {String} address url of request
    @param {String} body
    @returns {SC.Request} receiver
  */
  putUrl: function(address, body) {
    var req = this.create().set('address', address).set('type', 'PUT');
    if(body) req.set('body', body) ;
    return req ;
  }
  
});



/**
  The request manager coordinates all of the active XHR requests.  It will
  only allow a certain number of requests to be active at a time; queuing 
  any others.  This allows you more precise control over which requests load
  in which order.
*/
SC.Request.manager = SC.Object.create( SC.DelegateSupport, {

  /**
    Maximum number of concurrent requests allowed.  6 for all browsers.
    
    @property {Number}
  */
  maxRequests: 6,

  /**
    Current requests that are inflight.
    
    @property {Array}
  */
  inflight: [],
  
  /**
    Requests that are pending and have not been started yet.
  
    @property {Array}
  */
  pending: [],

  // ..........................................................
  // METHODS
  // 
  
  /**
    Invoked by the send() method on a request.  This will create a new low-
    level transport object and queue it if needed.
    
    @param {SC.Request} request the request to send
    @returns {SC.Object} response object
  */
  sendRequest: function(request) {
    if (!request) return null ;
    
    // create low-level transport.  copy all critical data for request over
    // so that if the request has been reconfigured the transport will still
    // work.
    var response = request.get('responseClass').create({ request: request });

    // add to pending queue
    this.get('pending').pushObject(response);
    this.fireRequestIfNeeded();
    
    return response ;
  },

  /** 
    Cancels a specific request.  If the request is pending it will simply
    be removed.  Otherwise it will actually be cancelled.
    
    @param {Object} response a response object
    @returns {Boolean} YES if cancelled
  */
  cancel: function(response) {

    var pending = this.get('pending'),
        inflight = this.get('inflight'),
        idx ;

    if (pending.indexOf(response) >= 0) {
      this.propertyWillChange('pending');
      pending.removeObject(response);
      this.propertyDidChange('pending');
      return YES;
      
    } else if (inflight.indexOf(response) >= 0) {
      
      response.cancel();
      
      inflight.removeObject(response);
      this.fireRequestIfNeeded();
      return YES;

    } else return NO ;
  },  

  /**
    Cancels all inflight and pending requests.  
    
    @returns {Boolean} YES if any items were cancelled.
  */
  cancelAll: function() {
    if (this.get('pending').length || this.get('inflight').length) {
      this.set('pending', []);
      this.get('inflight').forEach(function(r) { r.cancel(); });
      this.set('inflight', []);
      return YES;
      
    } else return NO ;
  },
  
  /**
    Checks the inflight queue.  If there is an open slot, this will move a 
    request from pending to inflight.
    
    @returns {Object} receiver
  */
  fireRequestIfNeeded: function() {
    var pending = this.get('pending'), 
        inflight = this.get('inflight'),
        max = this.get('maxRequests'),
        next ;
        
    if ((pending.length>0) && (inflight.length<max)) {
      next = pending.shiftObject();
      inflight.pushObject(next);
      next.fire();
    }
  },

  /**
    Called by a response/transport object when finishes running.  Removes 
    the transport from the queue and kicks off the next one.
  */
  transportDidClose: function(response) {
    this.get('inflight').removeObject(response);
    this.fireRequestIfNeeded();
  }
  
});

/* >>>>>>>>>> BEGIN source/system/root_responder.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('system/ready');

/** @class

  The RootResponder captures events coming from a web browser and routes them 
  to the correct view in the view hierarchy.  Usually you do not work with a 
  RootResponder directly.  Instead you will work with Pane objects, which 
  register themselves with the RootResponder as needed to receive events.
  
  h1. RootResponder and Platforms
  
  RootResponder is implemented differently on the desktop and mobile platforms
  using a technique called a "class cluster".  That is, although you get a 
  RootResponder instance at some point, you will likely be working with a 
  subclass of RootResponder that implements functionality unique to that 
  platform.
  
  The RootResponder you use depends on the active platform you have set and
  the framework you have loaded.
  
  h1. Event Types 
  
  RootResponders can route four types of events:
  
  - Direct events.  Such as mouse and touch events.  These are routed to the 
    nearest view managing the target DOM elment.
  - Keyboard events.  These are sent to the keyPane, which will send it 
    to the current firstResponder.
  - resize. This event is sent to all panes.
  - shortcuts.  Shortcuts are sent to the focusedPane first, which will go 
    down its view hierarchy.  Then they go to the mainPane, which will go down
    its view hierarchy.  Then they go to the mainMenu.  Usually any handler 
    that picks this up will then try to do a sendAction().
  - actions.  Actions are sent down the responder chain.  They go to 
    focusedPane -> mainPane.  Each of these will start at the firstResponder 
    and work their way up the chain.
  
  Differences between Mobile + Desktop RootResponder
  
  The Desktop root responder can deal with the following kinds of events:
   mousedown, mouseup, mouseover, mouseout, mousemoved
*/
SC.RootResponder = SC.Object.extend({
  
  /**
    Contains a list of all panes currently visible on screen.  Everytime a 
    pane attaches or detaches, it will update itself in this array.
  */
  panes: null,
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    this.panes = SC.Set.create();
  },
  
  // .......................................................
  // MAIN Pane
  // 
  
  /** @property
    The main pane.  This pane receives shortcuts and actions if the 
    focusedPane does not respond to them.  There can be only one main pane.  
    You can swap main panes by calling makeMainPane() here.
    
    Usually you will not need to edit the main pane directly.  Instead, you 
    should use a MainPane subclass, which will automatically make itself main 
    when you append it to the document.
  */
  mainPane: null,
  
  /** 
    Swaps the main pane.  If the current main pane is also the key pane, then 
    the new main pane will also be made key view automatically.  In addition 
    to simply updating the mainPane property, this method will also notify the
    panes themselves that they will lose/gain their mainView status.
    
    Note that this method does not actually change the Pane's place in the 
    document body.  That will be handled by the Pane itself.
    
    @param {SC.Pane} pane
    @returns {SC.RootResponder} receiver
  */
  makeMainPane: function(pane) {
    var currentMain = this.get('mainPane') ;
    if (currentMain === pane) return this ; // nothing to do
    
    this.beginPropertyChanges() ;
    
    // change key focus if needed.
    if (this.get('keyPane') === currentMain) this.makeKeyPane(pane) ;
    
    // change setting
    this.set('mainPane', pane) ;
    
    // notify panes.  This will allow them to remove themselves.
    if (currentMain) currentMain.blurMainTo(pane) ;
    if (pane) pane.focusMainFrom(currentMain) ;
    
    this.endPropertyChanges() ;
    return this ;
  }, 
  
  // .......................................................
  // KEY ROOT VIEW
  // 
  
  /** @property
    The current key pane.  This pane receives keyboard events, shortcuts, and 
    actions first.  This pane is usually the highest ordered pane or the 
    mainPane.
  */
  keyPane: null,
    
  /** @property
    A stack of the previous key panes.
    
    *IMPORTANT: Property is not observable*
  */
  previousKeyPanes: [],
  
  /**
    Makes the passed pane the new key pane.  If you pass nil or if the pane 
    does not accept key focus, then key focus will transfer to the previous
    key pane (if it is still attached), and so on down the stack.  This will
    notify both the old pane and the new root View that key focus has changed.
    
    @param {SC.Pane} pane
    @returns {SC.RootResponder} receiver
  */
  makeKeyPane: function(pane) {
    // Was a pane specified?
    var newKeyPane, previousKeyPane, previousKeyPanes ;
    
    if (pane) {
      // Does the specified pane accept being the key pane?  If not, there's
      // nothing to do.
      if (!pane.get('acceptsKeyPane')) {
        return this ;
      }
      else {
        // It does accept key pane status?  Then push the current keyPane to
        // the top of the stack and make the specified pane the new keyPane.
        // First, though, do a sanity-check to make sure it's not already the
        // key pane, in which case we have nothing to do.
        previousKeyPane = this.get('keyPane') ;
        if (previousKeyPane === pane) {
          return this ;
        }
        else {
          if (previousKeyPane) {
            previousKeyPanes = this.get('previousKeyPanes') ;
            previousKeyPanes.push(previousKeyPane) ;
          }
          
          newKeyPane = pane ;
        }
      }
    }
    else {
      // No pane was specified?  Then pop the previous key pane off the top of
      // the stack and make it the new key pane, assuming that it's still
      // attached and accepts key pane (its value for acceptsKeyPane might
      // have changed in the meantime).  Otherwise, we'll keep going up the
      // stack.
      previousKeyPane = this.get('keyPane') ;
      previousKeyPanes = this.get('previousKeyPanes') ;
  
      newKeyPane = null ;
      while (previousKeyPanes.length > 0) {
        var candidate = previousKeyPanes.pop();
        if (candidate.get('isPaneAttached')  &&  candidate.get('acceptsKeyPane')) {
          newKeyPane = candidate ;
          break ;
        }
      }
    }
    
    
    // If we found an appropriate candidate, make it the new key pane.
    // Otherwise, make the main pane the key pane (if it accepts it).
    if (!newKeyPane) {
      var mainPane = this.get('mainPane') ;
      if (mainPane && mainPane.get('acceptsKeyPane')) newKeyPane = mainPane ;
    }
    
    // now notify old and new key views of change after edit    
    if (previousKeyPane) previousKeyPane.willLoseKeyPaneTo(newKeyPane) ;
    if (newKeyPane) newKeyPane.willBecomeKeyPaneFrom(previousKeyPane) ;
    
    this.set('keyPane', newKeyPane) ;
    
    if (newKeyPane) newKeyPane.didBecomeKeyPaneFrom(previousKeyPane) ;
    if (previousKeyPane) previousKeyPane.didLoseKeyPaneTo(newKeyPane) ;
    
    return this ;
  },
  
  /**
    Overridden by subclasses to return the window size.  The default simply
    returns 640 x 480.
    
    @returns {Size} the size of the window in pixels
  */
  computeWindowSize: function() { 
    return { width: 640, height: 480 } ;
  },
  
  // .......................................................
  // ACTIONS
  // 
  
  /** @property
    Set this to a delegate object that can respond to actions as they are sent
    down the responder chain.
  */
  defaultResponder: null,
  
  /**
    Route an action message to the appropriate responder.  This method will 
    walk the responder chain, attempting to find a responder that implements 
    the action name you pass to this method.  Set 'tagret' to null to search 
    the responder chain.
    
    IMPORTANT: This method's API and implementation will likely change 
    significantly after SproutCore 1.0 to match the version found in 
    SC.ResponderContext.
    
    You generally should not call or override this method in your own 
    applications.
    
    @param {String} action The action to perform - this is a method name.
    @param {SC.Responder} target object to set method to (can be null)
    @param {Object} sender The sender of the action
    @param {SC.Pane} pane optional pane to start search with
    @param {Object} context optional. only passed to ResponderContexts
    @returns {Boolean} YES if action was performed, NO otherwise
    @test in targetForAction
  */
  sendAction: function( action, target, sender, pane, context) {
    target = this.targetForAction(action, target, sender, pane) ;
    
    // HACK: If the target is a ResponderContext, forward the action.
    if (target && target.isResponderContext) {
      return !!target.sendAction(action, sender, context);
    } else return target && target.tryToPerform(action, sender);
  },
  
  _responderFor: function(target, methodName) {
    var defaultResponder = target ? target.get('defaultResponder') : null;

    if (target) {
      target = target.get('firstResponder') || target;
      do {
        if (target.respondsTo(methodName)) return target ;
      } while (target = target.get('nextResponder')) ;
    }

    // HACK: Eventually we need to normalize the sendAction() method between
    // this and the ResponderContext, but for the moment just look for a 
    // ResponderContext as the defaultResponder and return it if present.
    if (typeof defaultResponder === SC.T_STRING) {
      defaultResponder = SC.objectForPropertyPath(defaultResponder);
    }

    if (!defaultResponder) return null;
    else if (defaultResponder.isResponderContext) return defaultResponder;
    else if (defaultResponder.respondsTo(methodName)) return defaultResponder;
    else return null;
  },
  
  /**
    Attempts to determine the initial target for a given action/target/sender 
    tuple.  This is the method used by sendAction() to try to determine the 
    correct target starting point for an action before trickling up the 
    responder chain.
    
    You send actions for user interface events and for menu actions.
    
    This method returns an object if a starting target was found or null if no
    object could be found that responds to the target action.
    
    Passing an explicit target or pane constrains the target lookup to just
    them; the defaultResponder and other panes are *not* searched.
    
    @param {Object|String} target or null if no target is specified
    @param {String} method name for target
    @param {Object} sender optional sender
    @param {SC.Pane} optional pane
    @returns {Object} target object or null if none found
  */
  targetForAction: function(methodName, target, sender, pane) {
    
    // 1. no action, no target...
    if (!methodName || (SC.typeOf(methodName) !== SC.T_STRING)) {
      return null ;
    }
    
    // 2. an explicit target was passed...
    if (target) {
      if (SC.typeOf(target) === SC.T_STRING) {
        target = SC.objectForPropertyPath(target) ;
      }
      
      if (target) {
        if (target.respondsTo && !target.respondsTo(methodName)) {
          target = null ;
        } else if (SC.typeOf(target[methodName]) !== SC.T_FUNCTION) {
          target = null ;
        }
      }
      
      return target ;
    }
    
    // 3. an explicit pane was passed...
    if (pane) {
      return this._responderFor(pane, methodName) ;
    }
    
    // 4. no target or pane passed... try to find target in the active panes
    // and the defaultResponder
    var keyPane = this.get('keyPane'), mainPane = this.get('mainPane') ;
    
    // ...check key and main panes first
    if (keyPane && (keyPane !== pane)) {
      target = this._responderFor(keyPane, methodName) ;
    }
    if (!target && mainPane && (mainPane !== keyPane)) {
      target = this._responderFor(mainPane, methodName) ;
    }
    
    // ...still no target? check the defaultResponder...
    if (!target && (target = this.get('defaultResponder'))) {
      if (SC.typeOf(target) === SC.T_STRING) {
        target = SC.objectForPropertyPath(target) ;
        if (target) this.set('defaultResponder', target) ; // cache if found
      }
      if (target) {
        if (target.respondsTo && !target.respondsTo(methodName)) {
          target = null ;
        } else if (SC.typeOf(target[methodName]) !== SC.T_FUNCTION) {
          target = null ;
        }
      }
    }
    
    return target ;
  },
  
  /**
    Finds the view that appears to be targeted by the passed event.  This only
    works on events with a valid target property.
    
    @param {SC.Event} evt
    @returns {SC.View} view instance or null
  */
  targetViewForEvent: function(evt) {
    return evt.target ? SC.$(evt.target).view()[0] : null ;
  },
  
  /**
    Attempts to send an event down the responder chain.  This method will 
    invoke the sendEvent() method on either the keyPane or on the pane owning 
    the target view you pass in.  It will also automatically begin and end 
    a new run loop.
    
    If you want to trap additional events, you should use this method to 
    send the event down the responder chain.
    
    @param {String} action
    @param {SC.Event} evt
    @param {Object} target
    @returns {Object} object that handled the event or null if not handled
  */
  sendEvent: function(action, evt, target) {
    var pane, ret ;
     
    SC.RunLoop.begin() ;
    
    // get the target pane
    if (target) pane = target.get('pane') ;
    else pane = this.get('keyPane') || this.get('mainPane') ;
    
    // if we found a valid pane, send the event to it
    ret = (pane) ? pane.sendEvent(action, evt, target) : null ;
    
    SC.RunLoop.end() ;
    
    return ret ;
  },
  
  // .......................................................
  // EVENT LISTENER SETUP
  // 
  
  /**
    Default method to add an event listener for the named event.  If you simply 
    need to add listeners for a type of event, you can use this method as 
    shorthand.  Pass an array of event types to listen for and the element to 
    listen in.  A listener will only be added if a handler is actually installed 
    on the RootResponder of the same name.
    
    @param {Array} keyNames
    @param {Element} target
    @returns {SC.RootResponder} receiver
  */
  listenFor: function(keyNames, target) {
    keyNames.forEach( function(keyName) {
      var method = this[keyName] ;
      if (method) SC.Event.add(target, keyName, this, method) ;
    },this) ;
    target = null ;
    return this ;
  },
  
  /** 
    Called when the document is ready to begin handling events.  Setup event 
    listeners in this method that you are interested in observing for your 
    particular platform.  Be sure to call arguments.callee.base.apply(this,arguments).
    
    @returns {void}
  */
  setup: function() {}
    
});

/* 
  Invoked when the document is ready, but before main is called.  Creates 
  an instance and sets up event listeners as needed.
*/
SC.ready(SC.RootResponder, SC.RootResponder.ready = function() {
  var r;
  r = SC.RootResponder.responder = SC.RootResponder.create() ;
  r.setup() ;
});

/* >>>>>>>>>> BEGIN source/system/routes.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class
  
  Routes makes it possible to load a location in the browser.
  
  This is useful when application need to change state depending upon the URL 
  change. Applications can support deep-linking using routes, which means user 
  can type specific URL to see certain state of the app e.g.
  http://localhost:4020/routes_demo#Documents/Photographs
  
  To use Routes, first add routes by using SC.routes.add(route, target, 
  method).
  
  - *route* - Route is the part of the URL that come after hash (#).
  - *target* - Object whose route handler needs to be invoked.
  - *method* - Method that is the route handler.
  
  This registers the route to the routes system. When application's URL 
  matches a registered route, system triggers the route handler. Route handler 
  should contain the app logic to bring the app to the required state.
  
  Second thing to do with routes is to set location. Whenever you want to 
  register any URL location in browser history you can use 
  SC.routes.set('location', 'some_path'); 
  
  This will register the URL to browser history and also change the URL of the 
  application. Ideally when you set the location you would like route handler 
  to get invoked. You should have a route registered to match this pattern of 
  the location.
  
  h2. Example
  
  {{{
    SC.routes.add(':', RoutesDemo, 'routeHandler');
  }}}
  
  This route would match any URL change. Whatever comes after # would get 
  passed as parameter. RouteDemo is the object that contains method 
  'routeHandler'.
  
  {{{
    SC.routes.set('location', 'Documents/Photographs');
  }}}
  
  Doing this changes the location to #Documents/Photographs. Part after #, 
  Documents/Photographs in this case, gets passed as parameter to route 
  handler.
  
  If your url has a route, the corresponding routeHandler is fired only after 
  the app's main function is executed. If you need the routeHandlers to be fired
  much before running main(), then you probably read the location hash manually, 
  possibly in the bootstrap of your app and not use routes.

  @extends SC.Object
  @since SproutCore 1.0
*/
SC.routes = SC.Object.create(
/** @scope SC.routes.prototype */ {
  
  // set this property to your current app lication
  location: function(key,value) {
    if (value !== undefined) {
      if (value === null) value = '' ;
      
      // convert an object hash to a string, if it was passed.
      if (typeof(value) == "object") {
        
        // get the original route and any params
        var parts = value.route ? value.route.split('&') : [''] ;
        var route = parts.shift() ;
        var params = {} ;
        parts.forEach(function(p) {
          var bits = p.split('=') ;
          params[bits[0]] = bits[1] ;
        }) ;
        
        // overlay any params passed in the object.
        for(key in value) {
          if (!value.hasOwnProperty(key)) continue ;
          if (key != 'route') {
            params[key] = encodeURIComponent(''+value[key]) ;
          }
        }
        
        // now build params.
        parts = [route] ;
        for(key in params) {
          if (!params.hasOwnProperty(key)) continue ;
          parts.push([key,params[key]].join('=')) ;          
        }
        
        // and combine.
        value = parts.join('&') ;
      }
      
      if (this._location != value) {
        this._location = value ;
        this._setWindowLocation(value) ;
        //this.gotoRoute(value) ;
      }
    }
    return this._location ;
  }.property(),
  
  /**
    Ensures we are at the current route location.
  */
  ping: function() { 
    if (!this._didSetupHistory) {
      this._didSetupHistory = true ;
      this._setupHistory() ;
    }
    this._checkWindowLocation(); 
  },
  
  /**
    Register a route here.  Routes have the following format:
    
    static/route/path -- matches this path only.
    static/route/:path -- matches any static/route, :path passed as param.
    static/ *route -- matches any static, route gets rest of URL.
    
    parameters can also be passed using &.
    static/route&param1=value&param2=value2
    
    @param {string} route
    @param {Object} target
    @param {Function or String} method or method name on target
    @returns {SC.routes} receiver
  */
  add: function(route, target, method) {
    // normalize the target/method
    if (method===undefined && SC.typeOf(target) === SC.T_FUNCTION) {
      method = target; target = null ;
    } else if (SC.typeOf(method) === SC.T_STRING) {
      method = target[method] ;
    }
    
    var parts = route.split('/') ;
    if (!this._routes) this._routes = SC.routes._Route.create() ;
    this._routes.addRoute(parts, target, method) ;
    return this;
  },
  
  /**
    Eval routes.
    
    @param {String} route
  */
  gotoRoute: function(route) {
    var params = {},
        parts, routeHandler, target, method ;
    
    // save this route for window location sensing
    this._lastRoute = route ;
    
    // step 1: split out parameters
    parts = route.split('&') ;
    if (parts && parts.length > 0) {
      route = parts.shift() ;
      parts.forEach(function(part) {
        var param = part.split('=') ;
        if (param && param.length > 1) params[param[0]] = decodeURIComponent(param[1]) ;
      }) ;
    } else route = '' ;
    
    // step 2: split our route parts
    parts = route.split('/') ;
    
    // step 3: evaluate route.
    if (!this._routes) this._routes = SC.routes._Route.create() ;
    
    routeHandler = this._routes.functionForRoute(parts,params) ;
    
    if (routeHandler) {
      target = routeHandler._target;
      method = routeHandler._method;
      if (method) method.call(target, params);
    }
      //else console.log('could not find route for: "'+route+'"') ;
  },
  
  /** @private */
  init: function() {
    arguments.callee.base.call(this) ;
    if (SC.browser.isSafari && parseInt(SC.browser.version,0) < 417) {
      SC.mixin(this,this.browserFuncs.safari) ;  
    } else if (SC.browser.isIE) {
      SC.mixin(this,this.browserFuncs.ie) ;  
    } else if (SC.browser.isMozilla) {
      SC.mixin(this,this.browserFuncs.firefox);
    }
    this._didSetupHistory = false ;
  },
  
  // use this method instead of invokeLater() to check windowLocation since
  // we don't want to trigger runLoops.
  invokeCheckWindowLocation: function(after) {
    var f = this.__checkWindowLocation, that = this;
    if (!f) {
      f = this.__checkWindowLocation = function() {
        that._checkWindowLocation();
      };
    }
    setTimeout(f, after);
  },
  
  /** @private
    _checkWindowLocation and _setWindowLocation are implemented separately for
    each browser.  Below are the implementations, which get copied during init.
  */
  browserFuncs: {
    
    // for Safari2 and earlier.
    safari: {
      
      _setupHistory: function() {
        // get initial cloc.
        var cloc = location.hash ;
        cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
        this._cloc = cloc ;
        
        // create back stack.
        this._backStack = [] ;
        this._backStack.length = history.length ;
        this._backStack.push(cloc) ;
        
        // create forward stack.
        this._forwardStack = [] ;
        
        this.invokeCheckWindowLocation(1000) ;
      },
      
      _checkWindowLocation: function() { 
        // The way we know the user has moved forward or back in the history 
        // is when the length of the history array no longer matched our own 
        // copy of the history. However, when we first change locations, it 
        // takes a little while for Safari to catch up.  So what we do instead 
        // is first check to see if Safari's length has changed from its last 
        // known length and only then check for a delta.
        var historyDidChange = (history.length - this._lastLength) !== 0;
        var delta = (historyDidChange) ? (history.length - this._backStack.length) : 0 ;
        this._lastLength = history.length ;
        
        if (historyDidChange) console.log('historyDidChange') ;
        
        // if the history length has changed, then we need to move forward or 
        // back in the history.
        if (delta) {
          if (delta < 0) { // back button has been pushed
            
            // shift out the current loc.
            this._forwardStack.push(this._cloc) ; 
            
            // shift out other items.
            for(var i=0; i < Math.abs(delta+1);i++) {
              this._forwardStack.push(this._backStack.pop()) ;
            }
            
            // set new cloc.
            this._cloc = this._backStack.pop() ;
            
            
          } else { // forward button has been pushed
            
            // shift out the current loc.
            this._backStack.push(this._cloc) ;
            
            for(i=0; i < (delta-1); i++) {
              this._backStack.push(this._forwardStack.pop()) ;
            }
            
            this._cloc = this._forwardStack.pop() ;
          }
          
        // if the history has changed but the delta hasn't, then that means
        // a new location was set via _setWindowLocation().  Normally we would
        // call gotoRoute in that method, but doing so will crash Safari.
        // Instead, we wait until Safari registers the change and then do the
        // route change.
        } else if (historyDidChange && this._locationDidChange) {
          this.gotoRoute(this._cloc) ;
          this._locationDidChange = false ;
        }
        
        var cloc = this._cloc ;
        var loc = this.get('location') ;
        if (cloc != loc) {
          this.set('location',(cloc) ? cloc : '') ;
          this.gotoRoute(cloc) ;
        }
        
        this.invokeCheckWindowLocation(50) ;
      },
      
      _setWindowLocation: function(loc) {
        var cloc = this._cloc ;
        if (cloc != loc) {
          this._backStack.push(this._cloc) ;
          this._forwardStack.length = 0 ;
          this._cloc = loc ;          
          location.hash = (loc && loc.length > 0) ? loc : '' ;
          this._locationDidChange = true ;
        }
      }
      
    },
    
    // for IE.
    ie: {
      _setupHistory: function() {
        this.invokeCheckWindowLocation(1000) ;
      },
      
      _checkWindowLocation: function() {
        var loc = this.get('location') ;
        var cloc = location.hash ;
        cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
        if (cloc != loc) this.set('location',(cloc) ? cloc : '') ;
        this.invokeCheckWindowLocation(100) ;
      },
      
      _setWindowLocation: function(loc) {
        //console.log('_setWindowLocation('+loc+')') ;
        var cloc = location.hash ;
        cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
        if (cloc != loc) {
          location.hash = (loc && loc.length > 0) ? loc : '#' ;
        }
        this.gotoRoute(loc) ;
      }
    },
    
    // Firefox
    // Because of bugs:
    // https://bugzilla.mozilla.org/show_bug.cgi?id=378962
    // https://bugzilla.mozilla.org/show_bug.cgi?id=483304
    firefox: {
      
      _checkWindowLocation: function() {
        var loc = this.get('location') ;
        var cloc = location.hash ;
        cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
        if (cloc != loc) {
          SC.RunLoop.begin();
          this.set('location',(cloc) ? cloc : '') ;
          SC.RunLoop.end();
        }

        this.invokeCheckWindowLocation(150) ;
      },
      
      _setWindowLocation: function(loc) {
        //console.log('_setWindowLocation('+loc+')') ;
        var cloc = location.hash ;
        cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
        if (cloc != loc) {
          location.hash = (loc && loc.length > 0) ? loc : '#' ;
        }
        this.gotoRoute(loc) ;
      }
      
    }
  },
  
  /** @private */
  _setupHistory: function() {
    var that = this ;
    this.invokeCheckWindowLocation(1000) ;
  },
  
  /** @private */
  _checkWindowLocation: function() {
    var loc = this.get('location') ;
    var cloc = decodeURI(location.hash) ;
    cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
    if (cloc !== loc) {
      SC.RunLoop.begin();
      this.set('location',(cloc) ? cloc : '') ;
      SC.RunLoop.end();
    }
    
    this.invokeCheckWindowLocation(150) ;
  },
  
  /** @private */
  _setWindowLocation: function(loc) {
    //console.log('_setWindowLocation('+loc+')') ;
    var cloc = location.hash ;
    cloc = (cloc && cloc.length > 0) ? cloc.slice(1,cloc.length) : '' ;
    if (cloc != loc) {
      location.hash = (loc && loc.length > 0) ? encodeURI(loc) : '#' ;
    }
    this.gotoRoute(loc) ;
  },
  
  /** @private */
  _routes: null,
  
  /** @private This object handles a single route level. */
  _Route: SC.Object.extend({
    
    // route handler class.
    _target: null,
    
    // route handler
    _method: null,
    
    // staticly named routes.
    _static: null,
    
    // dynamically named routes.
    _dynamic: null,
    
    // set the wildcard route name here.
    _wildcard: null,
    
    addRoute: function(parts, target, method) {
      
      if (!parts || parts.length === 0) {
        this._target = target;
        this._method = method;
        
      // add to route table.
      } else {
        var part = parts.shift() ; // get next route.
        var nextRoute = null ;
        switch(part.slice(0,1)) {
          
          // add a dynamic route
          case ':':
            part = part.slice(1,part.length) ;
            var routes = this._dynamic[part] || [] ;
            nextRoute = SC.routes._Route.create() ;
            routes.push(nextRoute) ;
            this._dynamic[part] = routes ;
            break ;
            
          // setup wildcard route
          case '*':
            part = part.slice(1,part.length) ;
            this._wildcard = part ;
            this._target = target;        
            this._method = method;
            break ;
            
          // setup a normal static route.
          default:
            routes = this._static[part] || [] ;
            nextRoute = SC.routes._Route.create() ;
            routes.push(nextRoute) ;
            this._static[part] = routes ;
        }
        
        // if we need to go another level deeper, call nextRoute
        if (nextRoute) nextRoute.addRoute(parts, target, method) ;
      }
    },
    
    // process the next level of the route and pass on.
    functionForRoute: function(parts, params) {
      
      // if parts it empty, then we are here, so return func
      if (!parts || parts.length === 0) {
        return this ;        
        
      // process the next part
      } else {
        var part = parts.shift(),
            ret  = null,
           routes, nextRoute, loc , routesLen;
        
        // try to match to static
        routes = this._static[part] ;
        if (routes) {
          for(loc=0, routesLen = routes.length;(loc < routesLen) && (ret===null);loc++) {
            var clone = parts.slice() ;
            ret = routes[loc].functionForRoute(clone, params) ;
          }
        }
        
        // try to match dynamic if no static match was found.
        if (ret === null) {
          for(var key in this._dynamic) {
            routes = this._dynamic[key] ;
            if (routes) {
              for(loc=0, routesLen = routes.length; (loc<routesLen) && (ret === null);loc++) {
                clone = parts.slice() ;
                ret = routes[loc].functionForRoute(clone,params) ;
            
                // if a route was found, save the current part in params.
                if (ret && params) params[key] = part ;
              }
            }

            if (ret) break ; 
          }
        }
        
        
        // if nothing still found, and there is a wildcard, match that.
        if ((ret === null) && this._wildcard) {
          parts.unshift(part) ;
          if (params) params[this._wildcard] = parts.join('/') ;
          ret = this;
        }
        
        return ret ;
      }
    },
    
    init: function() {
      arguments.callee.base.call(this) ;
      this._static = {} ; this._dynamic = {} ;
    }
  })
  
});

/* >>>>>>>>>> BEGIN source/system/time.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

/**
  The time library provides a common way for working with time offsets.

  #1 - Fast, not-chained
  
  t = SC.time.month(123) ;
  
  #2 - Chained
  
  t = SC.time(123).month(3).day(12).year(2003).done();

  t = SC.time(123).month(3) ;
*/
SC.time = function(timeoffset) {
  var ret = SC.beget(fn) ;
  ret.value = timeOffset ;
  return ret ;
} ;

(function() {

  var date = new Date();
  
  SC.mixin(SC.time, /** @scope SC.time @static */ { 

    month: function(offset, newMonth) {
      date.setTime(offset) ;
      if (newMonth === undefined) return date.getMonth() ;
      date.setMonth(newMonth) ;
      return date.getTime() ;
    },
    
    /**
      Converts an offset in local time into an offset in UTC time.
      
      @param {Time} offset the local time offset
      @returns {Time} the new offset
    */
    utc: function(offset) {
      date.setTime(offset) ;
      return offset + (date.getTimezoneOffset()*60*1000);  
    },
    
    local: function(offset) {
      date.setTime(offset) ;
      return offset - (date.getTimezoneOffset()*60*1000);  
    },
    
    parse: function(string) {
      
    },
    
    format: function(offset) {
      
    }

  }) ;
  
})() ;

SC.time.fmt = SC.time.format ;

SC.time.fn = {
  
  done: function() { return this.value ; }
  
} ;

"month day year".split(' ').forEach(function(key) {
  SC.time.fn[key] = function(newTime) {
    if (newTime === undefined) {
      return SC.time[key](this.value);
    } else {
      this.value = SC.time[key](this.value, newTime) ;
      return this ;  
    }
  } ;
}) ;

//-----

// Test.context("test basic Date mapping functions", {
//   "month() should return month, month(value) should set month": function() {
//     //...
//   }
// }) ;
// 
// Test.context("test basic Date mapping functions", (function() {
//   var methods = "month day".split(' ') ;
//   var tests = {} ;
//   methods.forEach(function(name) {
//     var testName = "%@() should return %@, %@(value) should set %@".fmt(name,name,name,name) ;
//     
//     tests[testName] = function() {
//       var date = new Date() ;
//       var time = date.getTime() ;
// 
//       var value = date["get%@".fmt(name.capitalize())]() ;
//       equals(value, SC.time[name](), "get");
//       
//       var value = date["set%@".fmt(name.capitalize())](3).getTime() ;
//       equals(value, SC.time[name](3), "set");
//       
//     } ;
//   });
//   
//   return tests ;
// })()) ;


// 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;}

SC.Locale.define('en', {
  longMonthNames: 'January February March April May'.split(' '),
  shortMonthNames: [],
  
  shortDateFormat: 'dd/mm/yy',
  longDateFormat: ''
}) ;

SC.mixin(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="";

    var locale = SC.Locale.currentLocale; 

  	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;
  }

}) ;

SC.mixin(Date.prototype, {
  
  // ------------------------------------------------------------------
  // formatDate (date_object, format)
  // 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'); }

}) ;

/* >>>>>>>>>> BEGIN source/system/timer.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

/**
  @class

  A Timer executes a method after a defined period of time.  Timers are 
  significantly more efficient than using setTimeout() or setInterval() 
  because they are cooperatively scheduled using the run loop.  Timers are
  also gauranteed to fire at the same time, making it far easier to keep 
  multiple timers in sync.
  
  h2. Overview
  
  Timers were created for SproutCore as a way to efficiently defer execution
  of code fragments for use in Animations, event handling, and other tasks.
  
  Browsers are typically fairly inconsistant about when they will fire a 
  timeout or interval based on what the browser is currently doing.  Timeouts 
  and intervals are also fairly expensive for a browser to execute, which 
  means if you schedule a large number of them it can quickly slow down the 
  browser considerably.
  
  Timers, on the other handle, are scheduled cooperatively using the 
  SC.RunLoop, which uses exactly one timeout to fire itself when needed and 
  then executes by timers that need to fire on its own.  This approach can
  be many timers faster than using timers and gaurantees that timers scheduled
  to execute at the same time generally will do so, keeping animations and
  other operations in sync.
  
  h2. Scheduling a Timer

  To schedule a basic timer, you can simply call SC.Timer.schedule() with 
  a target and action you wish to have invoked:
  
  {{{
    var timer = SC.Timer.schedule({ 
      target: myObject, action: 'timerFired', interval: 100 
    });
  }}}

  When this timer fires, it will call the timerFired() method on myObject.
  
  In addition to calling a method on a particular object, you can also use
  a timer to execute a variety of other types of code:
  
  - If you include an action name, but not a target object, then the action will be passed down the responder chain.
  - If you include a property path for the action property (e.g. 'MyApp.someController.someMethod'), then the method you name will be executed.
  - If you include a function in the action property, then the function will be executed.  If you also include a target object, the function will be called with this set to the target object.

  In general these properties are read-only.  Changing an interval, target,
  or action after creating a timer will have an unknown effect.

  h2. Scheduling Repeating Timers
  
  In addition to scheduling one time timers, you can also schedule timers to
  execute periodically until some termination date.  You make a timer
  repeating by adding the repeats: YES property:
  
  {{{
    var timer = SC.Timer.schedule({
      target: myObject, 
      action: 'updateAnimation', 
      interval: 100,
      repeats: YES, 
      until: Time.now() + 1000
    }) ;
  }}}
  
  The above example will execute the myObject.updateAnimation() every 100msec
  for 1 second from the current time.  
  
  If you want a timer to repeat without expiration, you can simply omit the
  until: property.  The timer will then repeat until you invalidate it.
  
  h2. Pausing and Invalidating Timers
  
  If you have created a timer but you no longer want it to execute, you can
  call the invalidate() method on it.  This will remove the timer from the 
  run loop and clear certain properties so that it will not run again.
  
  You can use the invalidate() method on both repeating and one-time timers.
  
  If you do not want to invalidate a timer completely but you just want to
  stop the timer from execution temporarily, you can alternatively set the
  isPaused property to YES:
  
  {{{
    timer.set('isPaused', YES) ;
    // Perform some critical function; timer will not execute
    timer.set('isPaused', NO) ;
  }}}
  
  When a timer is paused, it will be scheduled and will fire like normal, 
  but it will not actually execute the action method when it fires.  For a 
  one time timer, this means that if you have the timer paused when it fires,
  it may never actually execute the action method.  For repeating timers, 
  this means the timer will remain scheduled but simply will not execute its
  action while the timer is paused.
  
  h2. Firing Timers
  
  If you need a timer to execute immediately, you can always call the fire()
  method yourself.  This will execute the timer action, if the timer is not
  paused.  For a one time timer, it will also invalidate the timer and remove
  it from the run loop.  Repeating timers can be fired anytime and it will
  not interrupt their regular scheduled times.

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

  /**
    The target object whose method will be invoked when the time fires.
    
    You can set either a target/action property or you can pass a specific
    method.
    
    @type {Object}
    @field
  */
  target: null,
  
  /**
    The action to execute.
    
    The action can be a method name, a property path, or a function.  If you
    pass a method name, it will be invoked on the target object or it will 
    be called up the responder chain if target is null.  If you pass a 
    property path and it resolves to a function then the function will be 
    called.  If you pass a function instead, then the function will be 
    called in the context of the target object.
    
    @type {String, Function}
  */
  action: null,
  
  /**
    Set if the timer should be created from a memory pool.  Normally you will
    want to leave this set, but if you plan to use bindings or observers with
    this timer, then you must set isPooled to NO to avoid reusing your timer.
    
    @property {Boolean}
  */
  isPooled: NO,
  
  /**
    The time interval in milliseconds.
    
    You generally set this when you create the timer.  If you do not set it
    then the timer will fire as soon as possible in the next run loop.
    
    @type {Number}
  */
  interval: 0,
  
  /**
    Timer start date offset.
    
    The start date determines when the timer will be scheduled.  The first
    time the timer fires will be interval milliseconds after the start 
    date. 
    
    Generally you will not set this property yourself.  Instead it will be 
    set automatically to the current run loop start date when you schedule 
    the timer.  This ensures that all timers scheduled in the same run loop
    cycle will execute in the sync with one another.
    
    The value of this property is an offset like waht you get if you call
    Date.now().
    
    @type {Number}
  */
  startTime: null,
  
  /**
    YES if you want the timer to execute repeatedly.
    
    @type {Boolean}
  */
  repeats: NO,
  
  /**
    Last date when the timer will execute.
    
    If you have set repeats to YES, then you can also set this property to
    have the timer automatically stop executing past a certain date.
    
    This property should contain an offset value like startOffset.  However if
    you set it to a Date object on create, it will be converted to an offset
    for you.
    
    If this property is null, then the timer will continue to repeat until you
    call invalidate().
    
    @type {Date, Number}
  */
  until: null,
  
  /**
    Set to YES to pause the timer.
    
    Pausing a timer does not remove it from the run loop, but it will 
    temporarily suspend it from firing.  You should use this property if
    you will want the timer to fire again the future, but you want to prevent
    it from firing temporarily.
    
    If you are done with a timer, you should call invalidate() instead of 
    setting this property.
    
    @type {Boolean}
  */
  isPaused: NO,

  /**
    YES onces the timer has been scheduled for the first time.
  */
  isScheduled: NO,
  
  /**
    YES if the timer can still execute.
    
    This read only property will return YES as long as the timer may possibly
    fire again in the future.  Once a timer has become invalid, it cannot 
    become valid again. 
    
    @field
    @type {Boolean}
  */
  isValid: YES,
  
  /**
    Set to the current time when the timer last fired.  Used to find the 
    next 'frame' to execute.
  */
  lastFireTime: 0,
  
  /**
    Computed property returns the next time the timer should fire.  This 
    property resets each time the timer fires.  Returns -1 if the timer 
    cannot fire again.
    
    @property {Time}
  */
  fireTime: function() {
    if (!this.get('isValid')) return -1 ;  // not valid - can't fire
    
    // can't fire w/o startTime (set when schedule() is called).
    var start = this.get('startTime');
    if (!start || start === 0) return -1; 

    // fire interval after start.
    var interval = this.get('interval'), last = this.get('lastFireTime');
    if (last < start) last = start; // first time to fire
    
    // find the next time to fire
    var next ;
    if (this.get('repeats')) {
      if (interval === 0) { // 0 means fire as fast as possible.
        next = last ; // time to fire immediately!
        
      // find the next full interval after start from last fire time.
      } else {
        next = start + (Math.floor((last - start) / interval)+1)*interval;
      }
      
    // otherwise, fire only once interval after start
    } else next = start + interval ;
    
    // can never have a fireTime after until
    var until = this.get('until');
    if (until && until>0 && next>until) next = until;
    
    return next ;
  }.property('interval', 'startTime', 'repeats', 'until', 'isValid', 'lastFireTime').cacheable(),
  
  /**
    Schedules the timer to execute in the runloop. 
    
    This method is called automatically if you create the timer using the
    schedule() class method.  If you create the timer manually, you will
    need to call this method yourself for the timer to execute.
    
    @returns {SC.Timer} The receiver
  */
  schedule: function() {
    if (!this.get('isValid')) return this; // nothing to do
    
    this.beginPropertyChanges();
    
    // if start time was not set explicitly when the timer was created, 
    // get it from the run loop.  This way timer scheduling will always
    // occur in sync.
    if (!this.startTime) this.set('startTime', SC.RunLoop.currentRunLoop.get('startTime')) ;

    // now schedule the timer if the last fire time was < the next valid 
    // fire time.  The first time lastFireTime is 0, so this will always go.
    var next = this.get('fireTime'), last = this.get('lastFireTime');
    if (next >= last) {
      this.set('isScheduled', YES);
      SC.RunLoop.currentRunLoop.scheduleTimer(this, next);
    }
    
    this.endPropertyChanges() ;
    
    return this ;
  },
  /**
    Invalidates the timer so that it will not execute again.  If a timer has
    been scheduled, it will be removed from the run loop immediately.
    
    @returns {SC.Timer} The receiver
  */
  invalidate: function() {
    this.beginPropertyChanges();
    this.set('isValid', NO);
    SC.RunLoop.currentRunLoop.cancelTimer(this);
    this.action = this.target = null ; // avoid memory leaks
    this.endPropertyChanges();
    
    // return to pool...
    if (this.get('isPooled')) SC.Timer.returnTimerToPool(this);
    return this ;
  },
  
  /**
    Immediately fires the timer.
    
    If the timer is not-repeating, it will be invalidated.  If it is repeating
    you can call this method without interrupting its normal schedule.
    
    @returns {void}
  */
  fire: function() {

    // this will cause the fireTime to recompute
    var last = Date.now();
    this.set('lastFireTime', last);

    var next = this.get('fireTime');

    // now perform the fire action unless paused.
    if (!this.get('isPaused')) this.performAction() ;
    
     // reschedule the timer if needed...
     if (next > last) {
       this.schedule();
     } else {
       this.invalidate();
     }
  },

  /**
    Actually fires the action. You can override this method if you need
    to change how the timer fires its action.
  */
  performAction: function() {
    var typeOfAction = SC.typeOf(this.action);
    
    // if the action is a function, just try to call it.
    if (typeOfAction == SC.T_FUNCTION) {
      this.action.call((this.target || this), this) ;

    // otherwise, action should be a string.  If it has a period, treat it
    // like a property path.
    } else if (typeOfAction === SC.T_STRING) {
      if (this.action.indexOf('.') >= 0) {
        var path = this.action.split('.') ;
        var property = path.pop() ;

        var target = SC.objectForPropertyPath(path, window) ;
        var action = target.get ? target.get(property) : target[property];
        if (action && SC.typeOf(action) == SC.T_FUNCTION) {
          action.call(target, this) ;
        } else {
          throw '%@: Timer could not find a function at %@'.fmt(this, this.action) ;
        }

      // otherwise, try to execute action direction on target or send down
      // responder chain.
      } else {
        SC.RootResponder.responder.sendAction(this.action, this.target, this);
      }
    }
  },
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    
    // convert startTime and until to times if they are dates.
    if (this.startTime instanceof Date) {
      this.startTime = this.startTime.getTime() ;
    }
    
    if (this.until instanceof Date) {
      this.until = this.until.getTime() ;
    }
  },
  
  /** @private - Default values to reset reused timers to. */
  RESET_DEFAULTS: {
    target: null, action: null, 
    isPooled: NO, isPaused: NO, isScheduled: NO, isValid: YES,
    interval: 0, repeats: NO, until: null,
    startTime: null, lastFireTime: 0
  },
  
  /** 
    Resets the timer settings with the new settings.  This is the method 
    called by the Timer pool when a timer is reused.  You will not normally
    call this method yourself, though you could override it if you need to 
    reset additonal properties when a timer is reused.
    
    @params {Hash} props properties to copy over
    @returns {SC.Timer} receiver
  */
  reset: function(props) {
    if (!props) props = SC.EMPTY_HASH;
    
    // note: we copy these properties manually just to make them fast.  we 
    // don't expect you to use observers on a timer object if you are using 
    // pooling anyway so this won't matter.  Still notify of property change
    // on fireTime to clear its cache.
    this.propertyWillChange('fireTime');
    var defaults = this.RESET_DEFAULTS ;
    for(var key in defaults) {
      if (!defaults.hasOwnProperty(key)) continue ; 
      this[key] = SC.none(props[key]) ? defaults[key] : props[key];
    }
    this.propertyDidChange('fireTime');
    return this ;
  },
    
  // ..........................................................
  // TIMER QUEUE SUPPORT
  // 

  /** @private - removes the timer from its current timerQueue if needed. 
    return value is the new "root" timer.
  */
  removeFromTimerQueue: function(timerQueueRoot) {
    var prev = this._timerQueuePrevious, next = this._timerQueueNext ;

    if (!prev && !next && timerQueueRoot !== this) return timerQueueRoot ; // not in a queue...

    // else, patch up to remove...
    if (prev) prev._timerQueueNext = next ;
    if (next) next._timerQueuePrevious = prev ;
    this._timerQueuePrevious = this._timerQueueNext = null ;
    return (timerQueueRoot === this) ? next : timerQueueRoot ;
  },
  
  /** @private - schedules the timer in the queue based on the runtime. */
  scheduleInTimerQueue: function(timerQueueRoot, runTime) {
    this._timerQueueRunTime = runTime ;
    
    // find the place to begin
    var beforeNode = timerQueueRoot;
    var afterNode = null ;
    while(beforeNode && beforeNode._timerQueueRunTime < runTime) {
      afterNode = beforeNode ;
      beforeNode = beforeNode._timerQueueNext;
    }

    if (afterNode) {
      afterNode._timerQueueNext = this ;
      this._timerQueuePrevious = afterNode ;
    }
    
    if (beforeNode) {
      beforeNode._timerQueuePrevious = this ;
      this._timerQueueNext = beforeNode ;
    }
    
    // I am the new root if beforeNode === root
    return (beforeNode === timerQueueRoot) ? this : timerQueueRoot ;
  },
  
  /** @private 
    adds the receiver to the passed array of expired timers based on the 
    current time and then recursively calls the next timer.  Returns the
    first timer that is not expired.  This is faster than iterating through
    the timers because it does some faster cleanup of the nodes.
  */
  collectExpiredTimers: function(timers, now) {
    if (this._timerQueueRunTime > now) return this ; // not expired!
    timers.push(this);  // add to queue.. fixup next. assume we are root.
    var next = this._timerQueueNext ;
    this._timerQueueNext = null;
    if (next) next._timerQueuePrevious = null;
    return next ? next.collectExpiredTimers(timers, now) : null; 
  }
  
}) ;

/** @scope SC.Timer */

/*  
  Created a new timer with the passed properties and schedules it to 
  execute.  This is the same as calling SC.Time.create({ props }).schedule().
  
  Note that unless you explicitly set isPooled to NO, this timer will be 
  pulled from a shared memory pool of timers.  You cannot using bindings or
  observers on these timers as they may be reused for future timers at any
  time.
  
  @params {Hash} props Any properties you want to set on the timer.
  @returns {SC.Timer} new timer instance.
*/
SC.Timer.schedule = function(props) {
  // get the timer.
  var timer ;
  if (!props || SC.none(props.isPooled) || props.isPooled) {
    timer = this.timerFromPool(props);
  } else timer = this.create(props);
  return timer.schedule();
} ;

/**
  Returns a new timer from the timer pool, copying the passed properties onto
  the timer instance.  If the timer pool is currently empty, this will return
  a new instance.
*/
SC.Timer.timerFromPool = function(props) {
  var timers = this._timerPool;
  if (!timers) timers = this._timerPool = [] ;
  var timer = timers.pop();
  if (!timer) timer = this.create();
  return timer.reset(props) ;
};

/** 
  Returns a timer instance to the timer pool for later use.  This is done
  automatically when a timer is invalidated if isPooled is YES.
*/
SC.Timer.returnTimerToPool = function(timer) {
  if (!this._timerPool) this._timerPool = [];

  this._timerPool.push(timer);
  return this ;
};



/* >>>>>>>>>> BEGIN source/system/user_defaults.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class
  
  The UserDefaults object provides an easy way to store user preferences in
  your application on the local machine.  You use this by providing built-in
  defaults using the SC.userDefaults.defaults() method.  You can also
  implement the UserDefaultsDelegate interface to be notified whenever a
  default is required.  
  
  You should also set the userDomain property on the defaults on page load.
  This will allow the UserDefaults application to store/fetch keys from 
  localStorage for the correct user.
  
  You can also set an appDomain property if you want.  This will be 
  automatically prepended to key names with no slashes in them.
  
  SC.userDefaults.getPath("global:contactInfo.userName");
  
  @extends SC.Object
  @since SproutCore 1.0
*/
SC.UserDefaults = SC.Object.extend(/** @scope SC.UserDefaults.prototype */ {
  
  /** 
    the default domain for the user.  This will be used to store keys in
    local storage.  If you do not set this property, the wrong values may be
    returned.
  */
  userDomain: null,
  
  /**
    The default app domain for the user.  Any keys that do not include a 
    slash will be prefixed with this app domain key when getting/setting.
  */
  appDomain: null,
  
  /** @private
    Defaults.  These will be used if not defined on localStorage.
  */
  _defaults: null,
  
  /**
    Invoke this method to set the builtin defaults.  This will cause all
    properties to change.
  */
  defaults: function(newDefaults) {
    this._defaults = newDefaults ;
    this.allPropertiesDidChange();
  },
  
  /**
    Attempts to read a user default from local storage.  If not found on 
    localStorage, use the the local defaults, if defined.  If the key passed
    does not include a slash, then add the appDomain or use "app/".
    
    @param {String} keyName
    @returns {Object} read value
  */
  readDefault: function(keyName) {
    var ret= undefined ;
    
    // namespace keyname
    keyName = this._normalizeKeyName(keyName);
    var userKeyName = this._userKeyName(keyName);

    // look into recently written values
    if (this._written) ret = this._written[userKeyName];
    
    // attempt to read from localStorage
    var localStorage = window.localStorage ;
    if (!localStorage && window.globalStorage) {
      localStorage = window.globalStorage[window.location.hostname];
    }
    if (localStorage) {
      ret = localStorage[["SC.UserDefaults",userKeyName].join('@')];
      if (!SC.none(ret)) {
        try {
          ret = SC.json.decode(ret);
        } 
        catch(e) {
          ret = undefined;
        }
      } else ret = undefined;
    }
    
    // if not found in localStorage, try to notify delegate
    var del =this.delegate ;
    if (del && del.userDefaultsNeedsDefault) {
      ret = del.userDefaultsNeedsDefault(this, keyName, userKeyName);
    }
    
    // if not found in localStorage or delegate, try to find in defaults
    if ((ret===undefined) && this._defaults) {
      ret = this._defaults[userKeyName] || this._defaults[keyName];
    }
    
    return ret ;
  },
  
  /**
    Attempts to write the user default to local storage or at least saves them
    for now.  Also notifies that the value has changed.
    
    @param {String} keyName
    @param {Object} value
    @returns {SC.UserDefault} receiver
  */
  writeDefault: function(keyName, value) {
    
    keyName = this._normalizeKeyName(keyName);
    var userKeyName = this._userKeyName(keyName);
    
    // save to local hash
    var written = this._written ;
    if (!written) written = this._written = {};
    written[userKeyName] = value ;
    
    // save to local storage
    var localStorage = window.localStorage ;
    if (!localStorage && window.globalStorage) {
      localStorage = window.globalStorage[window.location.hostname];
    }
    if (localStorage) {
      localStorage[["SC.UserDefaults",userKeyName].join('@')] = SC.json.encode(value);
    }
    
    // also notify delegate
    var del = this.delegate;
    if (del && del.userDefaultsDidChange) {
      del.userDefaultsDidChange(this, keyName, value, userKeyName);
    }
    
    return this ;
  },
  
  /**
    Removed the passed keyName from the written hash and local storage.
    
    @param {String} keyName
    @returns {SC.UserDefaults} receiver
  */
  resetDefault: function(keyName) {  
    var fullKeyName = this._normalizeKeyName(keyName);
    var userKeyName = this._userKeyName(fullKeyName);
    
    this.propertyWillChange(keyName);
    this.propertyWillChange(fullKeyName);
    
    var written = this._written;
    if (written) delete written[userKeyName];
    
    var localStorage = window.localStorage ;
    if (!localStorage && window.globalStorage) {
      localStorage = window.globalStorage[window.location.hostname];
    }

    if (localStorage) {
      delete localStorage[["SC.UserDefaults",userKeyName].join('@')];
    }

    this.propertyDidChange(keyName);
    this.propertyDidChange(fullKeyName);
    return this ;
  },
  
  /**
    Is called whenever you .get() or .set() values on this object
    
    @param {Object} key
    @param {Object} value
    @returns {Object}
  */
  unknownProperty: function(key, value) {
    if (value === undefined) {
      return this.readDefault(key) ;
    } else {
      this.writeDefault(key, value);
      return value ;
    }
  },
  
  /**
    Normalize the passed key name.  Used by all accessors to automatically 
    insert an appName if needed.
  */
  _normalizeKeyName: function(keyName) {
    if (keyName.indexOf(':')<0) {
      var domain = this.get('appDomain') || 'app';
      keyName = [domain, keyName].join(':');
    } 
    return keyName;
  },
  
  /** 
    Builds a user key name from the passed key name
  */
  _userKeyName: function(keyName) {
    var user = this.get('userDomain') || '(anonymous)' ;
    return [user,keyName].join('@');
  },
  
  _domainDidChange: function() {
    var didChange = NO;
    if (this.get("userDomain") !== this._scud_userDomain) {
      this._scud_userDomain = this.get('userDomain');
      didChange = YES;
    }
    
    if (this.get('appDomain') !== this._scud_appDomain) {
      this._scud_appDomain = this.get('appDomain');
      didChange = YES;
    }
    
    if (didChange) this.allPropertiesDidChange();
  }.observes('userDomain', 'appDomain'),
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    this._scud_userDomain = this.get('userDomain');
    this._scud_appDomain  = this.get('appDomain');
  }
  
});

/** global user defaults. */
SC.userDefaults = SC.UserDefaults.create();
/* >>>>>>>>>> BEGIN source/system/utils.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

// These are helpful utility functions for calculating range and rect values
sc_require('system/browser');

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

  _downloadFrames: 0, // count of download frames inserted into document
  
  _copy_computed_props: [
    "maxWidth", "maxHeight", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom",
    "fontFamily", "fontSize", "fontStyle", "fontWeight", "fontVariant", "lineHeight",
    "whiteSpace"
  ],
  
  /**
    Starts a download of the file at the named path.
    
    Use this method when you want to cause a file to be downloaded to a users
    desktop instead of having it display in the web browser.  Note that your
    server must return a header indicating that the file is intended for 
    download also.
  */
  download: function(path) {
    var tempDLIFrame=document.createElement('iframe');
    var frameId = 'DownloadFrame_' + this._downloadFrames;
    SC.$(tempDLIFrame).attr('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.browser.isSafari) {
      SC.$(tempDLIFrame).attr('src',path);
    }
    document.getElementsByTagName('body')[0].appendChild(tempDLIFrame);
    if (SC.browser.isSafari) {
      SC.$(tempDLIFrame).attr('src',path);    
    }
    this._downloadFrames = this._downloadFrames + 1;
    if (!SC.browser.isSafari) {
      var r = function() { 
        document.body.removeChild(document.getElementById(frameId)); 
        frameId = null;
      } ;
      var t = r.invokeLater(null, 2000);
    }
    //remove possible IE7 leak
    tempDLIFrame = null;
  },

  /**
    Takes a URL of any type and normalizes it into a fully qualified URL with
    hostname.  For example:
    
    {{{
      "some/path" => "http://localhost:4020/some/path" 
      "/some/path" => "http://localhost:4020/some/path"
      "http://localhost:4020/some/path" => "http://localhost:4020/some/path"
    }}}
    
    @param url {String} the URL
    @returns {String} the normalized 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 ;
  },
  
  
  /** Return the left edge of the frame */
  minX: function(frame) { 
    return frame.x || 0; 
  },
  
  /** Return the right edge of the frame. */
  maxX: function(frame) { 
    return (frame.x || 0) + (frame.width || 0); 
  },
  
  /** Return the midpoint of the frame. */
  midX: function(frame) {
    return (frame.x || 0) + ((frame.width || 0) / 2) ;
  },
  
  /** Return the top edge of the frame */
  minY: function(frame) {
    return frame.y || 0 ;
  },
  
  /** Return the bottom edge of the frame */
  maxY: function(frame) {
    return (frame.y || 0) + (frame.height || 0) ;
  },
  
  /** Return the midpoint of the frame */
  midY: function(frame) {
    return (frame.y || 0) + ((frame.height || 0) / 2) ;
  },
  
  /** Returns the point that will center the frame X within the passed frame. */
  centerX: function(innerFrame, outerFrame) {
    return (outerFrame.width - innerFrame.width) / 2 ;
  },
  
  /** Return the point that will center the frame Y within the passed frame. */
  centerY: function(innerFrame, outerFrame) {
    return (outerFrame.height - innerFrame.height) /2  ;
  },
  
  /** Check if the given point is inside the rect. */
  pointInRect: function(point, f) {
    return  (point.x >= SC.minX(f)) &&
            (point.y >= SC.minY(f)) &&
            (point.x <= SC.maxX(f)) && 
            (point.y <= SC.maxY(f)) ;
  },
  
  /** Return true if the two frames match.  You can also pass only points or sizes.
  
    @param r1 {Rect} the first rect
    @param r2 {Rect} the second rect
    @param delta {Float} an optional delta that allows for rects that do not match exactly. Defaults to 0.1
    @returns {Boolean} true if rects match
   */
  rectsEqual: function(r1, r2, delta) {
    if (!r1 || !r2) return (r1 == r2) ;
  
    if (!delta && delta !== 0) delta = 0.1;
    if ((r1.y != r2.y) && (Math.abs(r1.y - r2.y) > delta)) return NO ; 
    if ((r1.x != r2.x) && (Math.abs(r1.x - r2.x) > delta)) return NO ; 
    if ((r1.width != r2.width) && (Math.abs(r1.width - r2.width) > delta)) return NO ; 
    if ((r1.height != r2.height) && (Math.abs(r1.height - r2.height) > delta)) return NO ; 
    return YES ;
  },
  
  /** Returns the insersection between two rectangles. 
  
    @param r1 {Rect} The first rect
    @param r2 {Rect} the second rect
    @returns {Rect} the intersection rect.  width || height will be 0 if they do not interset.
  */
  intersectRects: function(r1, r2) {
    // find all four edges
    var ret = {
      x: Math.max(SC.minX(r1), SC.minX(r2)),
      y: Math.max(SC.minY(r1), SC.minY(r2)),
      width: Math.min(SC.maxX(r1), SC.maxX(r2)),
      height: Math.min(SC.maxY(r1), SC.maxY(r2))
    } ;
    
    // convert edges to w/h
    ret.width = Math.max(0, ret.width - ret.x) ;
    ret.height = Math.max(0, ret.height - ret.y) ;
    return ret ;
  },
  
  /** Returns the union between two rectangles
  
    @param r1 {Rect} The first rect
    @param r2 {Rect} The second rect
    @returns {Rect} The union rect.
  */
  unionRects: function(r1, r2) {
    // find all four edges
    var ret = {
      x: Math.min(SC.minX(r1), SC.minX(r2)),
      y: Math.min(SC.minY(r1), SC.minY(r2)),
      width: Math.max(SC.maxX(r1), SC.maxX(r2)),
      height: Math.max(SC.maxY(r1), SC.maxX(r2))
    } ;
    
    // convert edges to w/h
    ret.width = Math.max(0, ret.width - ret.x) ;
    ret.height = Math.max(0, ret.height - ret.y) ;
    return ret ;
  },
  
  /** Duplicates the passed rect.  
  
    This is faster than Object.clone(). 
    
    @param r {Rect} The rect to clone.
    @returns {Rect} The cloned rect
  */
  cloneRect: function(r) {
    return { x: r.x, y: r.y, width: r.width, height: r.height } ;
  },
  
  /** Returns a string representation of the rect as {x, y, width, height}.  
    
    @param r {Rect} The rect to stringify.
    @returns {String} A string representation of the rect.
  */
  stringFromRect: function(r) {
    return '{%@, %@, %@, %@}'.fmt(r.x, r.y, r.width, r.height);
  },
  
  /**
    Returns a string representation of the layout hash.

    Layouts can contain the following keys:
      - left: the left edge
      - top: the top edge
      - right: the right edge
      - bottom: the bottom edge
      - height: the height
      - width: the width
      - centerX: an offset from center X 
      - centerY: an offset from center Y
      - minWidth: a minimum width
      - minHeight: a minimum height
      - maxWidth: a maximum width
      - maxHeight: a maximum height
    
    @param layout {Hash} The layout hash to stringify.
    @returns {String} A string representation of the layout hash.
  */
  stringFromLayout: function(layout) {
    // Put them in the reverse order that we want to display them, because
    // iterating in reverse is faster for CPUs that can compare against zero
    // quickly.
    var keys = ['maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'centerY',
                'centerX', 'width', 'height', 'bottom', 'right', 'top',
                'left'];

    var keyValues = [];
    var i = keys.length;
    while (--i >= 0) {
      var key = keys[i];
      if (layout.hasOwnProperty(key)) {
        keyValues.push(key + ':' + layout[key]);
      }
    }
    
    return '{' + keyValues.join(', ') + '}';
  },
  
  /**
    Given a string and a fixed width, calculates the height of that
    block of text using a style string, a set of class names,
    or both.

    @param str {String} The text to calculate
    @param width {Number} The fixed width to assume the text will fill
    @param style {String} A CSS style declaration.  E.g., 'font-weight: bold'
    @param classNames {Array} An array of class names that may affect the style
    @returns {Number} The height of the text given the passed parameters
  */
  heightForString: function(str, width, style, classNames) {
    var elem = this._heightCalcElement, classes, height;

    // Coalesce the array of class names to one string, if the array exists
    classes = (classNames && SC.typeOf(classNames) === SC.T_ARRAY) ? classNames.join(' ') : '';

    if (!width) width = 100; // default to 100 pixels

    // Only create the offscreen element once, then cache it
    if (!elem) {
      elem = this._heightCalcElement = document.createElement('div');
      document.body.insertBefore(elem, null);
    }

    style = '%@; width: %@px; left: %@px; position: absolute'.fmt(style, width, (-1*width));
    SC.$(elem).attr('style', style);

    if (classes !== '') {
      SC.$(elem).attr('class', classes);
    }

    elem.innerHTML = str;
    height = elem.clientHeight;

    elem = null; // don't leak memory
    return height;
  },
  
  /**
  Given a string and an example element or style string, and an optional
  set of class names, calculates the width and height of that block of text.
  
  To constrain the width, set max-width on the exampleElement or in the style string.
  
  @param string {String} The string to measure.
  @param exampleElement The example element to grab styles from, or the style string to use.
  @param classNames {String} (Optional) Class names to add to the test element.
  */
  metricsForString: function(string, exampleElement, classNames)
  {
    var element = this._metricsCalculationElement, width, height, classes, styles, style;
    
    // collect the class names
    classes = SC.A(classNames).join(' ');
    
    // get the calculation element
    if (!element) {
      element = this._metricsCalculationElement = document.createElement("div");
      document.body.insertBefore(element, null);
    }
    
    // two possibilities: example element or type string
    if (SC.typeOf(exampleElement) != SC.T_STRING) {
      var computed = null;
      if (document.defaultView && document.defaultView.getComputedStyle) {
        computed = document.defaultView.getComputedStyle(exampleElement, null);
      } else {
        computed = exampleElement.currentStyle;
      }
      
      // set (lovely cssText property here helps a lot—if it works. Unfortunately, only Safari supplies it.)
      style = computed.cssText;
      
      // if that didn't work (Safari-only?) go alternate route. This is SLOW code...
      if (!style || style.trim() === "") {
        // there is only one way to do it...
        var props = this._copy_computed_props;
        
        // firefox ONLY allows this method
        for (var i = 0; i < props.length; i++) {
          var prop = props[i], val = computed[prop];
          element.style[prop] = val;
        }
        
        // and why does firefox specifically need "font" set?
        var cs = element.style; // cached style
        if (cs.font === "") {
          var font = "";
          if (cs.fontStyle) font += cs.fontStyle + " ";
          if (cs.fontVariant) font += cs.fontVariant + " ";
          if (cs.fontWeight) font += cs.fontWeight + " ";
          if (cs.fontSize) font += cs.fontSize; else font += "10px"; //force a default
          if (cs.lineHeight) font += "/" + cs.lineHeight;
          font += " ";
          if (cs.fontFamily) font += cs.fontFamily; else cs += "sans-serif";
          
          element.style.font = font;
        }
        
        SC.mixin(element.style, {
          left: "0px", top: "0px", position: "absolute", bottom: "auto", right: "auto", width: "auto", height: "auto"
        });
      }
      else
      {
        // set style
        element.setAttribute("style", style + "; position:absolute; left: 0px; top: 0px; bottom: auto; right: auto; width: auto; height: auto;");
      }
      
      // clean up
      computed = null;
    } else {
      // it is a style string already
      style = exampleElement;
      
      // set style
      element.setAttribute("style", style + "; position:absolute; left: 0px; top: 0px; bottom: auto; right: auto; width: auto; height: auto;");
    }
    
    // the conclusion of which to use (innerText or textContent) should be cached
    if (typeof element.innerText != "undefined") element.innerText = string;
    else element.textContent = string;
    
    element.className = classes;
    
    // measure
    var result = {
      width: element.clientWidth,
      height: element.clientHeight
    };
    
    // clear element
    element.innerHTML = "";
    element.className = "";
    element.setAttribute("style", ""); // get rid of any junk from computed style.
    
    // clean up
    element = null;
    return result;
  },

  /** Finds the absolute viewportOffset for a given element.
    This method is more accurate than the version provided by prototype.
    
    If you pass NULL to this method, it will return a { x:0, y:0 }
    @param el The DOM element
    @returns {Point} A hash with x,y offsets.
  */
  viewportOffset: function(el) {
    // Some browsers natively implement getBoundingClientRect, so if it's
    // available we'll use it for speed.
    if (el.getBoundingClientRect) {
      var boundingRect = el.getBoundingClientRect();
      return { x:boundingRect.left, y:boundingRect.top };
    }
    
    var valueL = 0 ; var valueT = 0;

    // add up all the offsets for the element.
    var element = el ;
    var isFirefox3 = SC.browser.mozilla >= 3 ;
    while (element) {
      valueT += (element.offsetTop  || 0);
      if (!isFirefox3 || (element !== el)) {
        valueT += (element.clientTop  || 0);
      }

      valueL += (element.offsetLeft || 0);
      if (!isFirefox3 || (element !== el)) {
        valueL += (element.clientLeft || 0);
      }

      // bizarely for FireFox if your offsetParent has a border, then it can 
      // impact the offset. 
      if (SC.browser.mozilla) {
        var overflow = SC.$(element).attr('overflow') ;
        if (overflow !== 'visible') {
          var left = parseInt(SC.$(element).attr('borderLeftWidth'),0) || 0 ;
          var top = parseInt(SC.$(element).attr('borderTopWidth'),0) || 0 ;
          if (el !== element) {
            left *= 2; top *= 2 ;
          }
          valueL += left; valueT += top ;
        }
        
        // In FireFox 3 -- the offsetTop/offsetLeft subtracts the clientTop/
        // clientLeft of the offset parent.
        var offsetParent = element.offsetParent ;
        if ((SC.browser.mozilla >= 3) && offsetParent) {
          valueT -= offsetParent.clientTop ;
          valueL -= offsetParent.clientLeft;
        }
      }

      // Safari fix
      if (element.offsetParent == document.body &&
        SC.$(element).attr('position') == 'absolute') break;

      element = element.offsetParent ;

    }

    element = el;
    while (element) {
      if (!SC.browser.isOpera || element.tagName == 'BODY') {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
      
      element = element.parentNode ;
    }

    return { x: valueL, y: valueT } ;
  },
  
  /** A Point at {0,0} */
  ZERO_POINT: { x: 0, y: 0 },
  
  /** A zero length range at zero. */
  ZERO_RANGE: { start: 0, length: 0 },

  RANGE_NOT_FOUND: { start: 0, length: -1 },
  
  /** Returns true if the passed index is in the specified range */
  valueInRange: function(value, range) {
    return (value >= 0) && (value >= range.start) && (value < (range.start + range.length));  
  },
  
  /** Returns first value of the range. */
  minRange: function(range) { return range.start; },
  
  /** Returns the first value outside of the range. */
  maxRange: function(range) { return (range.length < 0) ? -1 : (range.start + range.length); },
  
  /** Returns the union of two ranges.  If one range is null, the other
   range will be returned.  */
  unionRanges: function(r1, r2) { 
    if ((r1 == null) || (r1.length < 0)) return r2 ;
    if ((r2 == null) || (r2.length < 0)) return r1 ;
    
    var min = Math.min(r1.start, r2.start) ;
    var max = Math.max(SC.maxRange(r1), SC.maxRange(r2)) ;
    return { start: min, length: max - min } ;
  },
  
  /** Returns the intersection of the two ranges or SC.RANGE_NOT_FOUND */
  intersectRanges: function(r1, r2) {
    if ((r1 == null) || (r2 == null)) return SC.RANGE_NOT_FOUND ;
    if ((r1.length < 0) || (r2.length < 0)) return SC.RANGE_NOT_FOUND;
    var min = Math.max(SC.minRange(r1), SC.minRange(r2)) ;
    var max = Math.min(SC.maxRange(r1), SC.maxRange(r2)) ;
    if (max < min) return SC.RANGE_NOT_FOUND ;
    return { start: min, length: max-min };
  },
  
  /** Returns the difference of the two ranges or SC.RANGE_NOT_FOUND */
  subtractRanges: function(r1, r2) {
    if ((r1 == null) || (r2 == null)) return SC.RANGE_NOT_FOUND ;
    if ((r1.length < 0) || (r2.length < 0)) return SC.RANGE_NOT_FOUND;
    var max = Math.max(SC.minRange(r1), SC.minRange(r2)) ;
    var min = Math.min(SC.maxRange(r1), SC.maxRange(r2)) ;
    if (max < min) return SC.RANGE_NOT_FOUND ;
    return { start: min, length: max-min };
  },
  
  /** Returns a clone of the range. */
  cloneRange: function(r) { 
    return { start: r.start, length: r.length }; 
  },
  
  /** Returns true if the two passed ranges are equal.  A null value is
    treated like RANGE_NOT_FOUND.
  */
  rangesEqual: function(r1, r2) {
    if (r1===r2) return true ;
    if (r1 == null) return r2.length < 0 ;
    if (r2 == null) return r1.length < 0 ;
    return (r1.start == r2.start) && (r1.length == r2.length) ;
  },

  /** Returns hex color from hsv value */
  convertHsvToHex: function (h, s, v) {
    var r = 0;
    var g = 0;
    var b = 0;

    if (v > 0) {
      var i = (h == 1) ? 0 : Math.floor(h * 6);
      var f = (h == 1) ? 0 : (h * 6) - i;
      var p = v * (1 - s);
      var q = v * (1 - (s * f));
      var t = v * (1 - (s * (1 - f)));
      var rgb = [[v,t,p],[q,v,p],[p,v,t],[p,q,v],[t,p,v],[v,p,q]];
      r = Math.round(255 * rgb[i][0]);
      g = Math.round(255 * rgb[i][1]);
      b = Math.round(255 * rgb[i][2]);
    }
    return this.parseColor('rgb(' + r + ',' + g + ',' + b + ')');
  },  

  /** Returns hsv color from hex value */
  convertHexToHsv: function (hex) {
    var rgb = this.expandColor(hex);
    var max = Math.max(Math.max(rgb[0], rgb[1]), rgb[2]);
    var min = Math.min(Math.min(rgb[0], rgb[1]), rgb[2]);

    var h = (max == min) ? 0 : ((max == rgb[0]) ? ((rgb[1]-rgb[2])/(max-min)/6) : ((max == rgb[1]) ? ((rgb[2]-rgb[0])/(max-min)/6+1/3) : ((rgb[0]-rgb[1])/(max-min)/6+2/3)));
    h = (h < 0) ? (h + 1) : ((h > 1)  ? (h - 1) : h);
    var s = (max == 0) ? 0 : (1 - min/max);
    var v = max/255;
    return [h, s, v];
  },

  /** regular expression for parsing color: rgb, hex */
  PARSE_COLOR_RGBRE: /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i,
  PARSE_COLOR_HEXRE: /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/,

  // return an array of r,g,b colour
  expandColor: function(color) {
    var hexColor, red, green, blue;
    hexColor = this.parseColor(color);
    if (hexColor) {
      red = parseInt(hexColor.slice(1, 3), 16);
      green = parseInt(hexColor.slice(3, 5), 16);
      blue = parseInt(hexColor.slice(5, 7), 16);
      return [red,green,blue];
    }
  },

  // parse rgb color or 3-digit hex color to return a properly formatted 6-digit hex colour spec, or false
  parseColor: function(string) {
    var i=0, color = '#', match;
    if(match = this.PARSE_COLOR_RGBRE.exec(string)) {
      var part;
      for (i=1; i<=3; i++) {
        part = Math.max(0, Math.min(255, parseInt(match[i],0)));
        color += this.toColorPart(part);
      }
      return color;
    }
    if (match = this.PARSE_COLOR_HEXRE.exec(string)) {
      if(match[1].length == 3) {
        for (i=0; i<3; i++) {
          color += match[1].charAt(i) + match[1].charAt(i);
        }
        return color;
      }
      return '#' + match[1];
    }
    return false;
  },

  // convert one r,g,b number to a 2 digit hex string
  toColorPart: function(number) {
    if (number > 255) number = 255;
    var digits = number.toString(16);
    if (number < 16) return '0' + digits;
    return digits;
  },
  
  
  // Get the computed style from specific element. Useful for cloning styles
  getStyle: function(oElm, strCssRule){
  	var strValue = "";
  	if(document.defaultView && document.defaultView.getComputedStyle){
  		strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
  	}
  	else if(oElm.currentStyle){
  		strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
  			return p1.toUpperCase();
  		});
  		strValue = oElm.currentStyle[strCssRule];
  	}
  	return strValue;
  }

}) ;

/* >>>>>>>>>> BEGIN source/validators/validator.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

SC.VALIDATE_OK = YES;
SC.VALIDATE_NO_CHANGE = NO;

/**
  @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
  @since SproutCore 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 validate() and validateError() methods.  validate() should return NO if the field is not valid, YES otherwise.  If you return NO from this method, then the validateError() method will be called so you can generate an error object describing the specific problem.

    @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.  Return an error object explaining why the field is not valid.  It will only be called if validate() returned NO.
    
    The default implementation of htis method returns a generic error message with the loc string "Invalid.Generate({fieldValue})".  You can simply define this loc string in strings.js if you prefer or you can override this method to provide a more specific error message.
  
    @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 SC.$error(
      "Invalid.General(%@)".loc(field.get('fieldValue')),
      field.get('fieldKey')) ; 
  },

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

  /**
    Invoked just before the user ends editing of the field.

    This is a primitive validation method.  You can implement the two higher-level methods (validate() and validateError()) if you prefer.
    
    The default implementation calls your validate() method and then validateError() if valiate() returns NO.  This method should return SC.VALIDATE_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.VALIDATE_OK or an error object.
  
  */
  validateChange: function(form, field, oldValue) { 
    return this.validate(form,field) ? SC.VALIDATE_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.VALIDATE_OK or an error object.
  
  */  
  validateSubmit: function(form, field) { 
    return this.validate(form,field) ? SC.VALIDATE_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.VALIDATE_NO_CHANGE if you
    did not actually validate the partial string.  If you return 
    SC.VALIDATE_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.VALIDATE_OK, SC.VALIDATE_NO_CHANGE or an error object.
  */  
  validatePartial: function(form, field) { 
    if (!field.get('isValid')) {
      return this.validate(form,field) ? SC.VALIDATE_OK : this.validateError(form, field);
    } else return SC.VALIDATE_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
  */
  validateKeyDown: 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.Validator} 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 (SC.typeOf(validatorKey) === SC.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.classify() ;
      var validatorClass = SC.Validator[validatorKey] ;
      if (SC.none(validatorClass)) {
        throw "validator %@ not found for %@".fmt(validatorKey, field) ;
      } 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) ;
        }
        
        if (!form._validatorHash) form._validatorHash = {} ;
        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) {
    if (this.prototype && this.prototype.fieldValueForObject) {
      return this.prototype.fieldValueForObject(object,form,field) ;
    }
    else return null ;
  },
  
  /**
    Convenience class method to call the objectForFieldValue() instance
    method you define in your subclass.
  */
  objectForFieldValue: function(value, form, field) {
    if (this.prototype && this.prototype.objectForFieldValue) {
      return this.prototype.objectForFieldValue(value,form,field) ;
    }
    else return null ;
  }
  
});

/* >>>>>>>>>> BEGIN source/validators/credit_card.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('validators/validator') ;

/** @class
  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)

  @extends SC.Validator
  @since SproutCore 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 SC.$error("Invalid.CreditCard(%@)".loc(label), label);
  },
  
  /** 
    Allow only numbers, dashes, and spaces 
  */
  validateKeyDown: function(form, field, charStr) {
    return !!charStr.match(/[0-9\- ]/);
  },
  
  checkNumber: function(ccNumb) {
    
    if (!ccNumb || ccNumb.length===0) return YES; // do not validate empty
    
    // remove any spaces or dashes
    ccNumb = ccNumb.replace(/[^0-9]/g,'');
    
    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
  }
    
}) ;

/* >>>>>>>>>> BEGIN source/validators/date.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('validators/validator') ;

/**
  Handle parsing and display of dates.
  
  @class
  @extends SC.Validator
  @author Charles Jolley
  @version 1.0
*/
SC.Validator.Date = SC.Validator.extend(
/** @scope SC.Validator.Date.prototype */ {

  /**
    The standard format you want the validator to convert dates to.
  */
  format: 'NNN d, yyyy h:mm:ss a',
  
  /**
    if we have a number, then convert to a date object.
  */
  fieldValueForObject: function(object, form, field) {
    var date ;
    if (typeof(object) === "number") {
      date = new Date(object) ;
    } else if (object instanceof Date) { date = object; }
      
    if (date) object = date.format(this.get('format')) ;
    
    return object ;
  },

  /**
    Try to pase value as a date. convert into a number, or return null if
    it could not be parsed.
  */
  objectForFieldValue: function(value, form, field) {
    if (value) {
      var date = Date.parseDate(value) ;
      value = (date) ? date.getTime() : null ;
    }
    return value ;
  }
    
}) ;

/* >>>>>>>>>> BEGIN source/validators/email.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('validators/validator') ;

/**
  Requires a valid email format.
  
  @class
  @extends SC.Validator
  @version 1.0
*/
SC.Validator.Email = SC.Validator.extend(
/** @scope SC.Validator.Email.prototype */ {
  
  validate: function(form, field) { 
    return (field.get('fieldValue') || '').match(/.+@.+\...+/) ; 
  },
  
  validateError: function(form, field) {
    var label = field.get('errorLabel') || 'Field' ;
    return SC.$error("Invalid.Email(%@)".loc(label), label) ;
  }  
    
}) ;

/**
  This variant allows an empty field as well as an email address.
  
  @class
  @extends SC.Validator.Email
  @author Charles Jolley
  @version 1.0
*/
SC.Validator.EmailOrEmpty = SC.Validator.Email.extend(
/** @scope SC.Validator.EmailOrEmpty.prototype */ {
  validate: function(form, field) {
    var value = field.get('fieldValue') ; 
    return (value && value.length > 0) ? value.match(/.+@.+\...+/) : true ;
  }
}) ;

/* >>>>>>>>>> BEGIN source/validators/not_empty.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('validators/validator') ;

/**
  Requires some content in field, but does not check the specific content.
  
  @class
  @extends SC.Validator
  @author Charles Jolley
  @version 1.0
*/
SC.Validator.NotEmpty = SC.Validator.extend(
/** @scope SC.Validator.NotEmpty.prototype */ {
  
  validate: function(form, field) {
    var value = field.get('fieldValue'); 
    var ret = !!value ;
    if (ret && value.length) ret = value.length > 0 ;
    return ret ;
  },
  
  validateError: function(form, field) {
    var label = field.get('errorLabel') || 'Field' ;
    return SC.$error("Invalid.NotEmpty(%@)".loc(label.capitalize()), field.get('errorLabel'));
  }
    
}) ;

/* >>>>>>>>>> BEGIN source/validators/number.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('validators/validator') ;

/**
  Handles parsing and validating of numbers.
  
  @extends SC.Validator
  @author Charles Jolley
  @version 1.0
  @class
*/
SC.Validator.Number = SC.Validator.extend(
/** @scope SC.Validator.Number.prototype */ {

  /**
    Number of decimal places to show.  
    
    If 0, then numbers will be treated as integers.  Otherwise, numbers will
    show with a fixed number of decimals.
  */
  places: 0,
  
  fieldValueForObject: function(object, form, field) {
    switch(SC.typeOf(object)) {
      case SC.T_NUMBER:
        object = object.toFixed(this.get('places')) ;
        break ;
      case SC.T_NULL:
      case SC.T_UNDEFINED:
        object = '';
        break ;
    }
    return object ;
  },

  objectForFieldValue: function(value, form, field) {
    
    // strip out commas
    value = value.replace(/,/g,'');
    
    switch(SC.typeOf(value)) {
      case SC.T_STRING:
        if (value.length == '') {
          value = null ;
        } else if (this.get('places') > 0) {
          value = parseFloat(value) ;
        } else {
          value = parseInt(value,0) ;
        }
        break ;
      case SC.T_NULL:
      case SC.T_UNDEFINED:
        value = null ;
        break ;
    }
    return value ;
  },
  
  validate: function(form, field) { 
    var value = field.get('fieldValue') ;
    return (value == '') || !(isNaN(value) || isNaN(parseFloat(value))) ; 
  },
  
  validateError: function(form, field) {
    var label = field.get('errorLabel') || 'Field' ;
    return SC.$error("Invalid.Number(%@)".loc(label), label) ;
  },
  
  /** 
    Allow only numbers, dashes, period, and commas
  */
  validateKeyDown: function(form, field, charStr) {
    return !!charStr.match(/[0-9\.,\-]/);
  }
    
}) ;

/* >>>>>>>>>> BEGIN source/validators/password.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('validators/validator') ;

/**
  Ensures all fields with the Password validator attached in the same form
  contain the same value.
  
  @class
  @extends SC.Validator
  @author Charles Jolley
  @version 1.0
*/
SC.Validator.Password = SC.Validator.extend(
/** @scope SC.Validator.Password.prototype */ {

  attachTo: function(form,field) {
    arguments.callee.base.apply(this,arguments);
    if (!this.fields) this.fields = [] ;
    this.fields.push(field) ;
  },

  validate: function(force) {
    if (!this.fields || this.fields.length === 0) return true ;
    
    var empty = false ;
    var notEmpty = false ;
    var ret = true ;
    var value = this.fields[0].get('fieldValue') ;
    this.fields.forEach(function(field) {
      var curValue = field.get('fieldValue') ;
      if (curValue != value) ret= false ;
      if (!curValue || curValue.length === 0) empty = true ;
      if (curValue && curValue.length > 0) notEmpty = true ;
    }) ;

    // if forces, valid OK if there was an empty.  If not forced, valid OK 
    // only if all fields match AND they are not all empty.
    if (force) {
      return (notEmpty === false) ? false : ret ;
    } else {
      return (empty === true) ? true : ret ;
    }
  },
  
  // update field states
  updateFields: function(form,valid) {
    if (!this.fields || this.fields.length === 0) return true ;
    var err = "Invalid.Password".loc();
    var topField = this._field ;
    this.fields.forEach(function(f) {
      var msg = (valid) ? null : ((f == topField) ? err : '') ;
      form.setErrorFor(f,msg) ;
    }) ;
    return (valid) ? SC.VALIDATE_OK : err ;
  },
  
  validateChange: function(form, field, oldValue) { 
    return this.updateFields(form, this.validate(false)) ;
  },

  // this method is called just before the form is submitted.
  // field: the field toe validate.
  validateSubmit: function(form, field) { 
    return this.updateFields(form, this.validate(true)) ;
  },

  // this method gets called 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.
  validatePartial: function(form, field) {
    var isInvalid = !this._field.get('isValid') ;
    if (isInvalid) {
      return this.updateFields(form, this.validate(false)) ;
    } else return SC.VALIDATE_NO_CHANGE ;
  }
    
}) ;

/* >>>>>>>>>> BEGIN source/views/container.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/view') ;

/** 
  @class
  
  A container view will display its "content" view as its only child.  You can
  use a container view to easily swap out views on your page.  In addition to
  displaying the actual view in the content property, you can also set the 
  nowShowing property to the property path of a view in your page and the
  view will be found and swapped in for you.
  
  If you want to change the way the container view swaps in your new view, 
  override the replaceContent() method.
  
  @extends SC.View
  @since SproutCore 1.0
*/
SC.ContainerView = SC.View.extend(
/** @scope SC.ContainerView.prototype */ {

  classNames: ['sc-container-view'],
  
  /**
    Optional path name for the content view.  Set this to a property path 
    pointing to the view you want to display.  This will automatically change
    the content view for you.  If you pass a single property name (e.g.
    "myView") then the container view will look up the property on its own 
    page object.  If you pass a full property name 
    (e.g. "MyApp.anotherPage.anotherView"), then the path will be followed 
    from the top-level.
    
    @property {String, SC.View}
  */
  nowShowing: null,

  /** 
    The content view to display.  This will become the only child view of
    the view.  Note that if you set the nowShowing property to any value other
    than 'null', the container view will automatically change the contentView
    to reflect view indicated by the value.
    
    @property {SC.View}
  */
  contentView: null,
  
  /** @private */
  contentViewBindingDefault: SC.Binding.single(),
  
  /**
    Replaces any child views with the passed new content.  
    
    This method is automatically called whenever your contentView 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) {
    this.removeAllChildren() ;
    if (newContent) this.appendChild(newContent) ;
  },

  /** @private */
  createChildViews: function() {
    // if contentView is defined, then create the content
    var view = this.get('contentView') ;
    if (view) {
      view = this.contentView = this.createChildView(view) ;
      this.childViews = [view] ;
    } 
  },
  
  /**
    When a container view awakes, it will try to find the nowShowing, if 
    there is one, and set it as content if necessary.
  */
  awake: function() {
    arguments.callee.base.apply(this,arguments);
    var nowShowing = this.get('nowShowing') ;
    if (nowShowing && nowShowing.length>0) this.nowShowingDidChange();
  },
  
  /**
    Invoked whenever the nowShowing property changes.  This will try to find
    the new content if possible and set it.  If you set nowShowing to an 
    empty string or null, then the current content will be cleared.
    
    If you set the content manually, the nowShowing property will be set to
    SC.CONTENT_SET_DIRECTLY
  */
  nowShowingDidChange: function() {
    var nowShowing = this.get('nowShowing') ;
    var content = null;
    //its a property path
    if(SC.typeOf(nowShowing) === SC.T_STRING){
      // if nowShowing was set because the content was set directly, then 
      // do nothing.
      if (nowShowing === SC.CONTENT_SET_DIRECTLY) return ;

      // otherwise, if nowShowing is a non-empty string, try to find it...
      if (nowShowing && nowShowing.length>0) {
        if (nowShowing.indexOf('.')>0) {
          content = SC.objectForPropertyPath(nowShowing, null);
        } else {
          content = SC.objectForPropertyPath(nowShowing, this.get('page'));
        }
      }
    }else{ //its a view
      content = nowShowing;
    }

    
    // only allow views
    if (content && !(content instanceof SC.View)) content = null;
    
    // set content
    this.set('contentView', content) ;
  }.observes('nowShowing'),
  
  /**
    Invoked whenever the content property changes.  This method will simply
    call replaceContent.  Override replaceContent to change how the view is
    swapped out.
  */
  contentViewDidChange: function() {
    this.replaceContent(this.get('contentView'));
  }.observes('contentView')
  
}) ;

/* >>>>>>>>>> BEGIN source/views/image.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/view') ;
sc_require('mixins/control') ;

SC.IMAGE_STATE_NONE = 'none';
SC.IMAGE_STATE_LOADING = 'loading';
SC.IMAGE_STATE_LOADED = 'loaded';
SC.IMAGE_STATE_FAILED = 'failed';
SC.IMAGE_STATE_SPRITE = 'sprite';

/**
  URL to a transparent GIF.  Used for spriting.
*/
SC.BLANK_IMAGE_DATAURL = "data:image/gif;base64,R0lGODlhAQABAJAAAP///wAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw==";

SC.BLANK_IMAGE_URL = SC.browser.msie && SC.browser.msie<8 ? '/static/sproutcore/foundation/en/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0/blank.gif' : SC.BLANK_IMAGE_DATAURL;

/**
  @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.

  Note that there are actually many controls that will natively include 
  images using an icon property name.
  
  @extends SC.View
  @extends SC.Control
  @since SproutCore 1.0
*/
SC.ImageView = SC.View.extend(SC.Control, 
/** @scope SC.ImageView.prototype */ {
  
  /** Image views contain an img tag. */
  classNames: 'sc-image-view',
  tagName: 'img',
  
  /**
    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, SC.IMAGE_STATE_SPRITE
    
    @property {String}
  */
  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.
    
    Note that if you provide a URL, it must contain at least one '/' as this
    is how we autodetect URLs.
    
    @property {String}
  */
  value: null,

  /**
    If YES, image view will use the imageCache to control loading.  This 
    setting is generally preferred.
    
    @property {String}
  */
  useImageCache: YES,
  
  /**
    If YES, this image can load in the background.  Otherwise, it is treated
    as a foreground image.  If the image is not visible on screen, it will
    always be treated as a background image.
  */
  canLoadInBackground: NO,
  
  /**
    If YES, any specified toolTip will be localized before display.
  */
  localize: YES,
  
  displayProperties: 'status toolTip'.w(),
  
  render: function(context, firstTime) {
    // the image source is the value if the status is LOADED or blank
    var status = this.get('status'), value = this.get('value') ;
    
    if (status === SC.IMAGE_STATE_NONE && value) this._image_valueDidChange() ; // setup initial state
    
    // query the status again, as calling this._image_valueDidChange() may
    // update status to SC.IMAGE_STATE_LOADED or SC.IMAGE_STATE_SPRITE
    status = this.get('status');

    var src = (status === SC.IMAGE_STATE_LOADED) ? value : SC.BLANK_IMAGE_URL ;
    if (status === SC.IMAGE_STATE_SPRITE) context.addClass(value) ;
    context.attr('src', src) ;
    
    // If there is a toolTip set, grab it and localize if necessary.
    var toolTip = this.get('toolTip') ;
    if (SC.typeOf(toolTip) === SC.T_STRING) {
      if (this.get('localize')) toolTip = toolTip.loc() ;
      context.attr('title', toolTip) ;
      context.attr('alt', toolTip) ;
    }
  },
  
  /** @private - 
    Whenever the value changes, update the image state and possibly schedule
    an image to load.
  */
  _image_valueDidChange: function() {
    var value = this.get('value'), isUrl;
    if(value && value.isEnumerable) value = value.firstObject();
    
    isUrl = SC.ImageView.valueIsUrl(value);

    // if the old image is still loading, cancel it
    // if (this._loadingUrl) SC.imageCache.abortImage(this._loadingUrl);
    
    // now update local state as needed....
    if (isUrl && this.get('useImageCache')) {
      var isBackground = this.get('isVisibleInWindow') || this.get('canLoadInBackground');
      
      this._loadingUrl = value ; // note that we're loading...
      SC.imageCache.loadImage(value, this, this.imageDidLoad, isBackground);
      
      // only mark us as loading if we are still loading...
      if (this._loadingUrl) this.set('status', SC.IMAGE_STATE_LOADING);
      
    // otherwise, just set state immediately
    } else {
      this._loadingUrl = null ; // not loading...
      this.set('status', (isUrl) ? SC.IMAGE_STATE_LOADED : SC.IMAGE_STATE_SPRITE);
      this.displayDidChange(); // call manually in case status did not change
      // (e.g value changes from one sprite to another)
    }
  }.observes('value'),
  
  /** 
    Called when the imageCache indicates that the image has loaded. 
    Changing the image state will update the display.
  */
  imageDidLoad: function(url, imageOrError) {
    if (url === this._loadingUrl) this._loadingUrl = null;

    // do nothing if we get this notification by the value of the image has 
    // since changed.
    if (this.get('value') === url) {
      this.set('status', SC.$ok(imageOrError) ? SC.IMAGE_STATE_LOADED : SC.IMAGE_STATE_FAILED);
      this.displayDidChange();
    }
  }
  
}) ;

/**
  Returns YES if the passed value looks like an URL and not a CSS class
  name.
*/
SC.ImageView.valueIsUrl = function(value) {
  return value ? value.indexOf('/') >= 0 : NO ;
} ;


/* >>>>>>>>>> BEGIN source/views/label.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/view') ;
sc_require('mixins/control') ;

SC.ALIGN_LEFT = 'left';
SC.ALIGN_RIGHT = 'right';
SC.ALIGN_CENTER = 'center';

SC.REGULAR_WEIGHT = 'normal';
SC.BOLD_WEIGHT = 'bold';

/**
  @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.InlineEditorDelegate
  @since SproutCore 1.0
*/
SC.LabelView = SC.View.extend(SC.Control,
/** @scope SC.LabelView.prototype */ {

  classNames: ['sc-label-view'],

  /**
    Specify the font weight for this.  You may pass SC.REGULAR_WEIGHT, or SC.BOLD_WEIGHT.
  */
  fontWeight: SC.REGULAR_WEIGHT,
  
  /**
    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,
  escapeHTMLBindingDefault: SC.Binding.oneWay().bool(),

  /**
    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,
  localizeBindingDefault: SC.Binding.oneWay().bool(),
  
  /**
    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: '',
  
  /**
    The hint to display if no value is set.  Should be used only if isEditable
    is set to YES.
  */
  hint: null,

  /**
    The exampleInlineTextFieldView property is by default a 
    SC.InlineTextFieldView but it can be set to a customized inline text field
    view.
  
    @property
    @type {SC.View}
    @default {SC.InlineTextFieldView}
  */
  exampleInlineTextFieldView: SC.InlineTextFieldView,
  
  /**
    An optional icon to display to the left of the label.  Set this value
    to either a CSS class name (for spriting) or an image URL.
  */
  icon: null,
  
  /**
    Set the alignment of the label view.
  */
  textAlign: SC.ALIGN_LEFT,
  
  /**
    If you want the inline editor to be multiline set this property to YES.
  */
  isInlineEditorMultiline: NO,
  
  /**
    [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('formatter', this.displayDelegate) ;
    if (formatter) {
      var formattedValue = (SC.typeOf(formatter) === SC.T_FUNCTION) ? formatter(value, this) : formatter.fieldValueForObject(value, this) ;
      if (!SC.none(formattedValue)) value = formattedValue ;
    }
    
    // 2. If the returned value is an array, convert items to strings and 
    // join with commas.
    if (SC.typeOf(value) === SC.T_ARRAY) {
      var ary = [];
      for(var idx=0;idx<value.get('length');idx++) {
        var x = value.objectAt(idx) ;
        if (!SC.none(x) && x.toString) x = x.toString() ;
        ary.push(x) ;
      }
      value = ary.join(',') ;
    }
    
    // 3. If value is not a string, convert to string. (handles 0)
    if (!SC.none(value) && value.toString) value = value.toString() ;
    
    // 4. Localize
    if (value && this.getDelegateProperty('localize', this.displayDelegate)) value = value.loc() ;

    // 5. escapeHTML if needed
    if (this.get('escapeHTML')) value = SC.RenderContext.escapeHTML(value);
    
    return value ;
  }.property('value', 'localize', 'formatter', 'escapeHTML').cacheable(),
  
  /**
    Enables editing using the inline editor.
  */
  isEditable: NO,
  isEditableBindingDefault: SC.Binding.bool(),

  /**
    YES if currently editing label view.
  */
  isEditing: NO,
  
  /**
    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 el = this.$();
    var value = this.get('value') || '' ;
    var f = SC.viewportOffset(el[0]) ;
    var frameTemp = this.convertFrameFromView(this.get('frame'), null) ;
    f.width=frameTemp.width;
    f.height=frameTemp.height;
    
    SC.InlineTextFieldView.beginEditing({
      frame: f,
      delegate: this,
      exampleElement: el,
      value: value, 
      multiline: this.get('isInlineEditorMultiline'), 
      isCollection: NO,
      validator: this.get('validator'),
      exampleInlineTextFieldView: this.get('exampleInlineTextFieldView')
    });
  },
  
  /**
    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.$().css('opacity') ;
    this.$().css('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.$().css('opacity', this._oldOpacity);
    this._oldOpacity = null ;
    this.set('isEditing', NO) ;
  },

  displayProperties: 'displayValue textAlign fontWeight icon'.w(),
  
  _TEMPORARY_CLASS_HASH: {},
  
  render: function(context, firstTime) {
    var value = this.get('displayValue'),
        icon = this.get('icon'),
        hint = this.get('hint');
    
    // add icon if needed
    if (icon) {
      var url = (icon.indexOf('/')>=0) ? icon : SC.BLANK_IMAGE_URL;
      var className = (url === icon) ? '' : icon ;
      icon = '<img src="%@" alt="" class="icon %@" />'.fmt(url, className) ;
      context.push(icon);
    }
    
    // if there is a hint set and no value, render the hint
    // otherwise, render the value
    if (hint && (!value || value === '')) {
      context.push('<span class="sc-hint">', hint, '</span>');
    } else { 
      context.push(value);
    }
    
    // and setup alignment and font-weight on styles
    context.addStyle('text-align',  this.get('textAlign'))
           .addStyle('font-weight', this.get('fontWeight'));
           
    var classes = this._TEMPORARY_CLASS_HASH;
    classes.icon = !!this.get('icon');
    context.setClass(classes);
           
    // if we are editing, set the opacity to 0
    if (this.get('isEditing')) context.addStyle('opacity', 0.0);
  }
  
});

/* >>>>>>>>>> BEGIN source/panes/main.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('panes/pane');

/** @class

  Most SproutCore applications have a main pane, which dominates the 
  application page.  You can extend from this view to implement your own main 
  pane.  This class will automatically make itself main whenever you append it 
  to a document, removing any other main pane that might be currently in 
  place.  If you do have another already focused as the keyPane, this view 
  will also make itself key automatically.  The default way to use the main 
  pane is to simply add it to your page like this:
  
  {{{
    SC.MainPane.create().append();
  }}}
  
  This will cause your root view to display.  The default layout for a 
  MainPane is to cover the entire document window and to resize with the 
  window.

  @extends SC.Pane
  @since SproutCore 1.0
*/
SC.MainPane = SC.Pane.extend({

  /** @private */
  layout: { left:0, right:0, top:0, bottom:0 },
  
  /** @private - extends SC.Pane's method */
  paneDidAttach: function() {
    var ret = arguments.callee.base.apply(this,arguments);
    var responder = this.rootResponder;
    responder.makeMainPane(this);
    if (!responder.get('keyRootView')) responder.makeKeyPane(this);
    return ret ;
  },
  
  /** @private */
  acceptsKeyPane: YES,

  /** @private */
  classNames: ['sc-main']
  
});
; tiki.script('sproutcore/foundation:en/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/desktop', {"packages":{"sproutcore\/runtime":{"scripts":[{"url":"\/static\/sproutcore\/runtime\/en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js","id":"sproutcore\/runtime:en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js"}]},"sproutcore\/datastore":{"scripts":[{"url":"\/static\/sproutcore\/datastore\/en\/334c3630f54926a13d0810d33b3686af21530ee7\/javascript.js","id":"sproutcore\/datastore:en\/334c3630f54926a13d0810d33b3686af21530ee7\/javascript.js"}]},"sproutcore\/foundation":{"stylesheets":[{"url":"\/static\/sproutcore\/foundation\/en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/stylesheet.css","id":"sproutcore\/foundation:en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/stylesheet.css"}],"scripts":[{"url":"\/static\/sproutcore\/foundation\/en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/javascript.js","id":"sproutcore\/foundation:en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/javascript.js"}]}},"stylesheets":[{"url":"\/static\/sproutcore\/desktop\/en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/stylesheet.css","id":"sproutcore\/desktop:en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/stylesheet.css"}],"depends":["sproutcore\/runtime","sproutcore\/datastore","sproutcore\/foundation"],"scripts":[{"url":"\/static\/sproutcore\/desktop\/en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/javascript.js","id":"sproutcore\/desktop:en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/javascript.js"}]});
tiki.global('sproutcore/desktop');

/* >>>>>>>>>> BEGIN source/lproj/strings.js */
// ========================================================================
// Sprout Core
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

// English Strings.
SC.stringsFor('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.",
  "Invalid.General(%@)": "%@ is invalid.  Please try again.",
  "Invalid.Number(%@)": "%@ is not a number."
}) ;

/* >>>>>>>>>> BEGIN source/core.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================


/**
  If set to NO, then pressing backspace will NOT navigate to the previous 
  page in the browser history, which is the default behavior in most browsers.
  
  Usually it is best to leave this property set to NO in order to prever the
  user from inadvertantly losing data by pressing the backspace key.
  
  @property {Boolean}
*/
SC.allowsBackspaceToPreviousPage = NO;

/* >>>>>>>>>> BEGIN source/mixins/border.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

SC.BORDER_BEZEL  = 'sc-bezel-border';
SC.BORDER_BLACK  = 'sc-black-border';
SC.BORDER_GRAY   = 'sc-gray-border';
SC.BORDER_TOP    = 'sc-top-border';
SC.BORDER_BOTTOM = 'sc-bottom-border';
SC.BORDER_NONE   = null ;

/**
  @namespace

  The Border mixin can be applied to any view to give it a visual border.
  In addition to specifying the mixing itself, you should specify the border
  style with the borderStyle property on your view.  
  
  border style can be any predefined CSS class name or a border color.  If 
  you specify a CSS class name, it must end in -border.  
  
  SproutCore pre-defines several useful border styles including:
  
  * SC.BORDER_BEZEL  - displays an inlaid bezel
  * SC.BORDER_BLACK  - displays a black border
  * SC.BORDER_GRAY   - displays a gray border
  * SC.BORDER_TOP    - displays a border on the top only
  * SC.BORDER_BOTTOM - displays a border on the bottom only
  * SC.BORDER_NONE   - disables the border
  
  Note that borders do not count in the dimensions of the view.  You may need
  to adjust your layout to make room for it.
  
  @since SproutCore 1.0
*/
SC.Border = {
  
  borderStyle: SC.BORDER_GRAY,
  
  _BORDER_REGEXP: (/-border$/),
  
  renderMixin: function(context, firstTime) {
    var style = this.get('borderStyle');
    if (style) {
      if (this._BORDER_REGEXP.exec(style)) {
        context.addClass(style);
      } else content.addStyle('border', '1px %@ solid'.fmt(style));
    }
  }
  
};
/* >>>>>>>>>> BEGIN source/mixins/collection_group.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @namespace

  TODO: Add full description of SC.CollectionGroup
  
  Any view you want to use as a group view in a collection must include this
  mixin.
  
  @since SproutCore 1.0
*/
SC.CollectionGroup = {
  
  classNames: ['sc-collection-group']
  
};

/* >>>>>>>>>> BEGIN source/mixins/collection_row_delegate.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================



/** 
  @namespace
  
  CollectionRowDelegates are consulted by SC.ListView and SC.TableView to 
  control the height of rows, including specifying custom heights for 
  specific rows.  
  
  You can implement a custom row height in one of two ways.  
  
*/
SC.CollectionRowDelegate = {

  /** walk like a duck */
  isCollectionRowDelegate: YES,
  
  /**
    Default row height.  Unless you implement some custom row height 
    support, this row height will be used for all items.
    
    @property
    @type Number
  */
  rowHeight: 18,

  /**
    Index set of rows that should have a custom row height.  If you need 
    certains rows to have a custom row height, then set this property to a 
    non-null value.  Otherwise leave it blank to disable custom row heights.
    
    @property
    @type SC.IndexSet
  */
  customRowHeightIndexes: null,
  
  /**
    Called for each index in the customRowHeightIndexes set to get the 
    actual row height for the index.  This method should return the default
    rowHeight if you don't want the row to have a custom height.
    
    The default implementation just returns the default rowHeight.
    
    @param {SC.CollectionView} view the calling view
    @param {Object} content the content array
    @param {Number} contentIndex the index 
    @returns {Number} row height
  */
  contentIndexRowHeight: function(view, content, contentIndex) {
    return this.get('rowHeight');    
  }
  
  
};

/* >>>>>>>>>> BEGIN source/mixins/collection_view_delegate.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @namespace

  A Collection View Delegate is consulted by a SC.CollectionView's to make
  policy decisions about certain behaviors such as selection control and
  drag and drop.  If you need to control other aspects of your data, you may
  also want to add the SC.CollectionContent mixin.
  
  To act as a Collection Delegate, just apply this mixin to your class.  You
  must then set the "delegate" property on the CollectionView to your object.
  
  Alternatively, if no delegate is set on a CollectionView, but the content 
  implements this mixin, the content object will be used as the delegate 
  instead.
  
  If you set an ArrayController or its arrangedObjects property as the content
  of a CollectionView, the ArrayController will automatically act as the 
  delegate for the view.
  
  @since SproutCore 1.0
*/
SC.CollectionViewDelegate = {

  /**
    Used to detect the mixin by SC.CollectionView
  */
  isCollectionViewDelegate: YES,
  
  // ..........................................................
  // SELECTION
  // 
  
  /**
    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 {SC.CollectionView} view the collection view
    @param {SC.IndexSet} sel Proposed array of selected objects.
    @returns {SC.IndexSet} Actual allow selection index set
  */
  collectionViewSelectionForProposedSelection: function(view, sel) {
    return sel ;
  },

  /**
    Called by the collection when attempting to select an item.  Return the 
    actual indexes you want to allow to be selected.  Return null to disallow
    the change.  The default allows all selection.
    
    @param {SC.CollectionView} view the view collection view
    @param {SC.IndexSet} indexes the indexes to be selected
    @param {Boolean} extend YES if the indexes will extend existing sel
    @returns {SC.IndexSet} allowed index set
  */
  collectionViewShouldSelectIndexes: function (view, indexes, extend) { 
    return indexes; 
  },
  
  /**
    Called by the collection when attempting to deselect an item.  Return the 
    actual indexes you want to allow to be deselected.  Return null to 
    disallow the change.  The default allows all selection.
    
    Note that you should not modify the passed in IndexSet.  clone it instead.
    
    @param {SC.CollectionView} view the view collection view
    @param {SC.IndexSet} indexes the indexes to be selected
    @returns {SC.IndexSet} allowed index set
  */
  collectionViewShouldDeselectIndexes: function (view, indexes) { 
    return indexes; 
  },

  // ..........................................................
  // EDIT OPERATIONS
  // 
  
  /**
    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 {SC.CollectionView} view the collection view
    @param {SC.IndexSet} indexes proposed index set of items to delete.
    @returns {SC.IndexSet} index set allowed to delete or null.
  */
  collectionViewShouldDeleteIndexes: function(view, indexes) { 
    return indexes; 
  },
  
  /**
    Called by the collection view to actually delete the selected items.
    
    The default behavior will use standard array operators to delete the 
    indexes from the array.  You can implement this method to provide your own 
    deletion method.
    
    If you simply want to control the items to be deleted, you should instead
    implement collectionViewShouldDeleteItems().  This method will only be 
    called if canDeleteContent is YES and collectionViewShouldDeleteIndexes()
    returns a non-empty index set
    
    @param {SC.CollectionView} view collection view
    @param {SC.IndexSet} indexes the items to delete
    @returns {Boolean} YES if the deletion was a success.
  */
  collectionViewDeleteContent: function(view, content, indexes) {
    if (!content) return NO ;

    if (SC.typeOf(content.destroyAt) === SC.T_FUNCTION) {
      content.destroyAt(indexes);
      view.selectPreviousItem(NO, 1) ;
      return YES ;
      
    } else if (SC.typeOf(content.removeAt) === SC.T_FUNCTION) {
      content.removeAt(indexes);
      view.selectPreviousItem(NO, 1) ;
      return YES;
      
    } else return NO ;
  },
  
  // ..........................................................
  // DRAGGING
  // 
  
  /**
    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, drag, dataType) {  
    return null ;
  },
  
  /**
    Called once during a drag the first time view is entered. Return all 
    possible drag operations OR'd together.
    
    @param {SC.CollectionView} view
      the collection view that initiated the drag

    @param {SC.Drag} drag
      the drag object
    
    @param {Number} proposedDragOperations
      proposed logical OR of allowed drag operations.

    @returns {Number} the allowed drag operations. Defaults to op
  */
  collectionViewComputeDragOperations: function(view, drag, proposedDragOperations) {
    return proposedDragOperations ;
  },
  
  /**
    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 proposedDropOperation 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 op {Number} proposed logical OR of allowed drag operations.
    @param proposedInsertionIndex {Number} an index into the content array 
      representing the proposed insertion point.
    @param proposedDropOperation {String} the proposed drop operation.  Will be one of SC.DROP_ON, SC.DROP_BEFORE, or SC.DROP_ANY.
    @returns the allowed drag operation.  Defaults to op
  */
  collectionViewValidateDragOperation: function(view, drag, op, proposedInsertionIndex, proposedDropOperation) {
    // don't allow dropping on by default
    return (proposedDropOperation & SC.DROP_ON) ? SC.DRAG_NONE : op ;
  },
  
  /**
    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}
    @param drag {SC.Drag} the current drag object
    @param op {Number} proposed logical OR of allowed drag operations.
    @param proposedInsertionIndex {Number} an index into the content array representing the proposed insertion point.
    @param proposedDropOperation {String} the proposed drop operation.  Will be one of SC.DROP_ON, SC.DROP_BEFORE, or SC.DROP_ANY.
    @returns the allowed drag operation.  Defaults to proposedDragOperation
  */
  collectionViewPerformDragOperation: function(view, drag, op, proposedInsertionIndex, proposedDropOperation) {
    return SC.DRAG_NONE ;
  },
  
  /**
    Renders a drag view for the passed content indexes. If you return null
    from this, then a default drag view will be generated for you.
    
    @param {SC.CollectionView} view
    @param {SC.IndexSet} dragContent
    @returns {SC.View} view or null
  */
  collectionViewDragViewFor: function(view, dragContent) {
    return null;
  },

  /**
    Allows the ghost view created in collectionViewDragViewFor to be displayed
    like a cursor instead of the default implementation. This sets the view 
    origin to be the location of the mouse cursor.
    
    @property {Boolean} ghost view acts like a cursor
  */
  ghostActsLikeCursor: NO
  
};

/* >>>>>>>>>> BEGIN source/mixins/scrollable.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

/**
  @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.ClassicView} 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 ;
  }
  
} ;


/* >>>>>>>>>> BEGIN source/panes/modal.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

/** @class

  A modal pane is used to capture mouse events inside a pane that is modal.
  You normally will not work with modal panes directly, though you may set 
  the modalPane property to a subclass of this pane when designing custom 
  panes.
  
  A modal pane is automatically appended when a pane with isModal set to
  YES is made visible and removed when the same pane is hidden.  The only 
  purpose of the ModalPane is to absorb mouse events so that they cannot 
  filter through to the underlying content. 
  
  @extends SC.Pane
  @since SproutCore 1.0
*/
SC.ModalPane = SC.Pane.extend({
  
  classNames: 'sc-modal',
  
  /** @private cover the entire screen */
  layout: { top: 0, left: 0, bottom: 0, right: 0 },

  _openPaneCount: 0,
  
  /** 
    Called by a pane just before it appends itself.   The modal pane can
    make itself visible first if needed.

    @param {SC.Pane} pane the pane
    @returns {SC.ModalPane} receiver
  */
  paneWillAppend: function(pane) {
    this._openPaneCount++;
    if (!this.get('isVisibleInWindow')) this.append();
    return this ;    
  },
  
  /**
    Called by a pane just after it removes itself.  The modal pane can remove
    itself if needed.   Modal panes only remove themselves when an equal 
    number of paneWillAppend() and paneDidRemove() calls are received.
  
    @param {SC.Pane} pane the pane
    @returns {SC.ModalPane} receiver
  */
  paneDidRemove: function(pane) { 
    this._openPaneCount--;
    if (this._openPaneCount <= 0) {
      this._openPaneCount = 0 ;
      if (this.get('isVisibleInWindow')) this.remove();
    }
  },
  
  /** 
    If owner pane implements modalPaneDidClick(), call it on mouse down.
  */
  mouseDown: function(evt) {
    var owner = this.get('owner');
    if (owner && owner.modalPaneDidClick) owner.modalPaneDidClick(evt);
  }
  
});

/* >>>>>>>>>> BEGIN source/panes/panel.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

sc_require('panes/modal');

/** 
  Shadow views from top-left corner clockwise
*/

/** @class

  Most SproutCore applications need modal panels. The default way to use the 
  panel pane is to simply add it to your page like this:
  
  {{{
    SC.PanelPane.create({
      layout: { width: 400, height: 200, centerX: 0, centerY: 0 },
      contentView: SC.View.extend({
      })
    }).append();
  }}}
  
  This will cause your panel to display.  The default layout for a Panel 
  is to cover the entire document window with a semi-opaque background, and to 
  resize with the window.
  
  @extends SC.Pane
  @author Erich Ocean
  @since SproutCore 1.0
*/
SC.PanelPane = SC.Pane.extend({

  layout: { left:0, right:0, top:0, bottom:0 },
  classNames: ['sc-panel'],
  acceptsKeyPane: YES,
  
  /**
    Indicates that a pane is modal and should not allow clicks to pass
    though to panes underneath it.  This will usually cause the pane to show
    the modalPane underneath it.
    
    @property {Boolean}
  */
  isModal: YES,

  /**
    The modal pane to place behind this pane if this pane is modal.  This 
    must be a subclass or an instance of SC.ModalPane.
  */
  modalPane: SC.ModalPane.extend({
    classNames: 'for-sc-panel'
  }),
  
  // ..........................................................
  // CONTENT VIEW
  // 
  
  /**
    Set this to the view you want to act as the content within the panel.
    
    @property {SC.View}
  */
  contentView: null,
  contentViewBindingDefault: SC.Binding.single(),

  /**
    Replaces any child views with the passed new content.  
    
    This method is automatically called whenever your contentView property 
    changes.  You can override it if you want to provide some behavior other
    than the default.
    
    @param {SC.View} newContent the new panel view or null.
    @returns {void}
  */
  
  render: function(context, firstTime) {
    if (context.needsContent) {
      this.renderChildViews(context, firstTime) ;
      context.push("<div class='top-left-edge'></div>",
       "<div class='top-edge'></div>",
       "<div class='top-right-edge'></div>",
       "<div class='right-edge'></div>",
       "<div class='bottom-right-edge'></div>",
       "<div class='bottom-edge'></div>",
       "<div class='bottom-left-edge'></div>",
       "<div class='left-edge'></div>");
    }
  },
  
  replaceContent: function(newContent) {
    this.removeAllChildren() ;
    if (newContent) this.appendChild(newContent) ;
  },

  /** @private */
  createChildViews: function() {
    // if contentView is defined, then create the content
    var view = this.contentView ;
    if (view) {
      view = this.contentView = this.createChildView(view) ;
      this.childViews = [view] ;
    }
  },

  
  /**
    Invoked whenever the content property changes.  This method will simply
    call replaceContent.  Override replaceContent to change how the view is
    swapped out.
  */
  contentViewDidChange: function() {
    this.replaceContent(this.get('contentView'));
  }.observes('contentView'),

  // ..........................................................
  // INTERNAL SUPPORT
  //
  
  // get the modal pane. 
  _modalPane: function() {
    var pane = this.get('modalPane');
    
    // instantiate if needed
    if (pane && pane.isClass) {
      pane = pane.create({ owner: this });
      this.set('modalPane', pane); 
    }
    
    return pane ;
  },
  
  /** @private - whenever showing on screen, deal with modal pane as well */
  appendTo: function(elem) {
    var pane ;
    if (!this.get('isVisibleInWindow') && this.get('isModal') && (pane = this._modalPane())) {
      this._isShowingModal = YES;
      pane.paneWillAppend(this);
    }
    return arguments.callee.base.apply(this,arguments);
  },
  
  /** @private - when removing from screen, deal with modal pane as well. */
  remove: function() {
    var pane, ret = arguments.callee.base.apply(this,arguments);
    
    if (this._isShowingModal) {
      this._isShowingModal = NO ;
      if (pane = this._modalPane()) pane.paneDidRemove(this);
    }
    return ret ;
  },
  
  /** @private - if isModal state changes, update pane state if needed. */
  _isModalDidChange: function() {
    var pane, isModal = this.get('isModal');
    if (isModal) {
       if (!this._isShowingModal && this.get('isVisibleInWindow') && (pane = this._modalPane())) {
         this._isShowingModal = YES;
         pane.paneWillAppend(this);
       }
       
    } else {
      if (this._isShowingModal && (pane = this._modalPane())) {
        this._isShowingModal = NO ;
        pane.paneDidRemove(this); 
      }
    }
  }.observes('isModal'),
  
  /** @private - extends SC.Pane's method - make panel keyPane when shown */
  paneDidAttach: function() {
    var ret = arguments.callee.base.apply(this,arguments);
    this.becomeKeyPane();
    return ret ;
  },

  /** @private - suppress all mouse events on panel itself. */
  mouseDown: function(evt) { return YES; }
  
});
/* >>>>>>>>>> BEGIN source/views/button.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/*jslint evil:true */

// Constants
SC.TOGGLE_BEHAVIOR = 'toggle';
SC.PUSH_BEHAVIOR =   'push';
SC.TOGGLE_ON_BEHAVIOR = "on";
SC.TOGGLE_OFF_BEHAVIOR = "off" ;  

/** @class

  Implements a push-button-style button.  This class is used to implement 
  both standard push buttons and tab-style controls.  See also SC.CheckboxView
  and SC.RadioView which are implemented as field views, but can also be 
  treated as buttons.
  
  @extends SC.View
  @extends SC.Control
  @extends SC.Button
  @since SproutCore 1.0  
*/
SC.ButtonView = SC.View.extend(SC.Control, SC.Button, SC.StaticLayout,
/** @scope SC.ButtonView.prototype */ {
  
  tagName: 'a',
  classNames: ['sc-button-view'],
  
  /**
    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", "capsule", "checkbox", 
    and "radio"
  */
  theme: 'square',
  
  /**
    Optionally set the behavioral mode of this button.  
  
    Possible values are:

    - *SC.PUSH_BEHAVIOR* Pressing the button will trigger an action tied to the button. Does not change the value of the button.
    - *SC.TOGGLE_BEHAVIOR* 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* Pressing the button will set the current state to true no matter the previous value.
    - *SC.TOGGLE_OFF_BEHAVIOR* Pressing the button will set the current state to false no matter the previous value.
  
  */  
  buttonBehavior: SC.PUSH_BEHAVIOR,

  /**
    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.oneWay().bool(),
  
  /**
    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.oneWay().bool(),

  /**
    The button href value.  This can be used to create localized button href values.  Setting an empty or null href will set it to javascript:;
  */
  href: '',

  /**
    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,
  
  /**
    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 NO;
    this.set('isActive', YES);
    this._action(evt);
    this.didTriggerAction();
    this.invokeLater('set', 200, 'isActive', NO);
    return true;
  },
  
  /**
    This method is called anytime the button's action is triggered.  You can implement this method in your own subclass to perform any cleanup needed after an action is performed.
  */
  didTriggerAction: function() {},

  /**
    The minimum width the button title should consume.  This property is used
    when generating the HTML styling for the title itself.  The default 
    width of 80 usually provides a nice looking style, but you can set it to 0
    if you want to disable minimum title width.
    
    Note that the title width does not exactly match the width of the button
    itself.  Extra padding added by the theme can impact the final total
    size.
  */
  titleMinWidth: 80,
  
  // ................................................................
  // INTERNAL SUPPORT

  /** @private - save keyEquivalent for later use */
  init: function() {
    arguments.callee.base.apply(this,arguments);
    
    //cache the key equivalent
    if(this.get("keyEquivalent")) this._defaultKeyEquivalent = this.get("keyEquivalent"); 
  },

  _TEMPORARY_CLASS_HASH: {},
  
  // display properties that should automatically cause a refresh.
  // isCancel and isDefault also cause a refresh but this is implemented as 
  // a separate observer (see below)
  displayProperties: ['href', 'icon', 'title', 'value', 'toolTip'],

  render: function(context, firstTime) {
    // add href attr if tagName is anchor...
    var href, toolTip, classes;
    if (this.get('tagName') === 'a') {
      href = this.get('href');
      if (!href || (href.length === 0)) href = "javascript"+":;";
      context.attr('href', href);
    }

    // If there is a toolTip set, grab it and localize if necessary.
    toolTip = this.get('toolTip') ;
    if (SC.typeOf(toolTip) === SC.T_STRING) {
      if (this.get('localize')) toolTip = toolTip.loc() ;
      context.attr('title', toolTip) ;
      context.attr('alt', toolTip) ;
    }
    
    // add some standard attributes & classes.
    classes = this._TEMPORARY_CLASS_HASH;
    classes.def = this.get('isDefault');
    classes.cancel = this.get('isCancel');
    classes.icon = !!this.get('icon');
    context.attr('role', 'button')
      .setClass(classes).addClass(this.get('theme'));
    // render inner html 
    if(firstTime){
       context = context.push("<span class='sc-button-inner' style = 'min-width:%@px'>"
            .fmt(this.get('titleMinWidth')));
      this.renderTitle(context, firstTime) ; // from button mixin
      context.push("</span>") ;
    }else{
      this.renderTitle(context, firstTime) ;
    }
   },
  
  /** @private {String} used to store a previously defined key equiv */
  _defaultKeyEquivalent: null,
  
  /** @private
    Whenever the isDefault or isCancel property changes, update the display and change the keyEquivalent.
  */  
  _isDefaultOrCancelDidChange: function() {
    var isDef = !!this.get('isDefault'),
        isCancel = !isDef && this.get('isCancel') ;
    
    if(this.didChangeFor('defaultCancelChanged','isDefault','isCancel')) {
      this.displayDidChange() ; // make sure to update the UI
      if (isDef) {
        this.set('keyEquivalent', 'return'); // change the key equivalent
      } else if (isCancel) {
        this.setIfChanged('keyEquivalent', 'escape') ;
      } else {
        //restore the default key equivalent
        this.set("keyEquivalent",this._defaultKeyEquivalent);
      }
    }
      
  }.observes('isDefault', 'isCancel'),
    
  isMouseDown: false, 

  /** @private 
    On mouse down, set active only if enabled.
  */    
  mouseDown: function(evt) {
    if (!this.get('isEnabled')) return YES ; // handled event, but do nothing
    this.set('isActive', YES);
    this._isMouseDown = YES;

    if (!this._isFocused && (this.get('buttonBehavior')!==SC.PUSH_BEHAVIOR)) {
      this._isFocused = YES ;
      this.becomeFirstResponder();
      if (this.get('isVisibleInWindow')) {
        this.$()[0].focus();
      }
    }

    return YES ;
  },

  /** @private
    Remove the active class on mouseOut if mouse is down.
  */  
  mouseExited: function(evt) {
    if (this._isMouseDown) this.set('isActive', NO);
    return YES;
  },

  /** @private
    If mouse was down and we renter the button area, set the active state again.
  */  
  mouseEntered: function(evt) {
    this.set('isActive', this._isMouseDown);
    return YES;
  },

  /** @private
    ON mouse up, trigger the action only if we are enabled and the mouse was released inside of the view.
  */  
  mouseUp: function(evt) {
    if (this._isMouseDown) this.set('isActive', NO); // track independently in case isEnabled has changed
    this._isMouseDown = false;
    var inside = this.$().within(evt.target) ;
    if (inside && this.get('isEnabled')) this._action(evt) ;
    return true ;
  },
  
  
  keyDown: function(evt) {
    // handle tab key
    if (evt.which === 9) {
      var view = evt.shiftKey ? this.get('previousValidKeyView') : this.get('nextValidKeyView');
      view.becomeFirstResponder();
      return YES ; // handled
    }    
    if (evt.which === 13) {
      this.triggerAction(evt);
      return YES ; // handled
    }
    return YES; 
  },

  /** @private  Perform an action based on the behavior of the button.
  
   - toggle behavior: switch to on/off state
   - on behavior: turn on.
   - off behavior: turn off.
   - otherwise: invoke target/action
  */
  _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) {
        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'),
          target = this.get('target') || null;
      if (action) {
        if (this._hasLegacyActionHandler()) {
          // old school... 
          this._triggerLegacyActionHandler(evt);
        } else {
          // newer action method + optional target syntax...
          this.getPath('pane.rootResponder').sendAction(action, target, this, this.get('pane'));
        }
      }
    }
  },
  
  /** @private */
  _hasLegacyActionHandler: function()
  {
    var action = this.get('action');
    if (action && (SC.typeOf(action) == SC.T_FUNCTION)) return true;
    if (action && (SC.typeOf(action) == SC.T_STRING) && (action.indexOf('.') != -1)) return true;
    return false;
  },

  /** @private */
  _triggerLegacyActionHandler: function( evt )
  {
    if (!this._hasLegacyActionHandler()) return false;
    
    var action = this.get('action');
    if (SC.typeOf(action) == SC.T_FUNCTION) this.action(evt);
    if (SC.typeOf(action) == SC.T_STRING) {
      eval("this.action = function(e) { return "+ action +"(this, e); };");
      this.action(evt);
    }
  },
  
  /** tied to the isEnabled state */
  acceptsFirstResponder: function() {
        return this.get('isEnabled');
      }.property('isEnabled'),
  
  willBecomeKeyResponderFrom: function(keyView) {
    // focus the text field.
    if (!this._isFocused) {
      this._isFocused = YES ;
      this.becomeFirstResponder();
      if (this.get('isVisibleInWindow')) {
        this.$()[0].focus();
      }
    }
  },
  
  willLoseKeyResponderTo: function(responder) {
    if (this._isFocused) this._isFocused = NO ;
  },
  
  didCreateLayer: function() {
    //Fix for IE7 min-with bug
    if(SC.browser.msie<8) {
      var buttonInner = this.$('.sc-button-inner')[0];
      if (buttonInner){
        var mL = buttonInner.style.marginLeft;
        this.$('.sc-button-label')[0].style.minWidth=this.get('titleMinWidth')-mL;
      }
    }
  }
  
}) ;


/* >>>>>>>>>> BEGIN source/panes/alert.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('panes/panel');
sc_require('views/button');
/** 
  button1 : 1st button from the right. default:OK
  button2 : 2nd button from the right. Optional. Could be Cancel or 2nd action.
  button3 : 1st button from the left. Optional. Could be Cancel or alternative option.
*/
/** 
  Passed to delegate when alert pane is dismissed by pressing button 1
*/
SC.BUTTON1_STATUS = 'button1';

/** 
  Passed to delegate when alert pane is dismissed by pressing button 2
*/
SC.BUTTON2_STATUS = 'button2';

/** 
  Passed to delegate when alert pane is dismissed by pressing button 3
*/
SC.BUTTON3_STATUS = 'button3';

/** @class
  Displays a preformatted modal alert pane.
  
  Alert panes are a simple way to provide modal messaging that otherwise 
  blocks the user's interaction with your application.  Alert panes are 
  useful for showing important error messages and confirmation dialogs.  They
  provide a better user experience than using the OS-level alert dialogs.
  
  h1. Displaying an Alert Pane
  
  The easiest way to display an alert pane is to use one of the various 
  class methods defined on SC.AlertPane, passing the message and an optional
  detailed description and caption.  
  
  There are four variations of this method can you can invoke:  
  
  - *warn()* - displays an alert pane with a warning icon to the left.
  - *error()* - displays an alert with an error icon to the left
  - *info()* - displays an alert with an info icon to the left
  - *plain()* - displays an alert w/o any icon
  - *show()* - displays an alert with a customizable icon to the left
  
  In addition to passing a message, description and caption, you can also customize
  the title of the button 1 (OK) and add an optional button 2 and 3 (Cancel or Extra).  Just
  pass these titles of these buttons to enable them or null to disable then.
  
  Additionally, you can pass a delegate object as the last parameter.  This
  delegate's 'alertPaneDidDismiss()' method will be called when the pane
  is dismissed, passing the pane instance and a key indicating which 
  button was pressed.
  
  h1. Examples
  
  Show a simple AlertPane with an OK button:
  
  {{{
    SC.AlertPane.warn("Could not load calendar", "Your internet connection may be unavailable or our servers may be down.  Try again in a few minutes.");
  }}}
  
  Show an AlertPane with a customized OK title (title will be 'Try Again'):
  
  {{{
    SC.AlertPane.warn("Could not load calendar", "Your internet connection may be unavailable or our servers may be down.  Try again in a few minutes.", "Try Again");
  }}}
  
  Show an AlertPane with a custom OK, a Cancel button and an Extra button, 
  each with custom titles.  Also, pass a delegate that will be invoked when
  the user's dismisses the dialog.
  
  {{{

    MyApp.calendarController = SC.Object.create({
      alertPaneDidDismiss: function(pane, status) {
        switch(status) {
          case SC.BUTTON1_STATUS:
            this.tryAgain();
            break;
            
          case SC.BUTTON2_STATUS:
            // do nothing
            break;
            
          case SC.BUTTON3_STATUS:
            this.showMoreInfo();
            break;
        }
      },
      
      ...
    });
    
    SC.AlertPane.warn("Could not load calendar", "Your internet connection may be unavailable or our servers may be down.  Try again in a few minutes.", "Try Again", "Cancel", "More Info...", MyApp.calendarController);
  }}}
  
  @extends SC.PanelPane
  @since SproutCore 1.0
*/
SC.AlertPane = SC.PanelPane.extend({
  
  classNames: 'sc-alert',
  
  /**
    The delegate to notify when the pane is dismissed.  If you set a 
    delegate, it should respond to the method:
    
    {{{
      alertPaneDidDismiss: function(pane, status)
    }}}
    
    The status will be on of SC.BUTTON1_STATUS, SC.BUTTON2_STATUS or SC.BUTTON3_STATUS
    depending on which button was clicked.
    
    @property {Object}
  */
  delegate: null,

  /**
    The icon URL or class name.  If you do not set this, an alert icon will
    be shown instead.
    
    @property {String}
  */
  icon: 'sc-icon-alert-48',

  /**
    The primary message to display.  This message will appear in large bold
    type at the top of the alert.
    
    @property {String}
  */
  message: "",

  /**
    An optional detailed decription.  Use this string to provide further 
    explanation of the condition and, optionally, ways the user can resolve
    the problem.
    
    @property {String}
  */
  description: "",
  
  displayDescription: function() {
    var desc = this.get('description');
    if (!desc || desc.length === 0) return desc ;
    
    desc = SC.RenderContext.escapeHTML(desc); // remove HTML
    return '<p class="description">' + desc.split('\n').join('</p><p class="description">') + '</p>';
  }.property('description').cacheable(),

  /**
    An optional detailed caption.  Use this string to provide further 
    fine print explanation of the condition and, optionally, ways the user can resolve
    the problem.
    
    @property {String}
  */
  caption: "",
  
  displayCaption: function() {
    var caption = this.get('caption');
    if (!caption || caption.length === 0) return caption ;
    
    caption = SC.RenderContext.escapeHTML(caption); // remove HTML
    return '<p class="caption">' + caption.split('\n').join('</p><p class="caption">') + '</p>';
  }.property('caption').cacheable(),
  
  /**
    The button view for the button 1 (OK).
    
    @property {SC.ButtonView}
  */
  buttonOne: SC.outlet('contentView.childViews.1.childViews.1'),

  /**
    The button view for the button 2 (Cancel).
    
    @property {SC.ButtonView}
  */
  buttonTwo: SC.outlet('contentView.childViews.1.childViews.0'),

  /**
    The button view for the button 3 (Extra).
    
    @property {SC.ButtonView}
  */
  buttonThree: SC.outlet('contentView.childViews.2.childViews.0'),

  /**
    The view for the button 3 (Extra) wrapper.
    
    @property {SC.View}
  */
  buttonThreeWrapper: SC.outlet('contentView.childViews.2'),
  
  layout: { centerX: 0, width: 500, top: 55 },

  /** @private - internal view that is actually displayed */
  contentView: SC.View.extend({
    
    useStaticLayout: YES,
    
    layout: { left: 0, right: 0, top: 0, height: "auto" },
		
    childViews: [
      SC.View.extend(SC.StaticLayout, {
        classNames: ['info'],

        render: function(context, firstTime) {
          var pane = this.get('pane');
          var blank = SC.BLANK_IMAGE_URL ;
          if(pane.get('icon') == 'blank') context.addClass('plain');
          context.push('<img src="%@" class="icon %@" />'.fmt(blank, pane.get('icon')));
          context.begin('h1').text(pane.get('message') || '').end();
          context.push(pane.get('displayDescription') || '');
          context.push(pane.get('displayCaption') || '');
          context.push('<div class="separator"></div>');
        }
      }),

      SC.View.extend({
        layout: { bottom: 13, height: 24, right: 18, width: 466 },
        childViews: ['cancelButton', 'okButton'],
        classNames: ['textAlignRight'],
        cancelButton : SC.ButtonView.extend({
            useStaticLayout: YES,
            actionKey: SC.BUTTON2_STATUS,
            localize: YES,
            titleMinWidth: 64,
            layout: { right: 5, height: 'auto', width: 'auto', bottom: 0 },
            theme: 'capsule',
            title: "Cancel", 
            action: "dismiss",
            isVisible: NO
          }),

        okButton : SC.ButtonView.extend({
            useStaticLayout: YES,
            actionKey: SC.BUTTON1_STATUS,
            localize: YES,
            titleMinWidth: 64,
            layout: { left: 0, height: 'auto', width: 'auto', bottom: 0 },
            theme: 'capsule',
            title: "OK", 
            isDefault: YES,
            action: "dismiss"
          })
      }),
      
      SC.View.extend({
        layout: { bottom: 13, height: 24, left: 18, width: 150 },
        isVisible: NO,
        childViews: [
          SC.ButtonView.extend({
            useStaticLayout: YES,
            actionKey: SC.BUTTON3_STATUS,
            localize: YES,
            titleMinWidth: 64,
            layout: { left: 0, height: 'auto', width: 'auto', bottom: 0 },
            theme: 'capsule',
            title: "Extra", 
            action: "dismiss",
            isVisible: NO
          })]
      })]
  }),

  /**
    Action triggered whenever any button is pressed.  Notifies any delegate
    and then hides the alert pane.
  */
  dismiss: function(sender) {
    var del = this.delegate;
    if (del && del.alertPaneDidDismiss) {
      del.alertPaneDidDismiss(this, sender.get('actionKey'));
    }
    this.remove(); // hide alert
  },
  
  /** @private 
    Executes whenever one of the icon, message, description or caption is changed.
    This simply causes the UI to refresh.
  */
  alertInfoDidChange: function() {
    var v = this.getPath('contentView.childViews.0');
    if (v) v.displayDidChange(); // re-render message
  }.observes('icon', 'message', 'displayDescription', 'displayCaption')
});

/** @private
  internal method normalizes arguments for processing by helper methods.
*/
SC.AlertPane._normalizeArguments = function(args) {
  args = SC.A(args); // convert to real array
  var len = args.length, delegate = args[len-1];
  if (SC.typeOf(delegate) !== SC.T_STRING) {
    args[len-1] = null;
  } else delegate = null ;
  args[7] = delegate ;
  return args ;
};

/**
  Displays a new alert pane according to the passed parameters.  Every 
  parameter except for the message is optional.  You can always pass the 
  delegate as the last parameter and it will be used, even if you omit items
  in between.
  
  If you need to pass other parameters but you want to omit some others 
  in between, pass null and the related UI item will be hidden
  
  Note that if you pass an icon, it should be 48 x 48 in size.
  
  @param {String} message the primary message
  @param {String} description an optional detailed description
  @param {String} caption an optional detailed fine print caption
  @param {String} button1Title optional unlocalized title for button 1 (OK)
  @param {String} button2Title optional unlocalized title for button 2 (Cancel)
  @param {String} button3Title optional unlocalized title for button 3 (extra)
  @param {String} iconUrl optional URL or class name for icon.
  @param {Object} delegate optional delegate to notify when pane is dismissed
  @returns {SC.AlertPane} new alert pane
*/
SC.AlertPane.show = function(message, description, caption, button1Title, button2Title, button3Title, iconUrl, delegate) {
  
  // get the delegate and normalize the rest of the params
  var args = this._normalizeArguments(arguments);
  
  // create basic AlertPane
  var ret = this.create({
    message: args[0] || '',
    description: args[1] || null,
    caption: args[2] || null,
    icon: args[6] || 'sc-icon-alert-48',
    delegate: args[7]
  });
  
  // customize buttons as needed
  var buttonKeys = 'buttonOne buttonTwo buttonThree'.w(), button, title;
  for(var idx=0;idx<3;idx++) {
    button = ret.get(buttonKeys[idx]);
    title = args[idx + 3];
    if (title) {
      button.set('title', title).set('isVisible', YES);
      if (idx==2) {
        var button_wrapper = ret.get('buttonThreeWrapper');
        button_wrapper.set('isVisible', YES);
      }
    }
  }
  var show = ret.append() ; // make visible.
  ret.adjust('height', ret.childViews[0].$().height()) ;
  ret.updateLayout() ;
  return show ;
};

/**
  Displays a warning alert pane.  See SC.AlertPane.show() for complete details. 
  
  @returns {SC.AlertPane} the pane
*/
SC.AlertPane.warn = function(message, description, caption, button1Title, button2Title, button3Title, delegate) {
  var args = this._normalizeArguments(arguments);
  args[6] = 'sc-icon-alert-48';
  return this.show.apply(this, args);
};


/**
  Displays a info alert pane.  See SC.AlertPane.show() for complete details. 
  
  @returns {SC.AlertPane} the pane
*/
SC.AlertPane.info = function(message, description, caption, button1Title, button2Title, button3Title, delegate) {
  var args = this._normalizeArguments(arguments);
  args[6] = 'sc-icon-info-48';
  return this.show.apply(this, args);
};

/**
  Displays a error allert pane.  See SC.AlertPane.show() for complete details. 
  
  @returns {SC.AlertPane} the pane
*/
SC.AlertPane.error = function(message, description, caption, button1Title, button2Title, button3Title, delegate) {
  var args = this._normalizeArguments(arguments);
  args[6] = 'sc-icon-error-48';
  return this.show.apply(this, args);
};

/**
  Displays a plain all-text allert pane w/o icon.  See SC.AlertPane.show() for complete details. 
  
  @returns {SC.AlertPane} the pane
*/
SC.AlertPane.plain = function(message, description, caption, button1Title, button2Title, button3Title, delegate) {
  var args = this._normalizeArguments(arguments);
  args[6] = 'blank';
  return this.show.apply(this, args);
};

/* >>>>>>>>>> BEGIN source/panes/palette.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            portions copyright @2009 Apple Inc.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('panes/panel');

/**
  Displays a non-modal, default positioned, drag&drop-able palette pane.

  The default way to use the palette pane is to simply add it to your page like this:
  
  {{{
    SC.PalettePane.create({
      layout: { width: 400, height: 200, right: 0, top: 0 },
      contentView: SC.View.extend({
      })
    }).append();
  }}}
  
  This will cause your palette pane to display.
  
  Palette pane is a simple way to provide non-modal messaging that won't 
  blocks the user's interaction with your application.  Palette panes are 
  useful for showing important detail informations with flexsible position.
  They provide a better user experience than modal panel.
  
  @extends SC.PanelPane
  @since SproutCore 1.0
*/
SC.PalettePane = SC.PanelPane.extend({
  
  classNames: 'sc-palette',
  
  /** Palettes are not modal by default */
  isModal: NO,
  
  /** Do not show smoke behind palettes */
  modalPane: SC.ModalPane,
  
  isAnchored: NO,
  
  _mouseOffsetX: null,
  _mouseOffsetY: null,

  /** @private - drag&drop palette to new position. */
  mouseDown: function(evt) {
    var f=this.get('frame');
    this._mouseOffsetX = f ? (f.x - evt.pageX) : 0;
    this._mouseOffsetY = f ? (f.y - evt.pageY) : 0;
    return YES;
  },

  mouseDragged: function(evt) {
    if(!this.isAnchored) {
      this.set('layout', { width: this.layout.width, height: this.layout.height, left: this._mouseOffsetX + evt.pageX, top: this._mouseOffsetY + evt.pageY });
      this.updateLayout();
    }
    return YES;
  }
  
 
});
/* >>>>>>>>>> BEGIN source/panes/picker.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            portions copyright @2009 Apple Inc.
// License:   Licened under MIT license (see license.js)
// ==========================================================================


sc_require('panes/palette');

/** 
  Popular customized picker position rules:
  default: initiated just below the anchor. 
           shift x, y to optimized picker visibility and make sure top-left corner is always visible.
  menu :   same as default rule +
           default(1,4,3) or custom offset below the anchor for default location to fine tunned visual alignment +
           enforce min left(7px)/right(8px) padding to the window
  fixed :  default(1,4,3) or custom offset below the anchor for default location to cope with specific anchor and skip fitPositionToScreen
  pointer :take default [0,1,2,3,2] or custom matrix to choose one of four perfect pointer positions.Ex:
           perfect right (0) > perfect left (1) > perfect top (2) > perfect bottom (3)
           fallback to perfect top (2)
*/
SC.PICKER_MENU = 'menu';
SC.PICKER_FIXED = 'fixed';
SC.PICKER_POINTER = 'pointer';
/** 
  Pointer layout for perfect right/left/top/bottom
*/
SC.POINTER_LAYOUT = ["perfectRight", "perfectLeft", "perfectTop", "perfectBottom"];

/**
  Displays a non-modal, self anchor positioned picker pane.

  The default way to use the picker pane is to simply add it to your page like this:
  
  {{{
    SC.PickerPane.create({
      layout: { width: 400, height: 200 },
      contentView: SC.View.extend({
      })
    }).popup(anchor);
  }}}
  
  This will cause your picker pane to display.
  
  Picker pane is a simple way to provide non-modal messaging that won't 
  blocks the user's interaction with your application.  Picker panes are 
  useful for showing important detail informations with optimized position around anchor.
  They provide a better user experience than modal panel.

  Examples for applying popular customized picker position rules:
  
  1. default:   
  {{{
    SC.PickerPane.create({layout: { width: 400, height: 200 },contentView: SC.View.extend({})
    }).popup(anchor);
  }}}

  2. menu below the anchor with default offset matrix [1,4,3]:   
  {{{
    SC.PickerPane.create({layout: { width: 400, height: 200 },contentView: SC.View.extend({})
    }).popup(anchor, SC.PICKER_MENU);
  }}}

  3. menu on the right side of anchor with custom offset matrix [2,6,0]:   
  {{{
    SC.PickerPane.create({layout: { width: 400, height: 200 },contentView: SC.View.extend({})
    }).popup(anchor, SC.PICKER_MENU, [2,6,0]);
  }}}

  4. fixed below the anchor with default offset matrix [1,4,3]:   
  {{{
    SC.PickerPane.create({layout: { width: 400, height: 200 },contentView: SC.View.extend({})
    }).popup(anchor, SC.PICKER_FIXED);
  }}}

  5. fixed on the right side of anchor with custom offset matrix [-22,-17,0]:   
  {{{
    SC.PickerPane.create({layout: { width: 400, height: 200 },contentView: SC.View.extend({})
    }).popup(anchor, SC.PICKER_FIXED, [-22,-17,0]);
  }}}

  6. pointer with default position pref matrix [0,1,2,3,2]:   
  {{{
    SC.PickerPane.create({layout: { width: 400, height: 200 },contentView: SC.View.extend({})
    }).popup(anchor, SC.PICKER_POINTER);
  }}}
  perfect right (0) > perfect left (1) > perfect top (2) > perfect bottom (3)
  fallback to perfect top (2)

  7. pointer with custom position pref matrix [3,0,1,2,2]:   
  {{{
    SC.PickerPane.create({layout: { width: 400, height: 200 },contentView: SC.View.extend({})
    }).popup(anchor, SC.PICKER_POINTER, [3,0,1,2,2]);
  }}}

  perfect bottom (3) > perfect right (0) > perfect left (1) > perfect top (2)
  fallback to perfect top (2)
  
  @extends SC.PalettePane
  @since SproutCore 1.0
*/
SC.PickerPane = SC.PalettePane.extend({
  
  classNames: 'sc-picker',
  isAnchored: YES,
  
  isModal: YES,
  
  pointerPos: 'perfectRight',
  pointerPosX: 0,
  pointerPosY: 0,
  
  /**
    This property will be set to the element (or view.get('layer')) that 
    triggered your picker to show.  You can use this to properly position your 
    picker.
    
    @property {Object}
  */
  anchorElement: null,
  
  /**
    popular customized picker position rule
    
    @property {String}
  */
  preferType: null,
  
  /**
    default/custom offset or position pref matrix for specific preferType
    
    @property {String}
  */
  preferMatrix: null,

  /**
    Displays a new picker pane according to the passed parameters.
    Every parameter except for the anchorViewOrElement is optional.
  
    @param {Object} anchorViewOrElement view or element to anchor to
    @param {String} preferType optional apply picker position rule
    @param {Array} preferMatrix optional apply custom offset or position pref matrix for specific preferType
    @returns {SC.PickerPane} receiver
  */
  popup: function(anchorViewOrElement, preferType, preferMatrix) {
    var anchor = anchorViewOrElement.isView ? anchorViewOrElement.get('layer') : anchorViewOrElement;
    this.beginPropertyChanges();
    this.set('anchorElement',anchor) ;
    if (preferType) this.set('preferType',preferType) ;
    if (preferMatrix) this.set('preferMatrix',preferMatrix) ;
    this.endPropertyChanges();

    this.positionPane();
    this.append();
  },

  /** @private
    The ideal position for a picker pane is just below the anchor that 
    triggered it + offset of specific preferType. Find that ideal position, 
    then call fitPositionToScreen to get final position. If anchor is missing, 
    fallback to center.
  */  
  positionPane: function() {
    var anchor       = this.get('anchorElement'),
        preferType   = this.get('preferType'),
        preferMatrix = this.get('preferMatrix'),
        layout       = this.get('layout'),
        origin ;
    
    // usually an anchorElement will be passed.  The ideal position is just 
    // below the anchor + default or custom offset according to preferType.
    // If that is not possible, fitPositionToScreen will take care of that for 
    // other alternative and fallback position.
    if (anchor) {
      anchor = this.computeAnchorRect(anchor);
      origin = SC.cloneRect(anchor);

      if (preferType) {
        switch (preferType) {
          case SC.PICKER_MENU:
          case SC.PICKER_FIXED:
            if(!preferMatrix || preferMatrix.length != 3) {
              // default below the anchor with fine tunned visual alignment 
              // for Menu to appear just below the anchorElement.
              this.set('preferMatrix', [1, 4, 3]) ;
            }

            // fine tunned visual alignment from preferMatrix
            origin.x += ((this.preferMatrix[2]===0) ? origin.width : 0) + this.preferMatrix[0] ;
            origin.y += ((this.preferMatrix[2]===3) ? origin.height : 0) + this.preferMatrix[1];    
            break;
          default:
            origin.y += origin.height ;
            break;
        }   
      } else {
        origin.y += origin.height ;
      }
      origin = this.fitPositionToScreen(origin, this.get('frame'), anchor) ;
      layout = { width: origin.width, height: origin.height, left: origin.x, top: origin.y };

    // if no anchor view has been set for some reason, just center.
    } else {
      layout = { width: layout.width, height: layout.height, centerX: 0, centerY: 0 };
    }
    this.set('layout', layout).updateLayout();
    return this ;
  },

  /** @private
    This method will return ret (x, y, width, height) from a rectangular element
  */  
  computeAnchorRect: function(anchor) {
    var ret = SC.viewportOffset(anchor); // get x & y
    var cq = SC.$(anchor);
    ret.width = cq.outerWidth();
    ret.height = cq.outerHeight();
    return ret ;
  },

  /** @private
    This method will dispatch to the right re-position rule according to preferType
  */  
  fitPositionToScreen: function(preferredPosition, picker, anchor) {
    // get window rect.
    var wsize = this.get('currentWindowSize') || SC.RootResponder.responder.computeWindowSize() ;
    var wret = { x: 0, y: 0, width: wsize.width, height: wsize.height } ;
    picker.x = preferredPosition.x ; picker.y = preferredPosition.y ;

    if(this.preferType) {
      switch(this.preferType) {
        case SC.PICKER_MENU:
          // apply default + menu re-position rule
          picker = this.fitPositionToScreenDefault(wret, picker, anchor) ;
          picker = this.fitPositionToScreenMenu(wret, picker) ;
          break;
        case SC.PICKER_POINTER:
          // apply pointer re-position rule
          picker = this.fitPositionToScreenPointer(wret, picker, anchor) ;
          break;
          
        case SC.PICKER_FIXED:
          // skip fitPositionToScreen
          break;
        default:
          break;
      }     
    } else {
      // apply default re-position rule
      picker = this.fitPositionToScreenDefault(wret, picker, anchor) ;
    }
    this.displayDidChange();
    return picker ;
  },

  /** @private
    re-position rule migrated from old SC.OverlayPaneView. 
    shift x, y to optimized picker visibility and make sure top-left corner is always visible.
  */
  fitPositionToScreenDefault: function(w, f, a) {
    // 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) > w.width) {
      var mx = Math.max(SC.maxX(a), f.width) ;
      f.x = Math.min(mx, w.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(a,0)) ;
      if (SC.maxX(f) > w.width) {
        f.x = Math.max(0, w.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) > w.height) {
      mx = Math.max((a.y - f.height), 0) ;
      if (mx > w.height) {
        f.y = Math.max(0, w.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) {
      mx = Math.min(SC.maxY(a), (w.height - a.height)) ;
      f.y = Math.max(mx, 0) ;
    }
    return f ;    
  },

  /** @private
    re-position rule optimized for Menu to enforce min left(7px)/right(8px) padding to the window
  */
  fitPositionToScreenMenu: function(w, f) {
    // min left/right padding to the window
    if( (f.x + f.width) > (w.width-9) ) f.x = w.width - f.width - 9;
    if( f.x < 7 ) f.x = 7;
	
	// if the height of the menu is bigger than the window height resize it.
	  if( f.height > w.height){
		  f.y = 15;
		  f.height = w.height - 35;
	  }
	
	  return f ;    
  },

  /** @private
    re-position rule for triangle pointer picker: take default [0,1,2,3,2] or custom matrix to choose one of four perfect pointer positions.
  */
  fitPositionToScreenPointer: function(w, f, a) {
    // initiate perfect positions matrix
    // 4 perfect positions: right > left > top > bottom
    // 2 coordinates: x, y
    // top-left corner of 4 perfect positioned f  (4x2)
    var overlapTunningX = (a.height > 12) ? 0 : 1;
    var overlapTunningY = (a.height > 12) ? 0 : 3;

    var prefP1    =[[a.x+a.width+(7+overlapTunningX), a.y+parseInt(a.height/2,0)-40], 
                    [a.x-f.width-(7+overlapTunningX),  a.y+parseInt(a.height/2,0)-40], 
                    [a.x+parseInt(a.width/2,0)-parseInt(f.width/2,0), a.y-f.height-(17+overlapTunningY)],
                    [a.x+parseInt(a.width/2,0)-parseInt(f.width/2,0), a.y+a.height+(17+overlapTunningY)]];
    // bottom-right corner of 4 perfect positioned f  (4x2)
    var prefP2    =[[a.x+a.width+f.width+(7+overlapTunningX), a.y+parseInt(a.height/2,0)+f.height-24], 
                    [a.x-(7+overlapTunningX),                  a.y+parseInt(a.height/2,0)+f.height-24], 
                    [a.x+parseInt(a.width/2,0)-parseInt(f.width/2,0)+f.width, a.y-(17+overlapTunningY)],
                    [a.x+parseInt(a.width/2,0)-parseInt(f.width/2,0)+f.width, a.y+a.height+f.height+(17+overlapTunningY)]];
    // cutoff of 4 perfect positioned f: top, right, bottom, left  (4x4)
    var cutoffPrefP =[[prefP1[0][1]>0 ? 0 : 0-prefP1[0][1], prefP2[0][0]<w.width ? 0 : prefP2[0][0]-w.width, prefP2[0][1]<w.height ? 0 : prefP2[0][1]-w.height, prefP1[0][0]>0 ? 0 : 0-prefP1[0][0]], 
                      [prefP1[1][1]>0 ? 0 : 0-prefP1[1][1], prefP2[1][0]<w.width ? 0 : prefP2[1][0]-w.width, prefP2[1][1]<w.height ? 0 : prefP2[1][1]-w.height, prefP1[1][0]>0 ? 0 : 0-prefP1[1][0]],
                      [prefP1[2][1]>0 ? 0 : 0-prefP1[2][1], prefP2[2][0]<w.width ? 0 : prefP2[2][0]-w.width, prefP2[2][1]<w.height ? 0 : prefP2[2][1]-w.height, prefP1[2][0]>0 ? 0 : 0-prefP1[2][0]],
                      [prefP1[3][1]>0 ? 0 : 0-prefP1[3][1], prefP2[3][0]<w.width ? 0 : prefP2[3][0]-w.width, prefP2[3][1]<w.height ? 0 : prefP2[3][1]-w.height, prefP1[3][0]>0 ? 0 : 0-prefP1[3][0]]];

    if(!this.preferMatrix || this.preferMatrix.length != 5) {
      // default re-position rule : perfect right (0) > perfect left (1) > perfect top (2) > perfect bottom (3)
      // fallback to perfect top (2)
      this.set('preferMatrix', [0,1,2,3,2]) ;
    }
    var m = this.preferMatrix;
    //var pointer = this.contentView.childViews[this.contentView.childViews.length-1];

    // initiated with fallback position
    // Will be used only if the following preferred alternative can not be found
    if(m[4] === -1) {
      //f.x = a.x>0 ? a.x+23 : 0; // another alternative align to left
      f.x = a.x+parseInt(a.width/2,0);
      f.y = a.y+parseInt(a.height/2,0)-parseInt(f.height/2,0);
      this.set('pointerPos', SC.POINTER_LAYOUT[0]+' fallback');
      this.set('pointerPosY', parseInt(f.height/2,0)-40);      
    } else {
      f.x = prefP1[m[4]][0];
      f.y = prefP1[m[4]][1];
      this.set('pointerPos', SC.POINTER_LAYOUT[m[4]]);
      this.set('pointerPosY', 0);      
    }
    this.set('pointerPosX', 0);

    for(var i=0; i<SC.POINTER_LAYOUT.length; i++) {
      if (cutoffPrefP[m[i]][0]===0 && cutoffPrefP[m[i]][1]===0 && cutoffPrefP[m[i]][2]===0 && cutoffPrefP[m[i]][3]===0) {
        // alternative i in preferMatrix by priority
        if (m[4] != m[i]) {
          f.x = prefP1[m[i]][0] ;
          f.y = prefP1[m[i]][1] ;
          this.set('pointerPosY', 0);
          this.set('pointerPos', SC.POINTER_LAYOUT[m[i]]);
        }
        i = SC.POINTER_LAYOUT.length;
      } else if ((m[i] === 0 || m[i] === 1) && cutoffPrefP[m[i]][0]===0 && cutoffPrefP[m[i]][1]===0 && cutoffPrefP[m[i]][2] < f.height-91 && cutoffPrefP[m[i]][3]===0) {
        if (m[4] != m[i]) {
          f.x = prefP1[m[i]][0] ;
          this.set('pointerPos', SC.POINTER_LAYOUT[m[i]]);
        }
        f.y = prefP1[m[i]][1] - cutoffPrefP[m[i]][2];
        this.set('pointerPosY', cutoffPrefP[m[i]][2]);
        i = SC.POINTER_LAYOUT.length;
      } else if ((m[i] === 0 || m[i] === 1) && cutoffPrefP[m[i]][0]===0 && cutoffPrefP[m[i]][1]===0 && cutoffPrefP[m[i]][2] <= f.height-57 && cutoffPrefP[m[i]][3]===0) {
        if (m[4] != m[i]) {
          f.x = prefP1[m[i]][0] ;
        }
        f.y = prefP1[m[i]][1] - (f.height-57) ;
        this.set('pointerPosY', (f.height-59));
        this.set('pointerPos', SC.POINTER_LAYOUT[m[i]]+' extra-low');
        i = SC.POINTER_LAYOUT.length;
      }
    }
    return f ;    
  },
  
  displayProperties: ["pointerPosY"],

  render: function(context, firstTime) {
    var ret = arguments.callee.base.apply(this,arguments);
    if (context.needsContent) {
      if (this.get('preferType') == SC.PICKER_POINTER) {
        context.push('<div class="sc-pointer %@" style="margin-top: %@px"></div>'.fmt(this.get('pointerPos'), this.get('pointerPosY')));
      }
    } else {
      var el = this.$('.sc-pointer');
      el.attr('class', "sc-pointer %@".fmt(this.get('pointerPos')));
      el.attr('style', "margin-top: %@px".fmt(this.get('pointerPosY')));
    }
    return ret ;
  },
  

  /** @private - click away picker. */
  modalPaneDidClick: function(evt) {
    var f = this.get("frame");
    if(!this.clickInside(f, evt)) this.remove();
    return YES ; 
  },

  mouseDown: function(evt) {
    return this.modalPaneDidClick(evt);
  },
  
  /** @private
    internal method to define the range for clicking inside so the picker 
    won't be clicked away default is the range of contentView frame. 
    Over-write for adjustments. ex: shadow
  */
  clickInside: function(frame, evt) {
    return SC.pointInRect({ x: evt.pageX, y: evt.pageY }, frame);
  },

  /** 
    Invoked by the root responder. Re-position picker whenever the window resizes. 
  */
  windowSizeDidChange: function(oldSize, newSize) {
    arguments.callee.base.apply(this,arguments);
    this.positionPane();
  }

});


/* >>>>>>>>>> BEGIN source/views/separator.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

/**
  @class

  Displays a horizontal or vertical separator line.  Simply create one of 
  these views and configure the layout direction and layout frame.
  
  @extends SC.View
  @since SproutCore 1.0
*/
SC.SeparatorView = SC.View.extend(
/** @scope SC.SeparatorView.prototype */ {

  classNames: ['sc-separator-view'],
  tagName: 'span',

  /** 
    Select the direction of the separator line.  Must be one of SC.LAYOUT_VERTICAL or SC.LAYOUT_HORIZONTAL.
    
    @property {String}
  */
  layoutDirection: SC.LAYOUT_HORIZONTAL,

  render: function(context, firstTime) {
    if(firstTime) context.push('<span></span>');
	  context.addClass(this.get('layoutDirection'));
  }



});

/* >>>>>>>>>> BEGIN source/views/menu_item.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================
sc_require('views/button') ;
sc_require('views/separator') ;

// Constants
SC.BENCHMARK_MENU_ITEM_RENDER = YES ;

/**
  @class SC.MenuItemView
  @extends SC.ButtonView
  @since SproutCore 1.0
*/
SC.MenuItemView = SC.ButtonView.extend( SC.ContentDisplay,
/** @scope SC.MenuItemView.prototype */{
  
  
  classNames: ['sc-menu-item'],
  tagName: 'div',

  /**
    This provides the parentPane for the current MenuItemView
  */
  parentPane: null,
  
  /** 
    @private
    @property
    @type {Boolean}
  */
  acceptsFirstResponder: YES,

  // ..........................................................
  // KEY PROPERTIES
  // 
  /**
    The content object the menu view will display.

    @type Object
  */
  content: null,
  
  /**
    This returns true if the child view is a menu list view.
    This property can be over written to have other child views as well.

    @type Boolean
  */
  isSubMenuViewVisible: null,
  
  /**
    This will return true if the menu item is a separator.

    @type Boolean
  */
  isSeparator: NO,

  /**
    (displayDelegate) The name of the property used for label itself   
    If null, then the content object itself will be used.
    
    @readOnly
    @type String
  */
  contentValueKey: null,

  /**
    (displayDelegate) The name of the property used to determine if the menu 
    item is a branch or leaf (i.e. if the branch arow should be displayed to 
    the right edge.)   
    If this is null, then the branch arrow will be collapsed.

    @readOnly
    @type String
  */
  contentIsBranchKey: null,

  /**
    The name of the property which will set the image for the short cut keys

    @readOnly
    @type String
  */
  shortCutKey: null,

  /**
    The name of the property which will set the icon image for the menu item.

    @readOnly
    @type String
  */
  contentIconKey: null,

  /**
    The name of the property which will set the checkbox image for the menu 
    item.

    @readOnly
    @type String
  */
  contentCheckboxKey: 'checkbox',

  /**
    The name of the property which will set the checkbox image for the menu 
    item.

    @readOnly
    @type String
  */
  contentActionKey: null,
  
  
  /**
    Describes the width of the menu item    
    Default it to 100

    @type Integer
  */
  itemWidth: 100,
  
  /**
    Describes the height of the menu item    
    Default it to 20

    @type Integer
  */
  itemHeight: 20,
  

  /**
    Sub Menu Items 
    If this is null then there is no branching

    @type MenuPane
  */
  subMenu: null,
  
  /**
    This property specifies whether this menu item is currently in focus

    @type Boolean
  */
  hasMouseExited: NO,
  
  /**
    Anchor for the Parent Menu of which the Menu Item is part of

    @type ButtonView/MenuItemView
  */
  anchor: null,
  
  /**
    This will hold the properties that can trigger a change in the diplay
  */
  displayProperties: ['contentValueKey', 'contentIconKey', 'shortCutKey',
                  'contentIsBranchKey', 'itemHeight',
                   'subMenu','isEnabled','content'],
  contentDisplayProperties: 'title value icon separator action checkbox shortcut branchItem subMenu'.w(),
  /**
    Fills the passed html-array with strings that can be joined to form the
    innerHTML of the receiver element.  Also populates an array of classNames
    to set on the outer element.
    
    @param {SC.RenderContext} context
    @param {Boolean} firstTime
    @returns {void}
  */
  render: function(context, firstTime) {
    var bkey ;
    if (SC.BENCHMARK_MENU_ITEM_RENDER) {
      bkey = '%@.render'.fmt(this) ;
      SC.Benchmark.start(bkey) ;
    }
    var content = this.get('content') ;
    var del = this.displayDelegate ;
    var key, val ;
    var ic ;
    var menu = this.parentMenu() ;
    var itemWidth = this.get('itemWidth') || menu.layout.width ;
    var itemHeight = this.get('itemHeight') || 20 ;
    this.set('itemWidth',itemWidth) ;
    this.set('itemHeight',itemHeight) ;
    
    if(!this.get('isEnabled')) context.addClass('disabled') ;
    //handle separator    
    ic = context.begin('a').attr('href', 'javascript: ;') ;   
    key = this.getDelegateProperty('isSeparatorKey', del) ;
    val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
    if (val) {
      ic.push("<span class='separator'></span>") ;
      context.addClass('disabled') ;
    } else {
      // handle checkbox
      key = this.getDelegateProperty('contentCheckboxKey', del) ;
      if (key) {
        val = content ? (content.get ? content.get(key) : content[key]) : NO ;
        if (val) {
          ic.begin('div').addClass('checkbox').end() ;
        }
      }

      // handle image -- always invoke
      key = this.getDelegateProperty('contentIconKey', del) ;
      val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
      if(val && SC.typeOf(val) !== SC.T_STRING) val = val.toString() ;
      if(val) {
        this.renderImage(ic, val) ;
        ic.addClass('hasIcon') ;
      }

      // handle label -- always invoke
      key = this.getDelegateProperty('contentValueKey', del) ;
      val = (key && content) ? (content.get ? content.get(key) : content[key]) : content ;
      if (val && SC.typeOf(val) !== SC.T_STRING) val = val.toString() ;
      this.renderLabel(ic, val||'') ;

      // handle branch
      key = this.getDelegateProperty('contentIsBranchKey', del) ;
      val = (key && content) ? (content.get ? content.get(key) : content[key]) : NO ;
      if (val) {       
        this.renderBranch(ic, val) ;
        ic.addClass('has-branch') ;
      } else { // handle action
        
        key = this.getDelegateProperty('action', del) ;
        val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
        if (val && isNaN(val)) this.set('action', val) ;

        key = this.getDelegateProperty('target', del) ;
        val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
        if (val && isNaN(val)) this.set('target', val) ;

        // handle short cut keys
        if (this.getDelegateProperty('shortCutKey', del)) {
          key = this.getDelegateProperty('shortCutKey', del) ;
          val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
          if (val) {
            this.renderShortcut(ic, val) ;
            ic.addClass('shortcutkey') ;
          }
        }
      }
    }
    ic.end() ;
    if (SC.BENCHMARK_MENU_ITEM_RENDER) SC.Benchmark.end(bkey) ;
  },
      
  /** 
   Generates the image used to represent the image icon. override this to 
   return your own custom HTML
 
   @param {SC.RenderContext} context the render context
   @param {String} the source path of the image
   @returns {void}
  */
  renderImage: function(context, image) {
    // get a class name and url to include if relevant

    var url, className ;
    if (image && SC.ImageView.valueIsUrl(image)) {
      url = image ;
      className = '' ;
    } else {
      className = image ;
      url = SC.BLANK_IMAGE_URL; 
    }
    // generate the img element...
    context.begin('img').addClass('image').addClass(className).attr('src', url).end() ;
  },

  /** 
   Generates the label used to represent the menu item. override this to 
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {String} menu item name
   @returns {void}
  */

  renderLabel: function(context, label) {
    context.push("<span class='value ellipsis'>"+label+"</span>") ;
  },
  
  /** 
   Generates the string used to represent the branch arrow. override this to 
   return your own custom HTML
 
   @param {SC.RenderContext} context the render context
   @param {Boolean} hasBranch YES if the item has a branch
   @returns {void}
  */

  renderBranch: function(context, hasBranch) {

    var a = '>' ;
    var url = SC.BLANK_IMAGE_URL;
    context.push('<span class= "hasBranch">'+a+'</span>') ; 
  },

  /** 
   Generates the string used to represent the short cut keys. override this to 
   return your own custom HTML

   @param {SC.RenderContext} context the render context
   @param {String} the shortcut key string to be displayed with menu item name
   @returns {void}
  */
  renderShortcut: function(context, shortcut) {
    context.push('<span class = "shortcut">' + shortcut + '</span>') ;
  },

  /**
    This method is used to fetch the Menu Item View to which the
    Parent Menu Pane is anchored 
    to

    @param {}
    @returns MenuPane
  */
  getAnchor: function() {
    var anchor = this.get('anchor') ;
    if(anchor && anchor.kindOf && anchor.kindOf(SC.MenuItemView)) return anchor ;
    return null ;
  },
  
  isCurrent: NO,

  /**
    This method checks if the menu item is a separator.

    @param {}
    @returns Boolean
  */	  
  isSeparator: function() {
    var content = this.get('content') ;
    var del = this.displayDelegate ;
    var key = this.getDelegateProperty('isSeparatorKey', del) ;
    var val = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
    if (val) return YES ;
    return NO ;
  },
  
  /**
    Checks if a menu is a sub menu, during branching.
    
    @param {}
    @returns MenuPane
  */
  isSubMenuAMenuPane: function() {
    var content = this.get('content') ;
    var subMenu = content.get('subMenu') ;
    if(subMenu && subMenu.kindOf(SC.MenuPane)) return subMenu ;
    return NO ;  
  },
  
  
  /**
    This method will check whether the current Menu Item is still
    selected and then create a submenu accordignly.
    
    @param {}
    @returns void
  */
  branching: function() {
    if(this.get('hasMouseExited')) {
      this.set('hasMouseExited',NO) ;
      return ;
    }
      this.createSubMenu() ;
  },
  
  /**
    This method will remove the focus of the current selected menu item.

    @param {}
  */
  loseFocus: function() {
    if(!this.isSubMenuAMenuPane()) {
      this.set('hasMouseExited',YES) ;
      this.$().removeClass('focus') ;
      //this.resignFirstResponder() ;
    }
  },
  
  /**
    This method will create the sub Menu with the current Menu Item as anchor
    
    @param {}
    @returns void
  */
  createSubMenu: function() {
    var subMenu = this.isSubMenuAMenuPane() ;
    if(subMenu) {
      subMenu.set('anchor', this) ;
      subMenu.popup(this,[0,0,0]) ;
      var context = SC.RenderContext(this) ;
      context = context.begin(subMenu.get('tagName')) ;
      subMenu.prepareContext(context, YES) ;
      context = context.end() ;
      var menuItemViews = subMenu.get('menuItemViews') ;
      if(menuItemViews && menuItemViews.length>0) {
        subMenu.becomeKeyPane();
      }
    }
  },
  
  parentMenu: function() {
    return this.get('parentPane') ;
  },

  //..........................................
  //Mouse Events Handling
  //..........................................

  // to check the 'isMouseDown' property of the anchor 
  isAnchorMouseDown: NO,

  mouseUp: function(evt) {
    // SproutCore's event system will deliver the mouseUp event to the view
    // that got the mouseDown event, but for menus we want to track the mouse,
    // so we'll do our own dispatching.
    var parentMenu = this.parentMenu() ;
    if (parentMenu) {
      var selectedMenuItem = parentMenu.get('currentSelectedMenuItem') ;
      if (selectedMenuItem  &&  (this !== selectedMenuItem)) {
        return selectedMenuItem.tryToPerform('mouseUp', evt) ;
      }
    }

    if (!this.get('isEnabled')) {
      this.set('hasMouseExited',NO) ;
      return YES ;
    }
    this.set('hasMouseExited',NO) ;
    var key = this.get('contentCheckboxKey') ;
    var content = this.get('content') ;
    if (key) {
      if (content && content.get(key)) {
        content.set(key, NO) ;
      } else if( content.get(key)!== undefined ) {
        content.set(key, YES) ;
      }
      this.displayDidChange();
    }
    this._action(evt) ;
    var anchor = this.getAnchor() ;
    if(anchor) {
      anchor.mouseUp(evt) ;
    } else {
      this.resignFirstResponder() ;
    }
    this.closeParent() ;
    return YES ;
  },

  /** @private*/
  mouseDown: function(evt) {
    return YES ;
  },

  /** @private
    This has been over ridden from button view to prevent calling of render 
    method (When isActive property is changed).
    Also based on whether the menu item has a sub Branch we create a sub Menu
    
    @returns Boolean
  */
  mouseEntered: function(evt) {
    var parentMenu = this.parentMenu() ;
    this.set('hasMouseExited', NO) ;
    if(parentMenu) {
      parentMenu.becomeKeyPane() ;
      // condition check whether the anchor tag has _isMouseDown or not
      if(parentMenu.get('anchor')._isMouseDown){
        var isAnchorMouseDown = parentMenu.getPath('anchor._isMouseDown') ;
        this.set('isAnchorMouseDown', isAnchorMouseDown) ;
        if(this.get('isAnchorMouseDown')) {
          SC.Event.trigger(this.get('layer'), 'mousedown');
        }
      }
    }
    if (!this.get('isEnabled') && !this.isSeparator()) return YES ;
    var key = this.get('contentIsBranchKey') ;
    if(key) {
      var content = this.get('content') ;
      var val = (key && content) ? (content.get ? content.get(key) : content[key]) : NO ;
      if(val) this.invokeLater(this.branching(),100) ;
    }
    this.becomeFirstResponder() ;
	  return YES ;
  },

  /** @private
    Set the focus based on whether the current Menu item is selected or not.
    
    @returns Boolean
  */
  mouseExited: function(evt) {
    this.loseFocus() ;
    var parentMenu = this.parentMenu() ;
    if(parentMenu) {
      parentMenu.set('previousSelectedMenuItem', this) ;
    }
    return YES ;
  },


  /** @private
    Call the moveUp function on the parent Menu
    
    @returns Boolean
  */
  moveUp: function(sender,evt) {
    var menu = this.parentMenu() ;
    if(menu) {
      menu.moveUp(this) ;
    }
    return YES ;
  },
  
  /** @private
    Call the moveDown function on the parent Menu
    
    @returns Boolean
  */
  moveDown: function(sender,evt) {
    var menu = this.parentMenu() ;
    if(menu) {
      menu.moveDown(this) ;
    }
    return YES ;
  },
  
  /** @private
    Call the function to create a branch
    
    @returns Boolean
  */
  moveRight: function(sender,evt) {
    this.createSubMenu() ;
    return YES ;
  },
  
  /** @private*/
  keyDown: function(evt) {
    return this.interpretKeyEvents(evt) ;
  },
  
  /** @private*/
  keyUp: function(evt) {
    return YES ;
  },
  
  /** @private*/
  cancel: function(evt) {
    this.loseFocus() ;
    var menu = this.parentMenu() ;
    if (menu) menu.remove() ;
    var pane = menu.getPath('anchor.pane') ;
    if (pane) pane.becomeKeyPane() ;
    return YES ;
  },
  
  /** @private*/
  didBecomeFirstResponder: function(responder) {
    if (responder !== this) return;
    if(!this.isSeparator()) {
      this.$().addClass('focus') ;
    }
    var parentMenu = this.parentMenu() ;
    if(parentMenu) {
      parentMenu.set('currentSelectedMenuItem', this) ;
    }
  },
  
  /** @private*/
  willLoseFirstResponder: function(responder) {
    if (responder !== this) return;
    this.$().removeClass('focus') ;
    var parentMenu = this.parentMenu() ;
    if(parentMenu) {
      parentMenu.set('currentSelectedMenuItem', null) ;
      parentMenu.set('previousSelectedMenuItem', this) ;
    }
  },
  
  /** @private*/
  insertNewline: function(sender, evt) {
    this.mouseUp(evt) ;
  },

  /**
    Close the parent Menu and remove the focus of the current Selected 
    Menu Item
    
    @returns void
  */
  closeParent: function() {
    this.$().removeClass('focus') ;
    var menu = this.parentMenu() ;
    if(menu) {
      menu.remove() ;
    }
  },
  
  /** @private*/
  clickInside: function(frame, evt) {
    return SC.pointInRect({ x: evt.pageX, y: evt.pageY }, frame) ;
  }
  
}) ;

/* >>>>>>>>>> BEGIN source/panes/menu.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================
sc_require('panes/picker');
sc_require('views/menu_item');

// Constants
SC.BENCHMARK_MENU_PANE_RENDER = YES ;

/**
  @class SC.MenuPane
  @extends SC.PickerPane
  @since SproutCore 1.0
*/
SC.MenuPane = SC.PickerPane.extend( 
/** @scope SC.MenuPane.prototype */ {

  menuItemKeys: 'itemTitleKey itemValueKey itemIsEnabledKey itemIconKey itemSeparatorKey itemActionKey itemCheckboxKey itemShortCutKey itemBranchKey itemHeightKey subMenuKey itemKeyEquivalentKey itemTargetKey'.w(),
  classNames: ['sc-menu'],

  tagName: 'div',
  
  isModal: YES,

  /**
    The key that explains whether each item is Enabled. If omitted, no icons 
    will be displayed.

    @readOnly
    @type Boolean
    @default isEnabled
  */
  itemIsEnabledKey: "isEnabled",
  
  /**
    The key that contains the title for each item.  If omitted, no icons will
     be displayed.

    @readOnly
    @type String
    @default title
  */
  itemTitleKey: 'title',

  /**
    The array of items to display.  This can be a simple array of strings,
    objects or hashes.  If you pass objects or hashes, you must also set the
    various itemKey properties to tell the MenuPane how to extract the
    information it needs.

    @type String
  */ 
  items: [],

  /** 
    The key that contains the value for each item.  If omitted, no icons will
    be displayed.

    @readOnly
    @type String
    @default value
  */
  itemValueKey: 'value',

  /** 
    The key that contains the icon for each item.  If omitted, no icons will
    be displayed.

    @readOnly
    @type String
    @default icon
  */
  itemIconKey: 'icon',

  /** 
    The width for each menu item and ultimately the menu itself.

    @type String
  */
  itemWidth: null,
  
  /** 
    The default height for each menu item.

    @type String
  */
  itemHeight: 20,

  /** 
    The height of the menu and ultimately the menu itself.

    @type Integer
  */
  menuHeight: null,
  
  /** 
    The height for each menu item and ultimately the menu itself.

    @readOnly
    @type String
    @default height
  */
  itemHeightKey: 'height',
  
  /** 
    The submenu for a menu item if any.

    @readOnly
    @type String
    @default subMenu
  */
  subMenuKey: 'subMenu',

  /**
    If YES, titles will be localized before display.
  */
  localize: YES,

  /** 
    This key defined which key represents Separator.

    @readOnly
    @type Boolean
    @default separator
  */  
  itemSeparatorKey: 'separator',

  /** 
    This key is need to assign an action to the menu item.

    @readOnly
    @type String
    @default action
  */
  itemActionKey: 'action',

  /** 
    The key for setting a checkbox for the menu item.

    @readOnly
    @type String
    @default checkbox
  */
  itemCheckboxKey: 'checkbox',

  /** 
    The key for setting a branch for the menu item.

    @readOnly
    @type String
    @default branchItem
  */
  itemBranchKey: 'branchItem',
  
  /** 
    The key for setting a branch for the menu item.

    @readOnly
    @type String
    @default shortcut
  */
  itemShortCutKey: 'shortcut',
  
  /** 
    The key for setting Key Equivalent for the menu item.

    @readOnly
    @type String
    @default keyEquivalent
  */
  itemKeyEquivalentKey: 'keyEquivalent',
  
  /** 
    The key for setting Key Equivalent for the menu item.

    @readOnly
    @type String
    @default target
  */
  itemTargetKey: 'target',
  
  /** @private */
  preferType: SC.PICKER_MENU,

  /**
    Define the current Selected Menu Item.

    type SC.MenuItemView
  */
  currentSelectedMenuItem : null,

  /**
    Define the current Selected Menu Item.

    @type SC.MenuItemView
  */
  previousSelectedMenuItem : null,

  /**
    The anchor for this Menu

    @type ButtonView/MenuItemView
  */
  anchor: null,
  
  /** @private

    Array of Display Items which is produced by displayItems function
  */
  displayItemsArray: null,
  
  /**
    Set of Menu Item Views created from items array
    
    @type SC.Array
  */
  menuItemViews: [],

  /** 
    Example view which will be used to create the Menu Items
    
    @default SC.MenuItemView
    @type SC.View
  */
  exampleView: SC.MenuItemView,
  
  /**
    Control Size for the Menu Item
  */
  controlSize: SC.REGULAR_CONTROL_SIZE,
  
  /**
    Padding to add to the minHeight of the pane.
  */
  menuHeightPadding: 0,

  createChildViews: function() {
    var childViews = [] , scroll, items, cv, t;
    scroll = SC.MenuScrollView;
    
    cv = this.get('menuItemViews');
    t = SC.View.design({ 
      layout:{ top: 0, left: 0, minHeight : this.get('menuHeight') },
      classNames: 'menuContainer', 
      childViews: cv
    });
    this.set('itemWidth',this.get('layout').width || 100) ; 
    scroll = this.createChildView(scroll, { 
      borderStyle: SC.BORDER_NONE, 
      contentView: t
    });
    this.childViews = [scroll] ;
  },
  
  /**
    @private
    
    Overwrite the popup function of the pickerPane
  */
  popup: function(anchorViewOrElement, preferMatrix) {  
    var anchor = anchorViewOrElement.isView ? anchorViewOrElement.get('layer') : anchorViewOrElement;
    this.beginPropertyChanges();
    var it = this.get('displayItems');
    this.set('anchorElement',anchor) ;
    this.set('anchor',anchorViewOrElement);
    this.set('preferType',SC.PICKER_MENU) ;
    if(preferMatrix) this.set('preferMatrix',preferMatrix) ;
    
    this.endPropertyChanges();
    this.positionPane() ;
    this.append() ;
  },

  /**
    This computed property is generated from the items array

    @property
    @type {String}
  */
  displayItems: function() {
    var items = this.get('items') ,
      loc = this.get('localize') ,
      keys = null, itemType, cur ,
      ret = [], rel,
      max = items.get('length') ,
      idx, item ,
      fetchKeys = SC._menu_fetchKeys ,
      fetchItem = SC._menu_fetchItem ,
      menuHeight = this.get('menuHeightPadding') ;
    // loop through items and collect data
    for (idx = 0; idx < max; ++idx) {
      item = items.objectAt(idx) ;
      if (SC.none(item)) continue ;
      itemType = SC.typeOf(item) ;
      rel = ret.length;
      if (itemType === SC.T_STRING) {
        ret[rel] = SC.Object.create({ title: item.humanize().titleize(),   
	                        value: item, isEnabled: YES, icon: null, 
	                        isSeparator: null, action: null, isCheckbox: NO, 
	                        menuItemNumber: idx, isShortCut: NO, isBranch: NO,
	                        itemHeight: this.get('itemHeight'), subMenu: null,keyEquivalent: null,
	                        target:null });
        menuHeight = menuHeight+this.get('itemHeight') ;
      } else if (itemType !== SC.T_ARRAY) {
          if (keys === null) keys = this.menuItemKeys.map(fetchKeys, this) ;
          cur = keys.map(fetchItem, item) ;
          cur[cur.length] = idx ;
          if (!keys[0] && item.toString) cur[0] = item.toString() ;
          if (!keys[1]) cur[1] = item ;
          if (!keys[2]) cur[2] = YES ;
          if (!cur[9]) cur[9] = this.get('itemHeight') ;
          if (cur[4]) cur[9] = 9 ;
          menuHeight = menuHeight+cur[9] ;
          if (loc && cur[0]) cur[0] = cur[0].loc() ;
          ret[rel] = SC.Object.create({ title: cur[0], value: cur[1],
                                              isEnabled: cur[2], icon: cur[3], 
                                              isSeparator: cur[4]||NO , action: cur[5],
                                              isCheckbox: cur[6], isShortCut: cur[7],
                                              menuItemNumber: idx, isBranch: cur[8],
                                              itemHeight: cur[9], subMenu: cur[10], 
                                              keyEquivalent: cur[11], target: cur[12] }) ;                         
      }
    }
    this.set('menuHeight', menuHeight);
    this.set('displayItemsArray',ret);
    this.generateMenuItems(ret);
    return ret;
  }.property('items').cacheable(),

  /**
    If the items array itself changes, add/remove observer on item...
  */
  itemsDidChange: function() {
    if (this._items) {
      this._items.removeObserver('[]', this, this.itemContentDidChange) ;
    }
    this._items = this.get('items') ;
    if (this._items) {
      this._items.addObserver('[]', this, this.itemContentDidChange) ;
    }
    this.itemContentDidChange() ;
  }.observes('items'),

  /** 
    Invoked whenever the item array or an item in the array is changed.  This 
    method will reginerate the list of items.
  */
  itemContentDidChange: function() {
    this.notifyPropertyChange('displayItems') ;
  },

  // ..........................................................
  // RENDERING/DISPLAY SUPPORT
  // 
  displayProperties: ['displayItems', 'value', 'controlSize'],

  /**
    The render function which depends on the displayItems and value
  */
  render: function(context, firstTime) {
    if (SC.BENCHMARK_MENU_PANE_RENDER) {
      var bkey = '%@.render'.fmt(this) ;
      SC.Benchmark.start(bkey);
    }
    
    arguments.callee.base.apply(this,arguments);
    
    if (firstTime) {
      if(!this.get('isEnabled')) return ;
      context.addStyle('text-align', 'center') ;
    }
    
    if (SC.BENCHMARK_MENU_PANE_RENDER) SC.Benchmark.end(bkey) ;
  },

  /**
    This method is used to observe the menuHeight and set the layout accordingly
    and position the pane.
    
    @observes menuHeight
  */
  menuHeightObserver: function() {
    var height = this.layout.height ;
    var menuHeight = this.get('menuHeight') ; 
    if( height !== menuHeight) {
      this.adjust('height',menuHeight).updateLayout() ;
    }
  }.observes('menuHeight'),
  
  /**
    Actually generates the menu HTML for the display items.  This method 
    is called the first time a view is constructed and any time the display
    items change thereafter.  This will construct the HTML but will not set
    any "transient" states such as the global isEnabled property or selection.
  */
  generateMenuItems: function(items) {
    if(!this.get('isEnabled')) return ;
    var item, itemAction, menuItemNumber, itemView, itemHeight, itemWidth, 
      menuItemViews = [], len, content, idx;
    len = items.length ;
    content = SC.makeArray(items) ;
    for (idx = 0; idx < len; ++idx) {
      item = items[idx];
      itemAction = item.get('action') ;
      menuItemNumber = item.get('menuItemNumber') ;
      itemHeight = item.get('itemHeight') ;
      itemWidth = this.get('itemWidth') ;
      itemView = this.createChildView(
        this.exampleView, {
          owner : itemView,
          displayDelegate : itemView,
          parentPane: this,
          anchor : this.get('anchor'),
          isVisible : YES,
          contentValueKey : 'title',
          contentIconKey : 'icon',
          contentCheckboxKey: this.itemCheckboxKey,
          contentIsBranchKey :'branchItem',  
          isSeparatorKey : 'separator',
          shortCutKey : 'shortCut',  
          action : itemAction,
          target : item.get('target'),
          layout : { top: 0, left: 0, width: itemWidth, height: itemHeight },
          isEnabled : item.get('isEnabled'),
          itemHeight : itemHeight,
          itemWidth : itemWidth,
          keyEquivalent : item.get('keyEquivalent'),
          controlSize: this.get('controlSize'),
          content : SC.Object.create({
            title : item.get('title'),
            value : item.get('value'),
            icon : item.get('icon'),
            separator : item.get('isSeparator'),
            action : itemAction,
            checkbox : item.get('isCheckbox'),
            shortCut : item.get('isShortCut'),
            branchItem : item.get('isBranch'),
            subMenu : item.get('subMenu')
          }),
        rootElementPath : [menuItemNumber]
      });
      menuItemViews.push(itemView) ;
    }
    var contentV = this.childViews[0].contentView;
    contentV.replaceAllChildren(menuItemViews);
    contentV.adjust('minHeight', this.get('menuHeight'));
    this.set('menuItemViews',menuItemViews) ;
  },
  
  
  
  
  /**
    Observes the PreviousSelectedMenuItem and clears the submenu 
    for that item.
    
    @returns void
  */
  previousSelectedMenuItemObserver: function(){
    var previousSelectedMenuItem = this.get('previousSelectedMenuItem') ;
    if(previousSelectedMenuItem) {
      var subMenu = previousSelectedMenuItem.isSubMenuAMenuPane() ;
      if(subMenu) subMenu.remove() ;
    }
  }.observes('previousSelectedMenuItem'),
  
  /**
    This function returns whether the anchor is of type of MenuItemView
    
    @returns Boolean
  */
  isAnchorMenuItemType: function() {
    var anchor = this.get('anchor') ;
    return (anchor && anchor.kindOf && anchor.kindOf(SC.MenuItemView)) ;
  },
  
  //..........................................................
  // mouseEvents and keyBoard Events handling
  //..........................................................

  /**
    Perform actions equivalent for the keyBoard Shortcuts

    @param {String} keystring
    @param {SC.Event} evt
    @returns {Boolean}  YES if handled, NO otherwise
  */
  performKeyEquivalent: function(keyString,evt) {
    var items, len, menuItems, item, keyEquivalent, 
        action, isEnabled, target, idx;
    if(!this.get('isEnabled')) return NO ;
    this.displayItems() ;
    items = this.get('displayItemsArray') ;
    if (!items) return NO;

    // handling esc key
    if (keyString === 'escape') {
      this.remove() ;
      var pane = this.getPath('anchor.pane') ;
      if (pane) pane.becomeKeyPane() ;
    }

    len = items.length ;
    for(idx=0; idx<len; ++idx) {
      item          = items[idx] ;
      keyEquivalent = item.get('keyEquivalent') ;
      action        = item.get('action') ;
      isEnabled     = item.get('isEnabled') ;
      target        = item.get('target') || this ;
      if(keyEquivalent == keyString && isEnabled) {
        var retVal = SC.RootResponder.responder.sendAction(action,target);
        this.remove();
        return retVal;
      }
    }
    return NO ;
  },
  
  //Mouse and Key Events
  
  /** @private */
  mouseDown: function(evt) {
    return YES ;
  },
  
  /** 
    @private 
    
    Get the anchor and send the event to the anchor in case the 
    current Menu is a subMenu
  */
  mouseUp: function(evt) {
    this.remove() ;
    var anchor = this.get('anchor') ;
    if(this.isAnchorMenuItemType()) this.sendEvent('mouseUp', evt, anchor) ;
    return YES ;
  },
  
  /** 
    @private 
    
    This function gets called from the Menu Item in order to set the 
    current Selected Menu Item and move the selection
  */
  moveDown: function(menuItem) {
    var currentSelectedMenuItem = this.getNextEnabledMenuItem(menuItem) ;
    if(menuItem) menuItem.resignFirstResponder() ;
    currentSelectedMenuItem.becomeFirstResponder() ;
  },
  
  /** 
    @private 
    
    This function gets called from the Menu Item in order to set the 
    current Selected Menu Item and move the selection
  */
  moveUp: function(menuItem) {
    var currentSelectedMenuItem = this.getPreviousEnabledMenuItem(menuItem) ;
    if(menuItem) menuItem.resignFirstResponder() ;
    currentSelectedMenuItem.becomeFirstResponder() ;
    return YES ;
  },
  
  /**
    Get the previous Enabled Menu Item which is not a separator
    
    @returns MenuItemView
  */
  getPreviousEnabledMenuItem : function(menuItem) {
    var content, itemView, menuItemViews = this.get('menuItemViews') ;
    if(menuItemViews) {
      var len = menuItemViews.length ;
      var idx = (menuItemViews.indexOf(menuItem) === -1) ? 
              len : menuItemViews.indexOf(menuItem) ;
      var menuIdx = idx, isEnabled = NO, isSeparator = NO ;
      while((!isEnabled || isSeparator) && --idx !== menuIdx) {
        if(idx === -1) idx = len - 1;
        itemView = menuItemViews[idx];
        isEnabled = itemView.get('isEnabled') ;
        content = itemView.get('content') ;
        if(content) {
          isSeparator = content.get(itemView.get('isSeparatorKey'));
        }
      }
      return menuItemViews[idx];
    }
  },

  /**
    Get the next Enabled Menu Item which is not a separator
    
    @returns MenuItemView
  */
  getNextEnabledMenuItem : function(menuItem) {
    var content, itemView, menuItemViews = this.get('menuItemViews') ;
    if(menuItemViews) {
      var len = menuItemViews.length ;
      var idx = (menuItemViews.indexOf(menuItem) === -1) ? 
        0 : menuItemViews.indexOf(menuItem) ;
      var menuIdx = idx, isEnabled = NO , isSeparator = NO ;
      while((!isEnabled || isSeparator) && ++idx !== menuIdx) {
        if(idx === len) idx = 0;
        itemView = menuItemViews[idx];
        isEnabled = itemView.get('isEnabled') ;
        content = itemView.get('content') ;
        if(content) {
          isSeparator = content.get(itemView.get('isSeparatorKey'));
        }
      }
      return menuItemViews[idx] ;
    }
  },
  
  /** 
    @private - click away picker. 
    
    Override to pass the event to the parent Menu
    in case the current Menu is a subMenu
    
    @returns Boolean
  */
  modalPaneDidClick: function(evt) {
    var parentMenu, anchor, currentSelectedMenuItem, f = this.get("frame");
    currentSelectedMenuItem = this.get('currentSelectedMenuItem');
    if(currentSelectedMenuItem) {
      anchor = currentSelectedMenuItem.getAnchor();
      if(anchor) {
        parentMenu = anchor.parentMenu();
        if(parentMenu.kindOf(SC.MenuPane)) parentMenu.modalPaneDidClick(evt);
      }
    }
    if(!this.clickInside(f, evt)) {
      this.remove() ;
    }
    return YES;
  },
  
  /** 
    Get the Menu Item based on the key,value passed
    @params {String} key 
    @params {String} value 
    
    @returns SC.MenuItemView
  */
  getMenuItem: function(key,value) {
    var displayItems, menuItemViews, idx;
    displayItems = this.get('displayItemsArray') ;
    menuItemViews = this.get('menuItemViews') ;
    if(displayItems && menuItemViews) {
      idx = displayItems.get(key).indexOf(value);
      if(idx !== -1) return menuItemViews[idx];
      else return null;
    }
    else return null;
  }
  
  
});

SC._menu_fetchKeys = function(k) {
  return this.get(k) ;
};
SC._menu_fetchItem = function(k) {
  if (!k) return null ;
  return this.get ? this.get(k) : this[k] ;
};

/* >>>>>>>>>> BEGIN source/panes/select_button.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  SelectButtonView has a functionality similar to that of SelectField

  Clicking the SelectButtonView button displays a menu pane with a
  list of items. The selected item will be displayed on the button.
  User has the option of enabling checkbox for the selected menu item.

  @extends SC.ButtonView
  @version 1.0
  @author Mohammed Ashik
*/
sc_require('views/button');

SC.SelectButtonView = SC.ButtonView.extend(
/** @scope SC.SelectButtonView.prototype */ {

  /**
    An array of items that will be form the menu you want to show.

    @property
    @type {Array}
  */
  objects: [],

  /**
    Binding default for an array of objects

    @property
    @default SC.Binding.multiple()
  */
  objectsBindingDefault: SC.Binding.multiple(),

  /**
    If you set this to a non-null value, then the name shown for each
    menu item will be pulled from the object using the named property.
    if this is null, the collection objects themselves will be used.

    @property
    @type {String}
    @default: null
  */
  nameKey: null,

  /**
    If you set this to a non-null value, then the value of this key will
    be used to sort the objects.  If this is not set, then nameKey will
    be used.

    @property
    @type: {String}
    @default: null
  */
  sortKey: null,

  /**
     Set this to a non-null value to use a key from the passed set of objects
     as the value for the options popup.  If you don't set this, then the
     objects themselves will be used as the value.

     @property
     @type {String}
     @default null
  */
  valueKey: null,

  /**
     Key used to extract icons from the objects array
  */
  iconKey: null,

  /**
    If true, the empty name will be localized.

    @property
    @type {Boolean}
    @default YES
  */
  localize: YES,

  /**
    if true, it means that no sorting will occur, objects will appear
    in the same order as in the array

    @property
    @type {Boolean}
    @default YES
  */
  disableSort: YES,

  /**

    @property
    @default ['select-button']
  */
  classNames: ['select-button'],

  /**
    Menu item list

    @property
    @type:{Array}
  */
  itemList: [],

  /**
    Current selected menu item

    @property
    @default null
  */
  currentSelItem: null,

  /**
    Property to set the index of the selected menu item. This in turn
    is used to calculate the preferMatrix.

    @property
    @type {Number}
    @default null
  */
  itemIdx: null,

  /**
     Current Value of the selectButton

     @property
     @default null
  */
  value: null ,

  /**
    if this property is set to 'YES', a checbox is shown next to the
    selected menu item.

    @private
    @default YES
  */
  checkboxEnabled: YES,

  /**
    Set this property to required display positon of separtor from bottom

    @private
    @default null
  */
  separatorPostion: null,

  /**
    Default value of the select button.
     This will be the first item from the menu item list.

    @private
  */
  _defaultVal: null,

  /**
    Default title of the select button.
     This will be the title corresponding to the _defaultVal.

    @private
  */
  _defaultTitle: null,

  /**
    Default icon of the select button.
     This will be the icon corresponding to the _defaultVal.

    @private
  */
  _defaultIcon: null,

  /**
    @private

    The button theme will be popup
  */
  theme: 'popup',

  /**
    Render method gets triggered when these properties change

    @property
    @type{SC.Array}
  */
  displayProperties: ['icon', 'value','controlSize','objects'],

  /**
    Prefer matrix to position the select button menu such that the
    selected item for the menu item will appear aligned to the
    the button. The value at the second index(0) changes based on the
    postion(index) of the menu item in the menu pane.

    @property
    @type {Array}
    @default null

  */
  preferMatrix: null,

  /**
    Width of the sprite image that gets applied due to the theme.
     This has to be accounted for while calculating the actual
     width of the button

    @property
    @type {Number}
    @default 28
  */
  SELECT_BUTTON_SPRITE_WIDTH: 28,

  /**
    Property to set the menu item height. This in turn is used for
    the calculation of prefMatrix.

    @property
    @type {Number}
    @default 20
  */
  CUSTOM_MENU_ITEM_HEIGHT: 20,

  /**
    Binds the button's selection state to the menu's visibility.

    @private
  */
  isSelectedBinding: '*menu.isVisibleInWindow',

  /**
    If this property is set to 'YES', the menu pane will be positioned
    below the anchor.

    @private
    @default NO
  */
  isDefaultPosition: NO,

  /**
    lastMenuWidth is the width of the last menu which was created from
    the objects of this select button.

    @private
  */
  lastMenuWidth: null,

  /**
    customView used to draw the menu
  */
  customView: null,

  /**
    This is a property for enabling/disabling ellipsis

    @private
    @default YES
  */
  needsEllipsis: YES,

  /**
    This property allows you at add extra padding to the height
    of the menu pane.

    @default 0
    @property {Number} heightPadding for menu pane.
  */
  menuPaneHeightPadding: 0,

  /**
    Left Alignment based on the size of the button

    @private
  */
  leftAlign: function() {
    var val = 0 ;
    var controlSize = this.get('controlSize') ;
    if(controlSize === SC.SMALL_CONTROL_SIZE) val = -14 ;
    if(controlSize === SC.REGULAR_CONTROL_SIZE) val = -16 ;
    return val;
  }.property('controlSize'),

  /**
    override this method to implement your own sorting of the menu. By
    default, menu items are sorted using the value shown or the sortKey

    @param{SC.Array} objects the unsorted array of objects to display.
    @returns sorted array of objects
  */
  sortObjects: function(objects) {
    if(!this.get('disableSort')){
      var nameKey = this.get('sortKey') || this.get('nameKey') ;
      objects = objects.sort(function(a,b) {
        if (nameKey) {
          a = a.get ? a.get(nameKey) : a[nameKey] ;
          b = b.get ? b.get(nameKey) : b[nameKey] ;
        }
        return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
      }) ;
    }
    return objects ;
  },

  /**
    render method

    @private
  */
  render: function(context,firstTime) {
    arguments.callee.base.apply(this,arguments);
    var layoutWidth = this.layout.width ;
    if(firstTime && layoutWidth) {
      this.adjust({ width: layoutWidth - this.SELECT_BUTTON_SPRITE_WIDTH }) ;
    }

    var objects = this.get('objects') ;
    objects = this.sortObjects(objects) ;
    var len = objects.length ;

    //Get the namekey, iconKey and valueKey set by the user
    var nameKey = this.get('nameKey') ;
    var iconKey = this.get('iconKey') ;
    var valueKey = this.get('valueKey') ;
    var checkboxEnabled = this.get('checkboxEnabled') ;

    //get the current selected value
    var currentSelectedVal = this.get('value') ;

    // get the localization flag.
    var shouldLocalize = this.get('localize') ;

    //get the separatorPostion
    var separatorPostion = this.get('separatorPostion') ;

    //itemList array to set the menu items
    var itemList = [] ;

    //to set the 'checkbox' property of menu items
    var isChecked = YES ;

    //index for finding the first item in the list
    var idx = 0 ;

    objects.forEach(function(object) {
    if (object) {

      //Get the name value. If value key is not specified convert obj
      //to string
      var name = nameKey ? (object.get ?
        object.get(nameKey) : object[nameKey]) : object.toString() ;

      // localize name if specified.
      name = shouldLocalize? name.loc() : name ;

      //Get the icon value
      var icon = iconKey ? (object.get ?
        object.get(iconKey) : object[iconKey]) : null ;
      if (SC.none(object[iconKey])) icon = null ;

      // get the value using the valueKey or the object
      var value = (valueKey) ? (object.get ?
        object.get(valueKey) : object[valueKey]) : object ;

      if (!SC.none(currentSelectedVal) && !SC.none(value)){
        if( currentSelectedVal === value ) {
          this.set('title', name) ;
          this.set('icon', icon) ;
        }
      }

      //Check if the item is currentSelectedItem or not
      if(value === this.get('value')) {

        //set the itemIdx - To change the prefMatrix accordingly.
        this.set('itemIdx', idx) ;
        isChecked = !checkboxEnabled ? NO : YES ;
      }
      else {
        isChecked = NO ;
      }

      //Set the first item from the list as default selected item
      if (idx === 0) {
        this._defaultVal = value ;
        this._defaultTitle = name ;
        this._defaultIcon = icon ;
      }

      var item = SC.Object.create({
        title: name,
        icon: icon,
        value: value,
        isEnabled: YES,
        checkbox: isChecked,
        action: this.displaySelectedItem
      }) ;

      //Set the items in the itemList array
      itemList.push(item);

    }

    idx += 1 ;

    // display the separator if specified by the user
    if (separatorPostion && idx === (len-separatorPostion)) {
      var separator = SC.Object.create({
        separator: YES
      }) ;
      itemList.push(separator);
    }

    this.set('itemList', itemList) ;
    }, this ) ;

    if(firstTime) {
      var value = this.get('value') ;
      if(SC.none(value)) {
        this.set('value', this._defaultVal) ;
        this.set('title', this._defaultTitle) ;
        this.set('icon', this._defaultIcon) ;
      }
    }

    //Set the preference matrix for the menu pane
    this.changeSelectButtonPreferMatrix(this.itemIdx) ;

  },

  /**
    Button action handler

    @private
    @param {DOMMouseEvent} evt mouseup event that triggered the action
  */
  _action: function( evt )
  {
    var buttonLabel = this.$('.sc-button-label')[0] ;
    // Get the length of the text on the button in pixels
    var menuWidth = this.get('layer').offsetWidth ;
    var scrollWidth = buttonLabel.scrollWidth ;
    var lastMenuWidth = this.get('lastMenuWidth') ;
    if(scrollWidth) {
       // Get the original width of the label in the button
       var offsetWidth = buttonLabel.offsetWidth ;
       if(scrollWidth && offsetWidth) {
          menuWidth = menuWidth + scrollWidth - offsetWidth ;
       }
    }
    if (!lastMenuWidth || (menuWidth > lastMenuWidth)) {
      lastMenuWidth = menuWidth ;
    }

    var items = this.get('itemList') ;
    var elementOffsetWidth, largestMenuWidth ;

    for (var idx = 0; idx < items.length; ++idx) {
      //getting the width of largest menu item
      var item = items.objectAt(idx) ;
      var element = document.createElement('div') ;
      element.style.cssText = 'top:-10000px; left: -10000px;  position: absolute;' ;
      element.className = 'sc-view sc-pane sc-panel sc-palette sc-picker sc-menu select-button sc-scroll-view sc-menu-scroll-view sc-container-view menuContainer sc-button-view sc-menu-item sc-regular-size' ;
      element.innerHTML = item.title ;
      document.body.appendChild(element) ;
      elementOffsetWidth = element.offsetWidth ;

      if (!largestMenuWidth || (elementOffsetWidth > largestMenuWidth)) {
        largestMenuWidth = elementOffsetWidth ;
      }
      document.body.removeChild(element) ;
    }

    lastMenuWidth = (largestMenuWidth > lastMenuWidth) ?
                      largestMenuWidth: lastMenuWidth ;

    this.set('lastMenuWidth',lastMenuWidth) ;
    var currSel = this.get('currentSelItem') ;
    var itemList = this.get('itemList') ;
    var menuControlSize = this.get('controlSize') ;
    var menuHeightPadding = this.get('menuPaneHeightPadding') ;

    // get the user defined custom view
    var customView = this.get('customView') ;
    var customMenuView = customView ? customView : SC.MenuItemView ;

    var menu  = SC.MenuPane.create({

      /**
        Class name - select-button-item
      */
      classNames: ['select-button'],

      /**
        The menu items are set from the itemList property of SelectButton

        @property
      */
      items: itemList,

      /**
        Example view which will be used to create the Menu Items

        @default SC.MenuItemView
        @type SC.View
      */
      exampleView: customMenuView,

      /**
        This property enables all the items and makes them selectable.

        @property
      */
      isEnabled: YES,

      menuHeightPadding: menuHeightPadding,

      preferType: SC.PICKER_MENU,
      itemHeightKey: 'height',
      layout: { width: lastMenuWidth },
      controlSize: menuControlSize,
      itemWidth: lastMenuWidth,
      contentView: SC.View.extend({
      })
    }) ;

    // no menu to toggle... bail...
    if (!menu) return NO ;
    menu.popup(this, this.preferMatrix) ;
    menu.set('currentSelectedMenuItem', currSel) ;
    return YES ;
  },

  /**
     Action method for the select button menu items

  */
  displaySelectedItem: function() {
    //Get MenuPane, currentSelectedMenuItem & menuItemView
    // Get the main parent view to show the menus
    var menuView = this.parentMenu() ;
    var currSel = menuView.get('currentSelectedMenuItem') ;
    var itemViews = menuView.menuItemViews ;
    var title,val ;

    //  Fetch the index of the current selected item
    var itemIdx = 0 ;
    if (currSel && itemViews) {
      itemIdx = itemViews.indexOf(currSel) ;
    }

    // Get the select button View
    var button = menuView.get('anchor') ;

    // set the value and title
    var object = menuView.get('items') ;
    var len = object.length ;
    var found = null ;

    while (!found && (--len >= 0)) {
      title = !SC.none(object[len].title) ? object[len].title: object.toString() ;
      val =  !SC.none(object[len].value) ? object[len].value: title ;

      if (title === this.get('value') && (itemIdx === len)) {
        found = object ;
        button.set('value', val) ;
        button.set('title', title) ;
      }
    }

    // set the icon, currentSelectedItem and itemIdx
    button.set('icon', this.get('icon')).set('currentSelItem', currSel).
      set('itemIdx', itemIdx) ;
  },

  /**
     Set the "top" attribute in the prefer matrix property which will
     position menu such that the selected item in the menu will be
     place aligned to the item on the button when menu is opened.
  */
  changeSelectButtonPreferMatrix: function() {
    var preferMatrixAttributeTop = 0 ;
    var itemIdx = this.get('itemIdx') ;
    var leftAlign = this.get('leftAlign') ;
    var defPreferMatrix ;
    var tempPreferMatrix ;

    if(this.get('isDefaultPosition')) {
      defPreferMatrix = [leftAlign, 4, 3] ;
      this.set('preferMatrix', defPreferMatrix) ;
    }
    else {
      if(itemIdx) {
        preferMatrixAttributeTop = itemIdx * this.CUSTOM_MENU_ITEM_HEIGHT ;
      }
      tempPreferMatrix = [leftAlign, -preferMatrixAttributeTop, 2] ;
      this.set('preferMatrix', tempPreferMatrix) ;
    }
  },

  /**
    @private

    Holding down the button should display the menu pane.
  */
  mouseDown: function(evt) {
    if (!this.get('isEnabled')) return YES ; // handled event, but do nothing
    this.set('isActive', YES);
    this._isMouseDown = YES;
    this.becomeFirstResponder() ;
    this._action() ;
    return YES ;
  },

  /**
    @private

    Handle Key event - Down arrow key
  */
  keyDown: function(event) {
    if ( this.interpretKeyEvents(event) ) {
      return YES;
    }
    else {
      arguments.callee.base.apply(this,arguments);
    }
  },

  /**
    @private

    Pressing the Up or Down arrow key should display the menu pane
  */
  interpretKeyEvents: function(event) {
    if (event) {
      if ((event.keyCode === 38 || event.keyCode === 40)) {
        this._action() ;
      }
      else if (event.keyCode === 27) {
        this.resignFirstResponder() ;
      }
    }
    return arguments.callee.base.apply(this,arguments);
  }

}) ;


/* >>>>>>>>>> BEGIN source/panes/sheet.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            portions copyright @2009 Apple Inc.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('panes/panel');

/**
  Displays a modal sheet pane animated drop down from top.

  The default way to use the sheet pane is to simply add it to your page like this:
  
  {{{
    SC.SheetPane.create({
      layout: { width: 400, height: 200, centerX: 0 },
      contentView: SC.View.extend({
      })
    }).append();
  }}}
  
  This will cause your sheet panel to display.  The default layout for a Sheet 
  is to cover the entire document window with a semi-opaque background, and to 
  resize with the window.
  
  @extends SC.PanelPane
  @since SproutCore 1.0
*/
SC.SheetPane = SC.PanelPane.extend({
  
  classNames: 'sc-sheet',
  
  /**
    Speed of transition.  Should be expressed in msec.
  */
  transitionDuration: 200,
  
  _state: 'NO_VIEW', // no view
  
  // Local Layout for the pane...stored for later
  _openLayout: { top: 0, left: 0, bottom: 0, right: 0 },
  _anchoredLayout: { top: 0, left: 0, width: 0, height: 0 },
  
  // states for view animation
  NO_VIEW: 'NO_VIEW',
  ANIMATING: 'ANIMATING',
  READY: 'READY',
  
  SLIDE_DOWN: 'SLIDEDOWN',
  SLIDE_UP: 'SLIDEUP',
  
  BLIND_DOWN: 'BLINDDOWN',
  BLIND_UP: 'BLINDUP',

  /** @private - standard layout assigned to views at rest and empty layout when contentview disappears*/
  STANDARD_LAYOUT: { top: 0, left: 0, bottom: 0, right: 0 },
  OFFSCREEN_LAYOUT: { top: 0, height: 0},
  
  init: function(){
    arguments.callee.base.apply(this,arguments);
    
    this._openLayout = this.get('layout');
    var pframe = this.computeParentDimensions();
    this._anchoredLayout = SC.View.convertLayoutToAnchoredLayout(this._openLayout, pframe);
    this.set('layout', SC.merge(this._anchoredLayout, this.OFFSCREEN_LAYOUT));
  },
  
  replaceContent: function(newContent) {
    arguments.callee.base.apply(this,arguments);
    this._anchoredLayout = SC.View.convertLayoutToAnchoredLayout(this._openLayout, this.computeParentDimensions());
    this.set('layout', SC.merge(this._anchoredLayout, this.OFFSCREEN_LAYOUT));
    
    this.slideDown();
  },
  
  slideDown: function(){
    this.append();
    // setup other general state
    this._start   = Date.now();
    this._end     = this._start + this.get('transitionDuration');
    this._state   = this.ANIMATING;
    this._direction = this.SLIDE_DOWN;
    this.tick();
  },
  
  slideUp: function(){
    // setup other general state
    this._start   = Date.now();
    this._end     = this._start + this.get('transitionDuration');
    this._state   = this.ANIMATING;
    this._direction = this.SLIDE_UP;
    this.tick();
  },
  
  blindDown: function(){
    this.append();
    // setup other general state
    this._start   = Date.now();
    this._end     = this._start + this.get('transitionDuration');
    this._state   = this.ANIMATING;
    this._direction = this.BLIND_DOWN;
    this.tick();
  },
  
  blindUp: function(){
    // setup other general state
    this._start   = Date.now();
    this._end     = this._start + this.get('transitionDuration');
    this._state   = this.ANIMATING;
    this._direction = this.BLIND_UP;
    this.tick();
  },
  
  // Needed because of the runLoop and that it is animated...must lose focus because will break if selection is change on text fields that don't move.
  blurTo: function(pane) { this.setFirstResponder(''); },
    
  /** @private - called while the animation runs.  Will move the content view down until it is in position and then set the layout to the content layout
   */
  tick: function() {  
    this._timer = null ; // clear out
    
    var now = Date.now();
    var target = this;
    var pct = (now-this._start)/(this._end-this._start);
    var dir = this._direction, layout, newLayout, adjust;
    if (pct<0) pct = 0;
    
    // the view is no longer visible, 
    if (!this.get('isVisibleInWindow')){
      newLayout = SC.View.convertLayoutToAnchoredLayout(this._openLayout, this.computeParentDimensions());
      target.adjust(SC.merge(newLayout, this.OFFSCREEN_LAYOUT));
      this.updateLayout();
    }
    // If we are done...
    if (pct>=1) {
      if (dir === this.SLIDE_DOWN || dir == this.BLIND_DOWN){
        target.adjust(this._openLayout);
        this.awake();
      }
      else {
        newLayout = SC.View.convertLayoutToAnchoredLayout(this._openLayout, this.computeParentDimensions());
        target.adjust(SC.merge(newLayout, this.OFFSCREEN_LAYOUT));
        this.remove();
      }
      this._state   = this.READY;
      this.updateLayout();
      return this;
    }

    // ok, now let's compute the new layouts for the two views and set them
    layout = SC.clone(this.get('layout'));
    adjust = Math.floor(this._anchoredLayout.height * pct);
    var invokeAgain = YES;
    
    // set the layout for the views, depending on the direction
    if (dir == this.SLIDE_DOWN) {
      layout.height = this._anchoredLayout.height;
      layout.top = 0-(layout.height-adjust);
      target.adjust(layout);
    } 
    else if (dir == this.SLIDE_UP) {
      layout.height = this._anchoredLayout.height;
      layout.top = 0-adjust;
      target.adjust(layout);
    }
    else if (dir == this.BLIND_DOWN) {
      layout.top = this._anchoredLayout.top;
      layout.height = adjust;
      target.adjust(layout);
    }
    else if (dir == this.BLIND_UP) {
      layout.top = this._anchoredLayout.top;
      layout.height = this._anchoredLayout.height-adjust;
      target.adjust(layout);
    }
    else {
      target.adjust(this._openlayout);
      invokeAgain = NO;
    }
    this.updateLayout();
    this._timer = invokeAgain ? this.invokeLater(this.tick, 20) : null ;
    
    return this;
  }

});
/* >>>>>>>>>> BEGIN source/system/drag.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

SC.DRAG_LINK = 0x0004; SC.DRAG_COPY = 0x0001; SC.DRAG_MOVE = 0x0002;
SC.DRAG_NONE = 0x0000; SC.DRAG_ANY = 0x0007; // includes SC.DRAG_REORDER
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 parent them under the drag pane, which has the class 
    name 'sc-ghost-view'.
  
  - *ghost:  YES | NO*  If NO, the drag view image will show, but the source 
    dragView will not be hidden.  Set to YES to make it appear that the 
    dragView itself is being dragged around.
  
  - *slideBack: YES | NO*  If YES and 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 be used instead.
  
  - *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 */ {
  
  /**
    The source object used to coordinate this drag.
    
    @readOnly
    @type SC.DragSource
  */
  source: null,
  
  /**
    The view actually dragged around the screen. This is created automatically
    from the dragView.
    
    @readOnly
    @type SC.View
  */
  ghostView: null,
  
  /**
    If YES, then the ghostView will acts like a cursor and attach directly
    to the mouse location.
    
    @readOnly
    @type Boolean
  */
  ghostActsLikeCursor: NO,
  
  /**  
    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 
    moved, you may want to temporarily hide it during the drag.
    
    @readOnly
    @type SC.View
  */
  dragView: null,
  
  /**
    If YES, the dragView is automatically hidden while dragging around the 
    ghost.
    
    @readOnly
    @type Boolean
  */
  ghost: YES,
  
  /**
    If YES, then the ghostView will slide back to its original location if 
    drag is cancelled.
    
    @type Boolean
  */
  slideBack: YES,
  
  /**
    The original mouse down event.
    
    @readOnly
    @type SC.Event
  */
  mouseDownEvent: null,
  
  /**
    The origin to slide back to in the coordinate of the dragView's 
    containerView.
    
    @type Point
  */
  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. Drop targets are 
    encouraged to update this property in their dragUpdated() method 
    implementations.
    
    The ghostView will be positioned at this location.
    
    @type Point
  */
  location: {},
  
  // ..........................................
  // 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.
    
    @property {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.
    var hash = this.data ;
    if (hash) {
      var ret = [];
      for (var key in hash) {
        if (hash.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().cacheable(),
  
  /**
    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) {
    return (this.get('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(this, dataType) ;
      
    // 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 && SC.typeOf(source.dragDataForType) == SC.T_FUNCTION) {
        return source.dragDataForType(this, dataType) ;
        
      // 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.
    
    @readOnly
    @type SC.DragDataSource
  */
  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.
    
    @readOnly
    @type Hash
  */
  data: null,
  
  /**
    Returns the currently allowed dragOperations for the drag.  This will be 
    set just before any callbacks are invoked on a drop target.  The drag 
    source is given an opportunity to set these operations.
    
    @readOnly
    @type Number
  */
  allowedDragOperations: SC.DRAG_ANY,
  
  /** @private required by autoscroll */
  _dragInProgress: YES,
  
  /** @private
    This will actually start the drag process. Called by SC.Drag.start().
  */
  startDrag: function() {
    // create the ghost view
    this._createGhostView() ;
    
    var evt = this.event ;
    
    // compute the ghost offset from the original mouse location
    
    var loc = { x: evt.pageX, y: evt.pageY } ;
    this.set('location', loc) ;
    
    var dv = this.dragView ;
    var pane = dv.get('pane') ;
    var pv = dv.get('parentView') ;
    var clippingFrame = dv.get('clippingFrame') ;
    // convert to global cooridinates
    //var f = pv ? pv.convertFrameToView(clippingFrame, null) : clippingFrame ;
    var f = pv ? pv.convertFrameToView(dv.get('frame'), null) : dv.get('frame') ;
    var pf = pane ? pane.get('frame') : {x:0, y: 0};
    
    dv.adjust({
      top: f.y + pf.y,
      left: f.x + pf.x,
      width: f.width,
      height: f.height
    });
    //get frame in global cords after pane adjustment
    var dvf = dv.get('frame');
    
    var origin = f;//pv.convertFrameToView(dv.get('frame'), null) ;
    
    if (this.ghostActsLikeCursor) this.ghostOffset = { x: 14, y: 14 };
    else this.ghostOffset = { x: (loc.x-origin.x), y: (loc.y-origin.y) } ;
    
    // position the ghost view
    if(!this._ghostViewHidden) this._positionGhostView(evt) ;
    
    // notify root responder that a drag is in process
    this.ghostView.rootResponder.dragDidStart(this) ;
    
    var source = this.source ;
    if (source && source.dragDidBegin) source.dragDidBegin(this, loc) ;
    
    // let all drop targets know that a drag has started
    var ary = this._dropTargets() ;
    for (var idx=0, len=ary.length; idx<len; idx++) {
      ary[idx].tryToPerform('dragStarted', this, evt) ;
    }
  },
  
  // ..........................................
  // PRIVATE PROPERTIES AND METHODS
  //
  
  /** @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 scrolled = this._autoscroll(evt) ;
    var loc = this.get('location') ;
    if (!scrolled && (evt.pageX == loc.x) && (evt.pageY == loc.y)) {
      return ; // quickly ignore duplicate calls
    } 
    
    // save the new location to avoid duplicate mouseDragged event processing
    loc = { x: evt.pageX, y: evt.pageY };
    this.set('location', loc) ;
    
    // 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 source = this.source ;
    var last = this._lastTarget ;
    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 (target && source && source.dragSourceOperationMaskFor) {
        op = source.dragSourceOperationMaskFor(this, target) ;
      } else op = SC.DRAG_ANY ; // assume drops are allowed
      
      // now, let's see if the target will accept the drag
      if ((op != SC.DRAG_NONE) && target && target.computeDragOperations) {
        op = op & target.computeDragOperations(this, evt, op) ;
      } else op = SC.DRAG_NONE ; // assume drops AREN'T allowed
      
      this.allowedDragOperations = op ;
      
      // 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 needed
    if (target != last) {
      if (last && last.dragExited) last.dragExited(this, evt) ;
      
      if (target) {
        if (target.dragEntered) target.dragEntered(this, evt) ;
        if (target.dragUpdated) target.dragUpdated(this, evt) ;
      }
      
      this._lastTarget = target ;
    } else {
      if (target && target.dragUpdated) target.dragUpdated(this, evt) ;
    }
     
    // notify source that the drag moved
    if (source && source.dragDidMove) source.dragDidMove(this, loc) ;
    
    // reposition the ghostView
    if(!this._ghostViewHidden) this._positionGhostView(evt) ;
  },
  
  /**
    @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    = { x: evt.pageX, y: evt.pageY },
        target = this._lastTarget, 
        op     = this.allowedDragOperations;
    
    this.set('location', loc);
    
    // try to have the drop target perform the drop...
    try {
      if (target && target.acceptDragOperation && target.acceptDragOperation(this, op)) {
        op = target.performDragOperation ? target.performDragOperation(this, op) : SC.DRAG_NONE ;  
      } else {
        op = SC.DRAG_NONE;
      }
    } catch (e) {
      console.error('Exception in SC.Drag.mouseUp(acceptDragOperation|performDragOperation): %@'.fmt(e)) ;
    }
    
    try {
      // notify last drop target that the drag exited, to allow it to cleanup
      if (target && target.dragExited) target.dragExited(this, evt) ;
    } catch (ex) {
      console.error('Exception in SC.Drag.mouseUp(target.dragExited): %@'.fmt(ex)) ;
    }
    
    // notify all drop targets that the drag ended
    var ary = this._dropTargets() ;
    for (var idx=0, len=ary.length; idx<len; idx++) {
      try {
        ary[idx].tryToPerform('dragEnded', this, evt) ;
      } catch (ex2) {
        console.error('Exception in SC.Drag.mouseUp(dragEnded on %@): %@'.fmt(ary[idx], ex2)) ;
      }
    }

    // destroy the ghost view
    this._destroyGhostView() ;
    
    // notify the source that everything has completed
    var source = this.source ;
    if (source && source.dragDidEnd) source.dragDidEnd(this, loc, op) ;
    
    this._lastTarget = null ;
    this._dragInProgress = NO ; // required by autoscroll (invoked by a timer)
  },
  
  /** @private
    This will create the ghostView and add it to the document.
  */
  _createGhostView: function() {
    var that  = this,
        frame = this.dragView.get('frame'),
        view;
        
    view = this.ghostView = SC.Pane.create({
      classNames:['sc-ghost-view'],
      layout: { top: frame.y, left: frame.x, width: frame.width, height: frame.height },
      owner: this,
      didCreateLayer: function() {
        if (that.dragView) {
          var layer = that.dragView.get('layer') ;
          if (layer) this.get('layer').appendChild(layer.cloneNode(true)) ;
        }
      }
    });
    
    view.append() ;  // add to window
  },
  
  /** @private
    Positions the ghost view underneath the mouse with the initial offset
    recorded by when the drag started.
  */
  _positionGhostView: function(evt) {
    var loc = this.get('location') ;
    loc.x -= this.ghostOffset.x ;
    loc.y -= this.ghostOffset.y ;
    var gV = this.ghostView;
    if(gV) {
      gV.adjust({ top: loc.y, left: loc.x }) ;   
      gV.invokeOnce('updateLayout') ;
    }
  },
  
  /**
    YES if the ghostView has been manually hidden.
    
    @private 
    @type {Boolean}
    @default NO
  */
  _ghostViewHidden: NO,
  
  /**
    Hide the ghostView.
  */
  hideGhostView: function() {
    if(this.ghostView && !this._ghostViewHidden) {
      this.ghostView.remove();
      this._ghostViewHidden = YES;
    }
  },

  /**
    Unhide the ghostView.
  */
  unhideGhostView: function() {
    if(this._ghostViewHidden) {
      this._ghostViewHidden = NO;
      this._createGhostView();
    }
  },
  
  /** @private */
  _destroyGhostView: function() {
    if (this.ghostView) {
      this.ghostView.remove() ;
      this.ghostView = null ; // this will allow the GC to collect it.
      this._ghostViewHidden = NO;
    }
  },
  
  /** @private
    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.
  */
  _dropTargets: function() {
    if (this._cachedDropTargets) return this._cachedDropTargets ;
    
    // build array of drop targets
    var ret = [] ;
    var hash = SC.Drag._dropTargets ;
    for (var key in hash) {
      if (hash.hasOwnProperty(key)) ret.push(hash[key]) ;
    }
    
    // views must be sorted so that drop targets with the deepest nesting 
    // levels appear first in the array.  The getDepthFor().
    var depth = {} ;
    var dropTargets = SC.Drag._dropTargets ;
    var getDepthFor = function(x) {
      if (!x) return 0 ;
      var guid = SC.guidFor(x);
      var ret = depth[guid];
      if (!ret) {
        ret = 1 ;
        while (x = x.get('parentView')) {
          if (dropTargets[SC.guidFor(x)] !== undefined) ret++ ;
        }
        depth[guid] = ret ;
      }
      return ret ;
    } ;
    
    // sort array of drop targets
    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 ;
  },
  
  /** @private
    This will search through the drop targets, looking for one in the target 
    area.
  */
  _findDropTarget: function(evt) {
    var loc = { x: evt.pageX, y: evt.pageY } ;
    
    var target, frame ;
    var ary = this._dropTargets() ;
    for (var idx=0, len=ary.length; idx<len; idx++) {
      target = ary[idx] ;
      
      // If the target is not visible, it is not valid.
      if (!target.get('isVisibleInWindow')) continue ;
      
      // get clippingFrame, converted to the pane.
      frame = target.convertFrameToView(target.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, frame)) return target;
    } 
    return null ;
  },
  
  /** @private
    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) {
    var dropTargets = SC.Drag._dropTargets ;
    while (target = target.get('parentView')) {
      if (dropTargets[SC.guidFor(target)]) return target ;
    }
    return null ;
  },
  
  // ............................................
  // AUTOSCROLLING
  //
  
  /** @private
    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 YES if a scroll was performed.
  */
  _autoscroll: function(evt) {
    if (!evt) evt = this._lastAutoscrollEvent ;
    
    // If drag has ended, exit
    if (!this._dragInProgress) return NO;
    
    // 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 ? { x: evt.pageX, y: evt.pageY } : this.get('location'),
        view = this._findScrollableView(loc),
        scrollableView = null, // become final view when found
        vscroll, hscroll, min, max, edge, container, f;
    
    // hscroll and vscroll will become either 1 or -1 to indicate scroll 
    // direction or 0 for no scroll.
    
    while (view && !scrollableView) {
      
      // quick check...can we scroll this view right now?
      vscroll = view.get('canScrollVertical') ? 1 : 0;
      hscroll = view.get('canScrollHorizontal') ? 1 : 0;

      // at least one direction might be scrollable.  Collect frame info
      if (vscroll || hscroll) {
        container = view.get('containerView');
        if (container) {
          f = view.convertFrameToView(container.get('frame'),null);
        } else {
          vscroll = hscroll = 0 ; // can't autoscroll this mother
        }
      }

      // handle vertical direction
      if (vscroll) {
        
        // bottom hotzone?
        max = SC.maxY(f); 
        min = max - SC.DRAG_AUTOSCROLL_ZONE_THICKNESS ; 
        if (loc.y >= min && loc.y <= max) vscroll = 1 ;
        else {
          // how about top
          min = SC.minY(f); 
          max = min + SC.DRAG_AUTOSCROLL_ZONE_THICKNESS ;
          if (loc.y >= min && loc.y <= max) vscroll = -1 ;
          else vscroll = 0 ; // can't scroll vertical
        }
      }

      // handle horizontal direction
      if (hscroll) {
        
        // bottom hotzone?
        max = SC.maxX(f); 
        min = max - SC.DRAG_AUTOSCROLL_ZONE_THICKNESS ; 
        if (loc.x >= min && loc.x <= max) hscroll = 1 ;
        else {
          // how about top
          min = SC.minX(f); 
          max = min + SC.DRAG_AUTOSCROLL_ZONE_THICKNESS ;
          if (loc.x >= min && loc.x <= max) hscroll = -1 ;
          else hscroll = 0 ; // can't scroll vertical
        }
      }
      
      // if we can scroll, then set this.
      if (vscroll || hscroll) 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 ; // accelerate scroll
      }
      
    // otherwise, reset everything and disallow scroll
    } else {
      this._lastScrollableView = scrollableView ;
      this._horizontalScrollAmount = 15 ;
      this._verticalScrollAmount = 15 ;
      this._hotzoneStartTime = (scrollableView) ? Date.now() : null ;
      hscroll = vscroll = 0 ;
    }
    
    // STEP 3: Scroll!
    if (scrollableView && (hscroll || vscroll)) {
      var scroll = { 
        x: hscroll * this._horizontalScrollAmount,
        y: vscroll * this._verticalScrollAmount 
      } ;
      scrollableView.scrollBy(scroll) ;
    }
    
    // If a scrollable view was found, then check later
    if (scrollableView) {
      if (evt) {
        this._lastAutoscrollEvent = { pageX: evt.pageX, pageY: evt.pageY };
      }
      this.invokeLater(this._autoscroll, 100, null);
      return YES ;
    } else {
      this._lastAutoscrollEvent = null;
      return NO ;
    }
  },
  
  /** @private
    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 state of scrollable 
    views.  Afterwards it uses the cached set until the drop completes.
  */
  _scrollableViews: function() {
    if (this._cachedScrollableView) return this._cachedScrollableView ;
    
    // build array of scrollable views
    var ret = [] ;
    var hash = SC.Drag._scrollableViews ;
    for (var key in hash) {
      if (hash.hasOwnProperty(key)) ret.push(hash[key]) ;
    }
    
    // now resort.  This custom function will sort nested scrollable views
    // at the start of the list.
    ret = ret.sort(function(a,b) {
      var view = a;
      while (view = view.get('parentView')) {
        if (b == view) return -1 ;
      }
      return 1; 
    }) ;
    
    this._cachedScrollableView = ret ;
    return ret ;
  },
  
  /** @private
    This will search through the scrollable views, looking for one in the 
    target area.
  */
  _findScrollableView: function(loc) {
    var ary = this._scrollableViews(),
        len = ary ? ary.length : 0,
        target, frame, idx;
        
    for (idx=0; idx<len; idx++) {
      target = ary[idx] ;
      
      if (!target.get('isVisibleInWindow')) continue ;
      
      // get clippingFrame, converted to the pane
      frame = target.convertFrameToView(target.get('clippingFrame'), null) ;
      
      // check to see if loc is inside
      if (SC.pointInRect(loc, frame)) return target;
    } 
    return null ;
  },
  
  /** @private
    Search the parent nodes of the target to find another scrollable view.
    return null if none is found.
  */
  _findNextScrollableView: function(view) {
    var scrollableViews = SC.Drag._scrollableViews ;
    while (view = view.get('parentView')) {
      if (scrollableViews[SC.guidFor(view)]) 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 ;
  },
  
  /** @private */
  _dropTargets: {},
  
  /** @private */
  _scrollableViews: {},
  
  /**
    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.
    
    @param {SC.View} target a view implementing the SC.DropTarget protocol
  */
  addDropTarget: function(target) {
    this._dropTargets[SC.guidFor(target)] = target ;
  },
  
  /**
    Unregister the view object as a drop target.
    
    This method is called automatically whenever a view is removed from the 
    hierarchy.  You generally will not need to call it yourself.
    
    @param {SC.View} target A previously registered drop target
  */
  removeDropTarget: function(target) {
    delete this._dropTargets[SC.guidFor(target)] ;
  },
  
  /**
    Register the view object as a scrollable view.  These views will 
    auto-scroll during a drag.
    
    @param {SC.View} target The view that should be auto-scrolled
  */
  addScrollableView: function(target) {
    this._scrollableViews[SC.guidFor(target)] = target ;  
  },
  
  /**
    Remove the view object as a scrollable view.  These views will auto-scroll
    during a drag.
    
    @param {SC.View} target A previously registered scrollable view
  */
  removeScrollableView: function(target) {
    delete this._scrollableViews[SC.guidFor(target)] ;  
  }
  
});
/* >>>>>>>>>> BEGIN source/system/key_bindings.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

// Key Bindings are used to map a keyboard input to an action message on a
// responder.  These bindings are most useful when implementing sophisticated
// keyboard input mechanisms.  For keyboard shortcuts, instead use menus, etc.

SC.MODIFIED_KEY_BINDINGS = {
  '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_KEY_BINDINGS = {
  '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'
} ;


/* >>>>>>>>>> BEGIN source/system/root_responder.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

/** Set to NO to leave the backspace key under the control of the browser.*/
SC.CAPTURE_BACKSPACE_KEY = NO ;

/**
  Order layer for regular Panels.  Panels appear in front of the main view, 
  but behind palettes, popups.
*/
SC.PANEL_ORDER_LAYER = 0x1000 ;

/** 
  Order layer for Palettes.  Palettes appear in front of the main view and 
  panels, but behind popups.
*/
SC.PALETTE_ORDER_LAYER = 0x2000 ;

/**
  Order layer for Popups.  Popups appear in fron of hte main view and panels.
*/
SC.POPUP_ORDER_LAYER = 0x3000 ;

/*
  This is the root responder subclass for desktop-style applications.  It 
  supports mouse events and window resize events in addition to the built
  in keyboard handling provided by the base class.
*/
SC.RootResponder = SC.RootResponder.extend(
/** @scope SC.RootResponder.prototype */ {

  platform: 'desktop',

  // ..........................................................
  // ORDERED PANES
  // 
  
  /** @property
    The current front view.  This view should have the highest z-index of all 
    the other views.
  */
  focusedPane: function() {
    var views = this.get('orderedPanes');
    return views[views.length-1];
  }.property('orderedPanes'),
  
  
  /** @property
    Array of panes currently displayed that can be reordered.  This property 
    changes when you orderBack() or orderOut() a pane to determine the next 
    frontmost pane.
  */
  orderedPanes: null,

  /**
    Inserts the passed panes into the orderedPanes array before the named pane 
    array.  Pass null to order at the front.  If this changes the frontmost 
    view, then focus will also be shifted.  The pane you request must have the 
    same orderLayer property at the pane you are passing in.  If it does not, 
    the pane will be placed nearest to the target as possible.
    
    @param {SC.Pane} pane
    @param {SC.Pane} beforePane
    @returns {SC.RootResponder} receiver
  */
  orderBefore: function(pane, beforePane) {
    var currentFocus = this.get('focusedPane'),
        panes = this.get('orderedPanes').without(pane),
        len, idx, currentOrder, newFocus ;

    // adjust the beforePane to match orderLayer
    var orderLayer = pane.get('orderLayer');
    if (beforePane) {
      len = panes.length;
      idx = panes.indexOf(beforePane);
      currentOrder = beforePane.get('orderLayer');
      
      if (currentOrder<orderLayer) {
        while((beforePane.get('orderLayer')<orderLayer) && (++idx<len)) beforePane = panes[idx];
        if (idx>=len) beforePane = null ; // insert at end if needed 
      } else if (currentOrder>orderLayer) {
        while((beforePane.get('orderLayer')>orderLayer) && (--idx>=0)) beforePane = panes[idx];
        beforePane = (idx<0) ? panes[0] : panes[idx+1]; // go to next pane
      }
    
    // otherwise, find the highest pane matching the order...
    } else {
      idx = panes.length ;
      while((--idx >= 0) && !beforePane) {
        beforePane = panes[idx] ;
        if (beforePane.get('orderLayer') > orderLayer) beforePane = null; // try next one
      }
      if (idx<0) { // did not find a match, insert at beginning
        beforePane = panes[0];
      } else beforePane = panes[idx+1]; // go to next pane
    }
    
    // adjust array
    if (beforePane) {
      idx = panes.indexOf(beforePane);
      panes.insertAt(idx, pane);
    } else panes.push(pane);
    this.set('orderedPanes', panes); // update

    newFocus = this.get('focusedPane'); 
    if (newFocus !== currentFocus) {
      if (currentFocus) currentFocus.blurTo(newFocus);
      if (newFocus) newFocus.focusFrom(currentFocus);
    }
    
    return this ;
  },

  /**
    Removes the named pane from the orderedPanes array.  If the pane was also 
    focused, it will also blur the pane and focus the next view.  If the view 
    is key, it will also determine the next view to make key by going down the 
    list of ordered panes, finally ending with the mainPane.
    
    @param {SC.Pane} pane
    @param {SC.Pane} beforePane
    @returns {SC.RootResponder} receiver
  */
  orderOut: function(pane) {
    var currentFocus = this.get('focusedPane'), currentKey = this.get('keyPane');
    
    var panes = this.get('orderedPanes').without(pane) ;
    this.set('orderedPanes', panes) ;
    
    // focus only changes if we are removing the current focus view.
    // in this case, blur the old view and focus the new.  Also, if the view was
    // key, try to make the new focus view key or make main key.
    if (currentFocus === pane) {
      var newFocus = this.get('focusedPane') ;
      if (currentFocus) currentFocus.blurTo(newFocus) ;
      if (newFocus) newFocus.focusFrom(currentFocus) ;
      if (currentKey === pane) this.makeKeyPane(newFocus); 
      
    // if the front is not changing, just check for key view.  Go back to main...
    } else if (currentKey === pane) {
      this.makeKeyPane(null);
    }
    
    return this ;
  },
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    this.orderedPanes = []; // create new array  
  },
  
  // .......................................................
  // EVENT HANDLING
  //
  
  setup: function() {
    // handle basic events        
    this.listenFor('keydown keyup mousedown mouseup click dblclick mouseout mouseover mousemove selectstart'.w(), document)
        .listenFor('resize focus blur'.w(), window);

    // handle special case for keypress- you can't use normal listener to block the backspace key on Mozilla
    if (this.keypress) {
      if (SC.CAPTURE_BACKSPACE_KEY && SC.browser.mozilla) {
        var responder = this ;
        document.onkeypress = function(e) { 
          e = SC.Event.normalizeEvent(e);
          return responder.keypress.call(responder, e); 
        };
        
        SC.Event.add(window, 'unload', this, function() { document.onkeypress = null; }); // be sure to cleanup memory leaks
  
      // Otherwise, just add a normal event handler. 
      } else SC.Event.add(document, 'keypress', this, this.keypress);
    }

    // handle these two events specially in IE
    'drag selectstart'.w().forEach(function(keyName) {
      var method = this[keyName] ;
      if (method) {
        if (SC.browser.msie) {
          var responder = this ;
          document.body['on' + keyName] = function(e) { 
            // return method.call(responder, SC.Event.normalizeEvent(e)); 
            return method.call(responder, SC.Event.normalizeEvent(event || window.event)); // this is IE :(
          };

          // be sure to cleanup memory leaks
           SC.Event.add(window, 'unload', this, function() { 
            document.body['on' + keyName] = null; 
          });
          
        } else {
          SC.Event.add(document, keyName, this, method);
        }
      }
    }, this);
    
    // handle mousewheel specifically for FireFox
    var mousewheel = SC.browser.mozilla ? 'DOMMouseScroll' : 'mousewheel';
    SC.Event.add(document, mousewheel, this, this.mousewheel);
    
    // do some initial set
    this.set('currentWindowSize', this.computeWindowSize()) ;
    this.focus(); // assume the window is focused when you load.
  },

  /**
    Invoked on a keyDown event that is not handled by any actual value.  This 
    will get the key equivalent string and then walk down the keyPane, then 
    the focusedPane, then the mainPane, looking for someone to handle it.  
    Note that this will walk DOWN the view hierarchy, not up it like most.
    
    @returns {Object} Object that handled evet or null
  */ 
  attemptKeyEquivalent: function(evt) {
    var ret = null ;

    // keystring is a method name representing the keys pressed (i.e 
    // 'alt_shift_escape')
    var keystring = evt.commandCodes()[0];
    
    // couldn't build a keystring for this key event, nothing to do
    if (!keystring) return NO;
    
    var keyPane  = this.get('keyPane'), mainPane = this.get('mainPane'), 
        mainMenu = this.get('mainMenu');

    // try the keyPane
    if (keyPane) ret = keyPane.performKeyEquivalent(keystring, evt) ;
    
    // if not, then try the main pane
    if (!ret && mainPane && (mainPane!==keyPane)) {
      ret = mainPane.performKeyEquivalent(keystring, evt);
    }

    // if not, then try the main menu
    if (!ret && mainMenu) {
      ret = mainMenu.performKeyEquivalent(keystring, evt);
    }
    
    return ret ;
  },

  /** @property The last known window size. */
  currentWindowSize: null,
  
  /** Computes the window size from the DOM. */  
  computeWindowSize: function() {
    var size ;
    if (window.innerHeight) {
      size = { 
        width: window.innerWidth, 
        height: window.innerHeight 
      } ;

    } else if (document.documentElement && document.documentElement.clientHeight) {
      size = { 
        width: document.documentElement.clientWidth, 
        height: document.documentElement.clientHeight 
      } ;

    } else if (document.body) {
      size = { 
        width: document.body.clientWidth, 
        height: document.body.clientHeight 
      } ;
    }
    return size;
  },
  
  /** 
    On window resize, notifies panes of the change. 
    
    @returns {Boolean}
  */
  resize: function() {
    this._resize();
    //this.invokeLater(this._resize, 10);
    return YES; //always allow normal processing to continue.
  },
  
  _resize: function() {
    // calculate new window size...
    var newSize = this.computeWindowSize(), oldSize = this.get('currentWindowSize');
    this.set('currentWindowSize', newSize); // update size
    
    if (!SC.rectsEqual(newSize, oldSize)) {
      // notify panes
      if (this.panes) {
        SC.RunLoop.begin() ;
        this.panes.invoke('windowSizeDidChange', oldSize, newSize) ;
        SC.RunLoop.end() ;
      }
    }
  },
  
  /** 
    Indicates whether or not the window currently has focus.  If you need
    to do something based on whether or not the window is in focus, you can
    setup a binding or observer to this property.  Note that the SproutCore
    automatically adds an sc-focus or sc-blur CSS class to the body tag as
    appropriate.  If you only care about changing the appearance of your 
    controls, you should use those classes in your CSS rules instead.
  */
  hasFocus: NO,
  
  /**
    Handle window focus.  Change hasFocus and add sc-focus CSS class 
    (removing sc-blur).  Also notify panes.
  */  
  focus: function() {
    if (!this.get('hasFocus')) {
      SC.$('body').addClass('sc-focus').removeClass('sc-blur');
      
      SC.RunLoop.begin();
      this.set('hasFocus', YES);
      SC.RunLoop.end();
    }
    return YES ; // allow default
  },
  
  /**
    Handle window focus.  Change hasFocus and add sc-focus CSS class (removing 
    sc-blur).  Also notify panes.
  */  
  blur: function() {
    if (this.get('hasFocus')) {
      SC.$('body').addClass('sc-blur').removeClass('sc-focus');
      
      SC.RunLoop.begin();
      this.set('hasFocus', NO);
      SC.RunLoop.end();
    }
    return YES ; // allow default
  },
  
  dragDidStart: function(drag) {
    this._mouseDownView = drag ;
    this._drag = drag ;
  },
  
  // .......................................................
  // KEYBOARD HANDLING
  // 
  
  _lastModifiers: null,
  
  /** @private
    Modifier key changes are notified with a keydown event in most browsers.  
    We turn this into a flagsChanged keyboard event.  Normally this does not
    stop the normal browser behavior.
  */  
  _handleModifierChanges: function(evt) {
    // if the modifier keys have changed, then notify the first responder.
    var m;
    m = this._lastModifiers = (this._lastModifiers || { alt: false, ctrl: false, shift: false });
    
    var changed = false;
    if (evt.altKey !== m.alt) { m.alt = evt.altKey; changed=true; }
    if (evt.ctrlKey !== m.ctrl) { m.ctrl = evt.ctrlKey; changed=true; }
    if (evt.shiftKey !== m.shift) { m.shift = evt.shiftKey; changed=true;}
    evt.modifiers = m; // save on event
    
    return (changed) ? (this.sendEvent('flagsChanged', evt) ? evt.hasCustomEventHandling : YES) : YES ;
  },
  
  /** @private
    Determines if the keyDown event is a nonprintable or function key. These
    kinds of events are processed as keyboard shortcuts.  If no shortcut
    handles the event, then it will be sent as a regular keyDown event.
  */
  _isFunctionOrNonPrintableKey: function(evt) {
    return !!(evt.altKey || evt.ctrlKey || evt.metaKey || ((evt.charCode !== evt.which) && SC.FUNCTION_KEYS[evt.which]));
  },
  
  /** @private 
    Determines if the event simply reflects a modifier key change.  These 
    events may generate a flagsChanged event, but are otherwise ignored.
  */
  _isModifierKey: function(evt) {
    return !!SC.MODIFIER_KEYS[evt.charCode];
  },
  
  /** @private
    The keydown event occurs whenever the physically depressed key changes.
    This event is used to deliver the flagsChanged event and to with function
    keys and keyboard shortcuts.
    
    All actions that might cause an actual insertion of text are handled in
    the keypress event.
  */
  keydown: function(evt) {
    // This code is to check for the simulated keypressed event
    if(!evt.kindOf) this._ffevt=null;
    else evt=this._ffevt;
    if (SC.none(evt)) return YES;    
    // Firefox does NOT handle delete here...
    if (SC.browser.mozilla && (evt.which === 8)) return true ;
    
    // modifier keys are handled separately by the 'flagsChanged' event
    // send event for modifier key changes, but only stop processing if this 
    // is only a modifier change
    var ret = this._handleModifierChanges(evt),
        target = evt.target || evt.srcElement,
        forceBlock = (evt.which === 8) && !SC.allowsBackspaceToPreviousPage && (target === document.body);
    
    if (this._isModifierKey(evt)) return (forceBlock ? NO : ret);
    
    // if this is a function or non-printable key, try to use this as a key
    // equivalent.  Otherwise, send as a keyDown event so that the focused
    // responder can do something useful with the event.
    ret = YES ;
    if (this._isFunctionOrNonPrintableKey(evt)) {
      // Simulate keydown events for firefox since keypress only triggers once
      // We don't do it in keypress as it doesn't work in certain cases, ie.
      // Caret is at last position and you press down arrow key.
      if (SC.browser.mozilla && evt.keyCode>=37 && evt.keyCode<=40){
        this._ffevt=evt;
        SC.RunLoop.begin();
        this.invokeLater(this.keydown, 100);
        SC.RunLoop.end();
      }
      // otherwise, send as keyDown event.  If no one was interested in this
      // keyDown event (probably the case), just let the browser do its own
      // processing.
      ret = this.sendEvent('keyDown', evt) ;
      
      // attempt key equivalent if key not handled
      if (!ret) {
        ret = !this.attemptKeyEquivalent(evt) ;
      } else {
        ret = evt.hasCustomEventHandling ;
        if (ret) forceBlock = NO ; // code asked explicitly to let delete go
      }
    }

    return forceBlock ? NO : ret ; 
  },
  
  /** @private
    The keypress event occurs after the user has typed something useful that
    the browser would like to insert.  Unlike keydown, the input codes here 
    have been processed to reflect that actual text you might want to insert.
    
    Normally ignore any function or non-printable key events.  Otherwise, just
    trigger a keyDown.
  */
  keypress: function(evt) {
    var ret ;
    
    // delete is handled in keydown() for most browsers
    if (SC.browser.mozilla && (evt.which === 8)) {
      ret = this.sendEvent('keyDown', evt);
      return ret ? (SC.allowsBackspaceToPreviousPage || evt.hasCustomEventHandling) : YES ;

    // normal processing.  send keyDown for printable keys...
    } else {
      if (evt.charCode !== undefined && evt.charCode === 0) return YES;
      return this.sendEvent('keyDown', evt) ? evt.hasCustomEventHandling:YES;
    }
  },
  
  keyup: function(evt) {
    // to end the simulation of keypress in firefox set the _ffevt to null
    if(this._ffevt) this._ffevt=null;
    // modifier keys are handled separately by the 'flagsChanged' event
    // send event for modifier key changes, but only stop processing if this is only a modifier change
    var ret = this._handleModifierChanges(evt);
    if (this._isModifierKey(evt)) return ret;
    return this.sendEvent('keyUp', evt) ? evt.hasCustomEventHandling:YES;
  },
  
  mousedown: function(evt) {
    try {
      // make sure the window gets focus no matter what.  FF is inconsistant 
      // about this.
      this.focus();
      if(SC.browser.msie) {
        this._lastMouseDownX = evt.clientX;
        this._lastMouseDownY = evt.clientY;
      }
      // first, save the click count.  Click count resets if your down is
      // more than 125msec after you last click up.
      this._clickCount += 1 ;
      if (!this._lastMouseUpAt || ((Date.now()-this._lastMouseUpAt) > 200)) {
        this._clickCount = 1 ; 
      }
      evt.clickCount = this._clickCount ;
      
      var fr, view = this.targetViewForEvent(evt) ;
      // InlineTextField needs to loose firstResponder whenever you click outside
      // the view. This is a special case as textfields are not supposed to loose 
      // focus unless you click on a list, another textfield or an special
      // view/control.
      if(view) fr=view.get('pane').get('firstResponder');
      
      if(fr && fr.kindOf(SC.InlineTextFieldView) && fr!==view){
        fr.resignFirstResponder();
      }
      
      view = this._mouseDownView = this.sendEvent('mouseDown', evt, view) ;
      if (view && view.respondsTo('mouseDragged')) this._mouseCanDrag = YES ;
    } catch (e) {
    
      console.warn('Exception during mousedown: %@'.fmt(e)) ;
      this._mouseDownView = null ;
      this._mouseCanDrag = NO ;
      throw e;
    }
    
    return view ? evt.hasCustomEventHandling : YES;
  },
  
  /**
    mouseUp only gets delivered to the view that handled the mouseDown evt.
    we also handle click and double click notifications through here to 
    ensure consistant delivery.  Note that if mouseDownView is not
    implemented, then no mouseUp event will be sent, but a click will be 
    sent.
  */
  mouseup: function(evt) {

    try {
      if (this._drag) {
        this._drag.tryToPerform('mouseUp', evt) ;
        this._drag = null ;
      }
      
      var handler = null, view = this._mouseDownView ;
      this._lastMouseUpAt = Date.now() ;
      
      // record click count.
      evt.clickCount = this._clickCount ;
      
      // attempt the mouseup call only if there's a target.
      // don't want a mouseup going to anyone unless they handled the mousedown...
      if (view) {
        handler = this.sendEvent('mouseUp', evt, view) ;
        
        // try doubleClick
        if (!handler && (this._clickCount === 2)) {
          handler = this.sendEvent('doubleClick', evt, view) ;
        }
        
        // try single click
        if (!handler) {
          handler = this.sendEvent('click', evt, view) ;
        }
      }
      
      // try whoever's under the mouse if we haven't handle the mouse up yet
      if (!handler) {
        view = this.targetViewForEvent(evt) ;
      
        // try doubleClick
        if (this._clickCount === 2) {
          handler = this.sendEvent('doubleClick', evt, view);
        }
      
        // try singleClick
        if (!handler) {
          handler = this.sendEvent('click', evt, view) ;
        }
      }
      
      // cleanup
      this._mouseCanDrag = NO; this._mouseDownView = null ;
    } catch (e) {
      this._drag = null; this._mouseCanDrag = NO; this._mouseDownView = null ;
      throw e;
    }
    return (handler) ? evt.hasCustomEventHandling : YES ;
  },
  
  dblclick: function(evt){
    if (SC.browser.isIE) {
      this._clickCount = 2;
      // this._onmouseup(evt);
      this.mouseup(evt);
    }
  },
  
  mousewheel: function(evt) {
    try {
      var view = this.targetViewForEvent(evt) ,
          handler = this.sendEvent('mouseWheel', evt, view) ;
    } catch (e) {
      throw e;
    }
    return (handler) ? evt.hasCustomEventHandling : YES ;
  },
  
  _lastHovered: null,
  
  /**
   This will send mouseEntered, mouseExited, mousedDragged and mouseMoved 
   to the views you hover over.  To receive these events, you must implement 
   the method. If any subviews implement them and return true, then you won't 
   receive any notices.
   
   If there is a target mouseDown view, then mouse moved events will also
   trigger calls to mouseDragged.
  */
  mousemove: function(evt) {
    if(SC.browser.msie){
      if(this._lastMoveX === evt.clientX && this._lastMoveY === evt.clientY) return;
      else {
        this._lastMoveX = evt.clientX;
        this._lastMoveY = evt.clientY;
      }
    }
    
    SC.RunLoop.begin();
    try {
      // make sure the view gets focus no matter what.  FF is inconsistant 
      // about this.
      this.focus();
      // only do mouse[Moved|Entered|Exited|Dragged] if not in a drag session
      // drags send their own events, e.g. drag[Moved|Entered|Exited]
      if (this._drag) {
        //IE triggers mousemove at the same time as mousedown
        if(SC.browser.msie){
          if (this._lastMouseDownX !== evt.clientX && this._lastMouseDownY !== evt.clientY) {
            this._drag.tryToPerform('mouseDragged', evt);
          }
        }
        else {
          this._drag.tryToPerform('mouseDragged', evt);
        }
      } else {
        var lh = this._lastHovered || [] , nh = [] , exited, loc, len, 
            view = this.targetViewForEvent(evt) ;
        
        // work up the view chain.  Notify of mouse entered and
        // mouseMoved if implemented.
        while(view && (view !== this)) {
          if (lh.indexOf(view) !== -1) {
            view.tryToPerform('mouseMoved', evt);
            nh.push(view) ;
          } else {
            view.tryToPerform('mouseEntered', evt);
            nh.push(view) ;
          }
          
          view = view.get('nextResponder');
        }
        // now find those views last hovered over that were no longer found 
        // in this chain and notify of mouseExited.
        for(loc=0, len=lh.length; loc < len; loc++) {
          view = lh[loc] ;
          exited = view.respondsTo('mouseExited') ;
          if (exited && !(nh.indexOf(view) !== -1)) {
            view.tryToPerform('mouseExited',evt);
          }
        }
        this._lastHovered = nh; 
        
        // also, if a mouseDownView exists, call the mouseDragged action, if 
        // it exists.
        if (this._mouseDownView) {
          if(SC.browser.msie){
            if (this._lastMouseDownX !== evt.clientX && this._lastMouseDownY !== evt.clientY) {
              this._mouseDownView.tryToPerform('mouseDragged', evt);
            }
          }
          else {
            this._mouseDownView.tryToPerform('mouseDragged', evt);
          }
        }
      }
    } catch (e) {
      throw e;
    }
    SC.RunLoop.end();
  },

  // these methods are used to prevent unnecessary text-selection in IE,
  // there could be some more work to improve this behavior and make it
  // a bit more useful; right now it's just to prevent bugs when dragging
  // and dropping.
  
  _mouseCanDrag: YES,
  
  selectstart: function(evt) { 
    var result = this.sendEvent('selectStart', evt, this.targetViewForEvent(evt));
    return (result !==null ? YES: NO) && (this._mouseCanDrag ? NO : YES);
  },
  
  drag: function() { return false; }
  
});

/* >>>>>>>>>> BEGIN source/system/undo_manager.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

sc_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) ;
  }
  
}) ;

/* >>>>>>>>>> BEGIN source/views/checkbox.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @class

  Renders a checkbox button view specifically.
  
  This view is basically a button view preconfigured to generate the correct
  HTML and to set to use a TOGGLE_BEHAVIOR for its buttons.
  
  This view renders a simulated checkbox that can display a mixed state and 
  has other features not found in platform-native controls.  
  
  @extends SC.FieldView
  @since SproutCore 1.0
*/
SC.CheckboxView = SC.FieldView.extend(SC.StaticLayout, SC.Button,
  /** @scope SC.CheckboxView.prototype */ {

  classNames: ['sc-checkbox-view'],
  tagName: 'label',

  /* Ellipsis is disabled by default to allow multiline text */
  needsEllipsis: NO,

  render: function(context, firstTime) {
    var dt, elem ;
    
    // add checkbox -- set name to view guid to separate it from others
    if (firstTime) {
      dt = this._field_currentDisplayTitle = this.get('displayTitle');

      var blank = SC.BLANK_IMAGE_URL;
      var disabled = this.get('isEnabled') ? '' : 'disabled="disabled"';
      if(SC.browser.msie) context.attr('for', SC.guidFor(this));
      context.push('<span class="button" ></span>');
      context.push('<input type="checkbox" id="%@" name="%@" %@ />'.fmt(SC.guidFor(this),SC.guidFor(this),disabled));
      if(this.get('needsEllipsis')){
        context.push('<span class="label ellipsis">', dt, '</span>');
      }else{
        context.push('<span class="label">', dt, '</span>');  
      }
      context.attr('name', SC.guidFor(this));

    // since we don't want to regenerate the contents each time 
    // actually search for and update the displayTitle.
    } else {
      
      if (elem = this.$input()[0]) {
        if (this.get('isEnabled')) elem.disabled=NO;
        else elem.disabled=YES;
        elem = null ; // avoid memory leaks
      }
      
      dt = this.get('displayTitle');
      if (dt !== this._field_currentDisplayTitle) {
        this._field_currentDisplayTitle = dt;
        this.$('span.label').text(dt);
      }
    }
  },
  
  // ..........................................
  // SC.FIELD SUPPORT
  //

  /** @private - return the input tag */
  $input: function() { return this.$('input'); },

  /** @private - get the checked value from the input tag.  If the value is
    unchecked and the last value set was mixed, then return mixed.  This
    allows mixed states to remain unchanged. */
  getFieldValue: function() { 
    var ret = this.$input().attr('checked'); 
    if (ret) {
      this._lastFieldValue = null; // clear last field value since it changed
      
    // possibly return mixed state if that was the last value set and the 
    // current checked value is still empty.
    } else {
      if (this._lastFieldValue === SC.MIXED_STATE) ret = SC.MIXED_STATE ;
    }
    return ret ;
  },

  /** @private - set the checked value on the input tag.  If the value is 
    mixed, treat that as unchecked and save the value.  This way the mixed 
    state can be retained.
  */
  setFieldValue: function(v) { 
    this._lastFieldValue = v; 
    this.$input().attr('checked', (v === SC.MIXED_STATE) ? NO : !!v); 
  },
  
  /** @private - Converts the field value to the toggleOnValue or mixed */
  fieldValueForObject: function(obj) {
    return this.computeIsSelectedForValue(obj) ;
  },

  /** @private - Converts the field value to the toggleOffValue.  If the
    value is MIXED_STATE, always return the current value */
  objectForFieldValue: function(v) {
    var ret = (v === SC.MIXED_STATE) ? this.get('value') : 
      (!!v) ? this.get('toggleOnValue') : this.get('toggleOffValue'); 
      return ret ;
  },
  
  
  didCreateLayer: function() {
    this.setFieldValue(this.get('fieldValue'));
    SC.Event.add(this.$input()[0], 'click', this, this._field_fieldValueDidChange) ;
  },
  
  willDestroyLayer: function() {
    SC.Event.remove(this.$input()[0], 'click', this, this._field_fieldValueDidChange); 
  },
  
  mouseDown: function(evt) {
    this.set('isActive', YES);
    this._field_isMouseDown = YES;
    return YES;
  }
    
}) ;

/* >>>>>>>>>> BEGIN source/views/list_item.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            portions copyright @2009 Apple Inc.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

SC.LIST_ITEM_ACTION_CANCEL = 'sc-list-item-cancel-action';
SC.LIST_ITEM_ACTION_REFRESH = 'sc-list-item-cancel-refresh';
SC.LIST_ITEM_ACTION_EJECT = 'sc-list-item-cancel-eject';

/**
  @class
  
  Many times list items need to display a lot more than just a label of text.
  You often need to include checkboxes, icons, right icons, extra counts and 
  an action or warning icon to the far right. 
  
  A ListItemView can implement all of this for you in a more efficient way 
  than you might get if you simply put together a list item on your own using
  views.
  
  @extends SC.View
  @extends SC.Control
  @extends SC.Editable
  @extends SC.StaticLayout
  @since SproutCore 1.0
*/
SC.ListItemView = SC.View.extend(
    SC.StaticLayout,
    SC.Control,
/** @scope SC.ListItemView.prototype */ {
  
  classNames: ['sc-list-item-view'],
  
  // ..........................................................
  // KEY PROPERTIES
  // 
  
  /**
    The content object the list item will display.
    
    @type SC.Object
  */
  content: null,
  
  /**
    (displayDelegate) True if you want the item view to display an icon.
    
    If false, the icon on the list item view will be hidden.  Otherwise,
    space will be left for the icon next to the list item view.
  */
  hasContentIcon: NO,

  /**
    (displayDelegate) True if you want the item view to display a right icon.
    
    If false, the icon on the list item view will be hidden.  Otherwise,
    space will be left for the icon next to the list item view.
  */
  hasContentRightIcon: NO,
  
  /**
    (displayDelegate) True if you want space to be allocated for a branch 
    arrow.
    
    If false, the space for the branch arrow will be collapsed.
  */
  hasContentBranch: NO,
  
  /**
    (displayDelegate) The name of the property used for the checkbox value.
    
    The checkbox will only be visible if this key is not null.
    
    @type {String}
  */
  contentCheckboxKey: null,
  
  /**
    (displayDelegate) Property key to use for the icon url

    This property will be checked on the content object to determine the 
    icon to display.  It must return either a URL or a CSS class name.
  */
  contentIconKey: null,
 
  /**
    (displayDelegate) Property key to use for the right icon url

    This property will be checked on the content object to determine the 
    icon to display.  It must return either a URL or a CSS class name.
  */
  contentRightIconKey: null,
  
  /**
    (displayDelegate) The name of the property used for label itself
    
    If null, then the content object itself will be used..
  */
  contentValueKey: null,
  
  /**
    IF true, the label value will be escaped to avoid HTML injection attacks.
    You should only disable this option if you are sure you will only 
    display content that is already escaped and you need the added 
    performance gain.
  */
  escapeHTML: YES,
  
  /**
    (displayDelegate) The name of the property used to find the count of 
    unread items. 
    
    The count will only be visible if this property is not null and the 
    returned value is not 0.
  */
  contentUnreadCountKey: null,
  
  /**
    (displayDelegate) The name of the property used to determine if the item
    is a branch or leaf (i.e. if the branch icon should be displayed to the
    right edge.)
    
    If this is null, then the branch view will be completely hidden.
    Otherwise space will be allocated for it.
  */
  contentIsBranchKey: null,
  

  /**
    YES if the item view is currently editing.
  */
  isEditing: NO,
  
  /**
    Indent to use when rendering a list item with an outline level > 0.  The
    left edge of the list item will be indented by this amount for each 
    outline level.
  */
  outlineIndent: 16,
  
  /**
    Outline level for this list item.  Usually set by the collection view.
  */
  outlineLevel: 0,
  
  /**
    Disclosure state for this list item.  Usually set by the collection view
    when the list item is created.
  */
  disclosureState: SC.LEAF_NODE,
  
  contentPropertyDidChange: function() {
    //if (this.get('isEditing')) this.discardEditing() ;
    if (this.get('contentIsEditable') !== this.contentIsEditable()) {
      this.notifyPropertyChange('contentIsEditable');
    }
    
    this.displayDidChange();
  },
  
  /**
    Determines if content is editable or not.  Checkboxes and other related
    components will render disabled if an item is not editable.
  */
  contentIsEditable: function() {
    var content = this.get('content');
    return content && (content.get ? content.get('isEditable')!==NO : NO);
  }.property('content').cacheable(),
  
  /**
    Fills the passed html-array with strings that can be joined to form the
    innerHTML of the receiver element.  Also populates an array of classNames
    to set on the outer element.
    
    @param {SC.RenderContext} context
    @param {Boolean} firstTime
    @returns {void}
  */
  render: function(context, firstTime) {
    var content = this.get('content'),
        del     = this.displayDelegate,
        level   = this.get('outlineLevel'),
        indent  = this.get('outlineIndent'),
        key, value, working ;
    
    // add alternating row classes
    context.addClass((this.get('contentIndex')%2 === 0) ? 'even' : 'odd');
    context.setClass('disabled', !this.get('isEnabled'));

    // outline level wrapper
    working = context.begin("div").addClass("sc-outline");
    if (level>=0 && indent>0) working.addStyle("left", indent*(level+1));

    // handle disclosure triangle
    value = this.get('disclosureState');
    if (value !== SC.LEAF_NODE) {
      this.renderDisclosure(working, value);
      context.addClass('has-disclosure');
    }
    
    
    // handle checkbox
    key = this.getDelegateProperty('contentCheckboxKey', del) ;
    if (key) {
      value = content ? (content.get ? content.get(key) : content[key]) : NO ;
      this.renderCheckbox(working, value);
      context.addClass('has-checkbox');
    }
    
    // handle icon
    if (this.getDelegateProperty('hasContentIcon', del)) {
      key = this.getDelegateProperty('contentIconKey', del) ;
      value = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
      
      this.renderIcon(working, value);
      context.addClass('has-icon');
    }
    
    // handle label -- always invoke
    key = this.getDelegateProperty('contentValueKey', del) ;
    value = (key && content) ? (content.get ? content.get(key) : content[key]) : content ;
    if (value && SC.typeOf(value) !== SC.T_STRING) value = value.toString();
    if (this.get('escapeHTML')) value = SC.RenderContext.escapeHTML(value);
    this.renderLabel(working, value);

    // handle right icon
    if (this.getDelegateProperty('hasContentRightIcon', del)) {
      key = this.getDelegateProperty('contentRightIconKey', del) ;
      value = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
      
      this.renderRightIcon(working, value);
      context.addClass('has-right-icon');
    }
    
    // handle unread count
    key = this.getDelegateProperty('contentUnreadCountKey', del) ;
    value = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
    if (!SC.none(value) && (value !== 0)) {
      this.renderCount(working, value) ;
      var digits = ['zero', 'one', 'two', 'three', 'four', 'five'];
      var digit = (value.toString().length < digits.length) ? digits[value.toString().length] : digits[digits.length-1];
      context.addClass('has-count %@-digit'.fmt(digit));
    }
    
    // handle action 
    key = this.getDelegateProperty('listItemActionProperty', del) ;
    value = (key && content) ? (content.get ? content.get(key) : content[key]) : null ;
    if (value) {
      this.renderAction(working, value);
      context.addClass('has-action');
    }
    
    // handle branch
    if (this.getDelegateProperty('hasContentBranch', del)) {
      key = this.getDelegateProperty('contentIsBranchKey', del);
      value = (key && content) ? (content.get ? content.get(key) : content[key]) : NO ;
      this.renderBranch(working, value);
      context.addClass('has-branch');
    }
    
    context = working.end();
  },
  
  /**
    Adds a disclosure triangle with the appropriate display to the content.
    This method will only be called if the disclosure state of the view is
    something other than SC.LEAF_NODE.

    @param {SC.RenderContext} context the render context
    @param {Boolean} state YES, NO or SC.MIXED_STATE
    @returns {void}
  */
  renderDisclosure: function(context, state) {
    var key = (state === SC.BRANCH_OPEN) ? "open" : "closed",
        cache = this._scli_disclosureHtml,
        html, tmp;
        
    if (!cache) cache = this.constructor.prototype._scli_disclosureHtml = {};
    html = cache[key];

    if (!html) {
      html = cache[key] = '<img src="%@" class="disclosure button %@" />'.fmt(SC.BLANK_IMAGE_URL, key);
    }
    
    context.push(html);
  },
  
  /**
    Adds a checkbox with the appropriate state to the content.  This method
    will only be called if the list item view is supposed to have a 
    checkbox.
    
    @param {SC.RenderContext} context the render context
    @param {Boolean} state YES, NO or SC.MIXED_STATE
    @returns {void}
  */
  renderCheckbox: function(context, state) {
    
    var key = (state === SC.MIXED_STATE) ? "mixed" : state ? "sel" : "nosel",
        cache = this._scli_checkboxHtml,
        isEnabled = this.get('contentIsEditable') && this.get('isEnabled'),
        html, tmp;
        
    if (!isEnabled) key = SC.keyFor('disabled', key);
    if (!cache) cache = this.constructor.prototype._scli_checkboxHtml = {};
    html = cache[key];
    
    if (!html) {
      tmp = SC.RenderContext('a').attr('href', 'javascript:;')
        .classNames(SC.CheckboxView.prototype.classNames);

      // set state on html
      if (state === SC.MIXED_STATE) tmp.addClass('mixed');
      else tmp.setClass('sel', state);
      
      // disabled
      tmp.setClass('disabled', !isEnabled);

      // now add inner content.  note we do not add a real checkbox because
      // we don't want to have to setup a change observer on it.
      tmp.push('<span class="button"></span>');

      // apply edit
      html = cache[key] = tmp.join();
    }
    
    context.push(html);
  },
  
  /** 
    Generates an icon for the label based on the content.  This method will
    only be called if the list item view has icons enabled.  You can override
    this method to display your own type of icon if desired.
    
    @param {SC.RenderContext} context the render context
    @param {String} icon a URL or class name.
    @returns {void}
  */
  renderIcon: function(context, icon){
    // get a class name and url to include if relevant
    var url = null, className = null ;
    if (icon && SC.ImageView.valueIsUrl(icon)) {
      url = icon; className = '' ;
    } else {
      className = icon; url = SC.BLANK_IMAGE_URL ;
    }
    
    // generate the img element...
    context.begin('img')
      .addClass('icon').addClass(className)
      .attr('src', url)
    .end();
  },
  
  /** 
   Generates a label based on the content.  You can override this method to 
   display your own type of icon if desired.
   
   @param {SC.RenderContext} context the render context
   @param {String} label the label to display, already HTML escaped.
   @returns {void}
  */
  renderLabel: function(context, label) {
    context.push('<label>', label || '', '</label>') ;
  },
  
  /**
    Finds and retrieves the element containing the label.  This is used
    for inline editing.  The default implementation returns a CoreQuery
    selecting any label elements.   If you override renderLabel() you 
    probably need to override this as well.
  
    @returns {SC.CoreQuery} CQ object selecting label elements
  */
  $label: function() {
    return this.$('label') ;
  },

  /** 
    Generates a right icon for the label based on the content.  This method will
    only be called if the list item view has icons enabled.  You can override
    this method to display your own type of icon if desired.
    
    @param {SC.RenderContext} context the render context
    @param {String} icon a URL or class name.
    @returns {void}
  */
  renderRightIcon: function(context, icon){
    // get a class name and url to include if relevant
    var url = null, className = null ;
    if (icon && SC.ImageView.valueIsUrl(icon)) {
      url = icon; className = '' ;
    } else {
      className = icon; url = SC.BLANK_IMAGE_URL ;
    }
    
    // generate the img element...
    context.begin('img')
      .addClass('right-icon').addClass(className)
      .attr('src', url)
    .end();
  },
  
  /** 
   Generates an unread or other count for the list item.  This method will
   only be called if the list item view has counts enabled.  You can 
   override this method to display your own type of counts if desired.
   
   @param {SC.RenderContext} context the render context
   @param {Number} count the count
   @returns {void}
  */
  renderCount: function(context, count) {
    context.push('<span class="count"><span class="inner">')
      .push(count.toString()).push('</span></span>') ;
  },
  
  /**
    Generates the html string used to represent the action item for your 
    list item.  override this to return your own custom HTML
    
    @param {SC.RenderContext} context the render context
    @param {String} actionClassName the name of the action item
    @returns {void}
  */
  renderAction: function(context, actionClassName){
    context.push('<img src="',SC.BLANK_IMAGE_URL,'" class="action" />');
  },
  
  /**
   Generates the string used to represent the branch arrow. override this to 
   return your own custom HTML
   
   @param {SC.RenderContext} context the render context
   @param {Boolean} hasBranch YES if the item has a branch
   @returns {void}
  */
  renderBranch: function(context, hasBranch) {
    context.begin('span').addClass('branch')
      .addClass(hasBranch ? 'branch-visible' : 'branch-hidden')
      .push('&nbsp;').end();
  },
  
  /** 
    Determines if the event occured inside an element with the specified
    classname or not.
  */
  _isInsideElementWithClassName: function(className, evt) {
    var layer = this.get('layer');
    if (!layer) return NO ; // no layer yet -- nothing to do
    
    var el = SC.$(evt.target) ;
    var ret = NO, classNames ;
    while(!ret && el.length>0 && (el.get(0) !== layer)) {
      if (el.hasClass(className)) ret = YES ;
      el = el.parent() ;
    }
    el = layer = null; //avoid memory leaks
    return ret ;
  },
  
  /** @private
    Returns YES if the list item has a checkbox and the event occurred 
    inside of it.
  */
  _isInsideCheckbox: function(evt) {
    var del = this.displayDelegate ;
    var checkboxKey = this.getDelegateProperty('contentCheckboxKey', del) ;
    return checkboxKey && this._isInsideElementWithClassName('sc-checkbox-view', evt);
  },
  
  /** @private 
    Returns YES if the list item has a disclosure triangle and the event 
    occurred inside of it.
  */
  _isInsideDisclosure: function(evt) {
    if (this.get('disclosureState')===SC.LEAF_NODE) return NO;
    return this._isInsideElementWithClassName('disclosure', evt);
  },
  
  
  /** @private 
  mouseDown is handled only for clicks on the checkbox view or or action
  button.
  */
  mouseDown: function(evt) {
    
    // if content is not editable, then always let collection view handle the
    // event.
    if (!this.get('contentIsEditable')) return NO ; 
    
    // if occurred inside checkbox, item view should handle the event.
    if (this._isInsideCheckbox(evt)) {
      this._addCheckboxActiveState() ;
      this._isMouseDownOnCheckbox = YES ;
      this._isMouseInsideCheckbox = YES ;
      return YES ; // listItem should handle this event

    } else if (this._isInsideDisclosure(evt)) {
      this._addDisclosureActiveState();
      this._isMouseDownOnDisclosure = YES;
      this._isMouseInsideDisclosure = YES ;
      return YES;

    }
    
    return NO ; // let the collection view handle this event
  },
  
  mouseUp: function(evt) {
    var ret= NO, del, checkboxKey, content, state, idx, set;

    // if mouse was down in checkbox -- then handle mouse up, otherwise 
    // allow parent view to handle event.
    if (this._isMouseDownOnCheckbox) {
   
      // update only if mouse inside on mouse up...
      if (this._isInsideCheckbox(evt)) {
        del = this.displayDelegate ;
        checkboxKey = this.getDelegateProperty('contentCheckboxKey', del);
        content = this.get('content') ;
        if (content && content.get) {
          var value = content.get(checkboxKey) ;
          value = (value === SC.MIXED_STATE) ? YES : !value ;
          content.set(checkboxKey, value) ; // update content
          this.displayDidChange(); // repaint view...
        }
      }
 
      this._removeCheckboxActiveState() ;
      ret = YES ;
    
    // if mouse as down on disclosure -- handle mosue up.  otherwise pass on
    // to parent.
    } else if (this._isMouseDownOnDisclosure) {
      if (this._isInsideDisclosure(evt)) {
        state = this.get('disclosureState');
        idx   = this.get('contentIndex');
        set   = (!SC.none(idx)) ? SC.IndexSet.create(idx) : null;
        del = this.get('displayDelegate');
        
        if (state === SC.BRANCH_OPEN) {
          if (set && del && del.collapse) del.collapse(set);
          else this.set('disclosureState', SC.BRANCH_CLOSED);
          this.displayDidChange();
          
        } else if (state === SC.BRANCH_CLOSED) {
          if (set && del && del.expand) del.expand(set);
          else this.set('disclosureState', SC.BRANCH_OPEN);
          this.displayDidChange();
        }
      }
     
      this._removeDisclosureActiveState();
      ret = YES ;
    } 
   
    // clear cached info
    this._isMouseInsideCheckbox = this._isMouseDownOnCheckbox = NO ;
    this._isMouseDownOnDisclosure = this._isMouseInsideDisclosure = NO ;
    return ret ;
  },
  
  mouseExited: function(evt) {
   if (this._isMouseDownOnCheckbox) {
     this._removeCheckboxActiveState() ;
     this._isMouseInsideCheckbox = NO ;
     
   } else if (this._isMouseDownOnDisclosure) {
     this._removeDisclosureActiveState();
     this._isMouseInsideDisclosure = NO ;
   }
   return NO ;
  },
  
  mouseEntered: function(evt) {
   if (this._isMouseDownOnCheckbox) {
     this._addCheckboxActiveState() ;
     this._isMouseInsideCheckbox = YES ;
     
   } else if (this._isMouseDownOnDisclosure) {
     this._addDisclosureActiveState();
     this._isMouseInsideDisclosure = YES;
   }
   return NO ;
  },
  
  _addCheckboxActiveState: function() {
   var enabled = this.get('isEnabled');
   this.$('.sc-checkbox-view').setClass('active', enabled);
  },
  
  _removeCheckboxActiveState: function() {
   this.$('.sc-checkbox-view').removeClass('active');
  },

  _addDisclosureActiveState: function() {
   var enabled = this.get('isEnabled');
   this.$('img.disclosure').setClass('active', enabled);
  },
  
  _removeDisclosureActiveState: function() {
   this.$('img.disclosure').removeClass('active');
  },
  
  /**
    Returns true if a click is on the label text itself to enable editing.
  
    Note that if you override renderLabel(), you probably need to override 
    this as well, or just $label() if you only want to control the element
    returned.
  
    @param evt {Event} the mouseUp event.
    @returns {Boolean} YES if the mouse was on the content element itself.
  */
  contentHitTest: function(evt) {
   // if not content value is returned, not much to do.
   var del = this.displayDelegate ;
   var labelKey = this.getDelegateProperty('contentValueKey', del) ;
   if (!labelKey) return NO ;
   
   // get the element to check for.
   var el = this.$label().get(0) ;
   if (!el) return NO ; // no label to check for.
   
   var cur = evt.target, layer = this.get('layer') ;
   while(cur && (cur !== layer) && (cur !== window)) {
     if (cur === el) return YES ;
     cur = cur.parentNode ;
   }
   
   return NO;
  },
  
  beginEditing: function() {
    if (this.get('isEditing')) return YES ;
    //if (!this.get('contentIsEditable')) return NO ;
    return this._beginEditing(YES);
  },
  
  _beginEditing: function(scrollIfNeeded) {
    var content  = this.get('content'),
        del      = this.get('displayDelegate'),
        labelKey = this.getDelegateProperty('contentValueKey', del),
        parent   = this.get('parentView'),
        pf       = parent ? parent.get('frame') : null,
        el       = this.$label(),
        f, v, offset, oldLineHeight, fontSize, top, lineHeight, 
        lineHeightShift, targetLineHeight, ret ;

    // if possible, find a nearby scroll view and scroll into view.
    // HACK: if we scrolled, then wait for a loop and get the item view again
    // and begin editing.  Right now collection view will regenerate the item
    // view too often.
    if (scrollIfNeeded && this.scrollToVisible()) {
      var collectionView = this.get('owner'), idx = this.get('contentIndex');
      this.invokeLater(function() {
        var item = collectionView.itemViewForContentIndex(idx);
        if (item && item._beginEditing) item._beginEditing(NO);
      });
      return YES; // let the scroll happen then begin editing...
    }
    
    // nothing to do...    
    if (!parent || !el || el.get('length')===0) return NO ;
    v = (labelKey && content && content.get) ? content.get(labelKey) : null ;


    f = this.computeFrameWithParentFrame(null);
    offset = SC.viewportOffset(el[0]);

    // if the label has a large line height, try to adjust it to something
    // more reasonable so that it looks right when we show the popup editor.
    oldLineHeight = el.css('lineHeight');
    fontSize = el.css('fontSize');
    top = this.$().css('top');

    if (top) top = parseInt(top.substring(0,top.length-2),0);
    else top =0;

    lineHeight = oldLineHeight;
    lineHeightShift = 0;

    if (fontSize && lineHeight) {
      targetLineHeight = fontSize * 1.5 ;
      if (targetLineHeight < lineHeight) {
        el.css({ lineHeight: '1.5' });
        lineHeightShift = (lineHeight - targetLineHeight) / 2; 
      } else oldLineHeight = null ;
    }

    f.x = offset.x;
    f.y = offset.y+top + lineHeightShift ;
    f.height = el[0].offsetHeight ;
    f.width = el[0].offsetWidth ;

    ret = SC.InlineTextFieldView.beginEditing({
      frame: f, 
      exampleElement: el, 
      delegate: this, 
      value: v,
      multiline: NO,
      isCollection: YES
    }) ;

    // restore old line height for original item if the old line height 
    // was saved.
    if (oldLineHeight) el.css({ lineHeight: oldLineHeight }) ;

    // Done!  If this failed, then set editing back to no.
    return ret ;
  },
  
  commitEditing: function() {
   if (!this.get('isEditing')) return YES ;
   return SC.InlineTextFieldView.commitEditing();
  },
  
  discardEditing: function() {
   if (!this.get('isEditing')) return YES ;
   return SC.InlineTextFieldView.discardEditing();
  },
  
  /** @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) {
   var el = this.$label() ;
   this._oldOpacity = el.css('opacity');
   el.css('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.set('isEditing', NO) ;
    
    var content = this.get('content') ;
    var del = this.displayDelegate ;
    var labelKey = this.getDelegateProperty('contentValueKey', del) ;
    if (labelKey && content && content.set) {
     content.set(labelKey, finalValue) ;
    }
    this.displayDidChange();
  }
  
});

/* >>>>>>>>>> BEGIN source/views/collection.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('mixins/collection_view_delegate') ;
sc_require('views/list_item');

/**
  Special drag operation passed to delegate if the collection view proposes
  to perform a reorder event.
*/
SC.DRAG_REORDER = 0x0010 ;

/** 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' ;

SC.BENCHMARK_RELOAD = NO ;

/**
  @class 

  TODO: Document SC.CollectionView
  
  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
  @extends SC.CollectionContent
  @since SproutCore 0.9
*/
SC.CollectionView = SC.View.extend(
  SC.CollectionViewDelegate,
  SC.CollectionContent,
/** @scope SC.CollectionView.prototype */ {
  
  classNames: ['sc-collection-view'],
  
  ACTION_DELAY: 200,
  
  // ......................................
  // 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 {SC.Array}
  */
  content: null,
  
  /** @private */
  contentBindingDefault: SC.Binding.multiple(),
  
  /**
    The current length of the content.
    
    @property
    @type {Numer}
  */
  length: 0,
  
  /**
    The set of indexes that are currently tracked by the collection view.
    This property is used to determine the range of items the collection view
    should monitor for changes.
    
    The default implementation of this property returns an index set covering
    the entire range of the content.  It changes automatically whenever the
    length changes.
    
    Note that the returned index set for this property will always be frozen.
    To change the nowShowing index set, you must create a new index set and 
    apply it.
    
    @property
    @type {SC.IndexSet}
  */
  nowShowing: function() {
    return this.computeNowShowing();
  }.property('length', 'clippingFrame').cacheable(),
  
  /**
    Indexes of selected content objects.  This SC.SelectionSet is modified 
    automatically by the collection view when the user changes the selection 
    on the collection.
    
    Any item views representing content objects in this set will have their 
    isSelected property set to YES automatically.
    
    @type {SC.SelectionSet}
  */
  selection: 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 selectedIndexes property, they will still be reflected
    visually.
    
    @type {Boolean}
  */
  isSelectable: YES,
  
  /** @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 {Boolean}
  */
  isEnabled: YES,
  
  /** @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.
    
    @type {Boolean}
  */
  isEditable: YES,
  
  /** @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.
    
    @type {Boolean}
  */
  canReorderContent: NO,
  
  /** @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.
    
    @type {Boolean}
  */
  canDeleteContent: NO,
  
  /** @private */
  canDeleteContentBindingDefault: SC.Binding.bool(),
  
  /**
    Allow user to edit the content by double clicking on it or hitting return.
    This will only work if isEditable is YES and the item view implements 
    the beginEditing() method.
    
    @type {Boolean}
  */
  canEditContent: NO,
  
  /** @private */
  canEditContentBindingDefault: 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.
    
    @type {Boolean}
  */
  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 select an item and clicking on it again will deselect 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 YES, 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.
    
    @property {Boolean}
  */  
  actOnSelect: NO,
  
  
  /**
    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.  
    
    @property {Boolean}
  */  
  selectOnMouseDown: YES,
  
  /**
    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.
    
    @property {SC.View}
  */
  exampleView: SC.ListItemView,
  
  /**
    If set, this key will be used to get the example view for a given
    content object.  The exampleView property will be ignored.
    
    @property {String}
  */
  contentExampleViewKey: null,

  /**
    The view class to use when creating new group 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.
    
    If you leave this set to null then the regular example view will be used
    with the isGroupView property set to YES on the item view.
    
    @property {SC.View}
  */
  groupExampleView: null,
  
  /**
    If set, this key will be used to get the example view for a given
    content object.  The groupExampleView property will be ignored.
    
    @property {String}
  */
  contentGroupExampleViewKey: null,
  
  /**
    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.
    
    @property {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.
    
    @property {String|Object}
  */
  target: null,
  
  /** 
    Property 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.
    
    @property {String}
  */
  contentValueKey: null,
  
  /**
    Enables keyboard-based navigate, deletion, etc. if set to true.
  */
  acceptsFirstResponder: NO,
  
  /**
    Changing this property value by default will cause the CollectionView to
    add/remove an 'active' class name to the root element.
    
    @type Boolean
  */
  isActive: NO,
  
  
  /** 
    This property is used to store the calculated height to have 
    a consistent scrolling behavior due to the issues generated by using top
    instead of scrollTop. We could look at the min-height set in the view, but
    to avoid perf hits we simply store it and the scrollView will use it if 
    different than 0.
    
    @type Number
  */  
  calculatedHeight: 0,
  
  /** 
    This property is used to store the calculated width to have 
    a consistent scrolling behavior due to the issues generated by using left
    instead of scrollLeft. We could look at the min-width set in the view, but
    to avoid perf hits we simply store it and the scrollView will use it if 
    different than 0.
    
    @type Number
  */
  calculatedWidth: 0,
  
  
  // ..........................................................
  // SUBCLASS METHODS
  // 
  
  /**
    Override to return the computed layout dimensions of the collection view.
    You can omit any dimensions you don't care about setting in your 
    computed value.
    
    This layout is automatically applied whenever the content changes.
    
    If you don't care about computing the layout at all, you can return null.
    
    @returns {Hash} layout properties
  */
  computeLayout: function() { return null; },
  
  /**
    Override to compute the layout of the itemView for the content at the 
    specified index.  This layout will be applied to the view just before it
    is rendered.
    
    @param {Number} contentIndex the index of content beind rendered by
      itemView
    @returns {Hash} a view layout
  */
  layoutForContentIndex: function(contentIndex) {
    return null ;
  },
  
  /**
    This computed property returns an index set selecting all content indexes.
    It will recompute anytime the length of the collection view changes.
    
    This is used by the default contentIndexesInRect() implementation.
    
    @property {SC.Range}
  */
  allContentIndexes: function() {
    return SC.IndexSet.create(0, this.get('length')).freeze();
  }.property('length').cacheable(),
  
  /**
    Override to return an IndexSet with the indexes that are at least 
    partially visible in the passed rectangle.  This method is used by the 
    default implementation of computeNowShowing() to determine the new 
    nowShowing range after a scroll.
    
    Override this method to implement incremental rendering.
    
    The default simply returns the current content length.
    
    @param {Rect} rect the visible rect
    @returns {SC.IndexSet} now showing indexes
  */
  contentIndexesInRect: function(rect) {
    return null; // select all
  },
  
  /**
    Compute the nowShowing index set.  The default implementation simply 
    returns the full range.  Override to implement incremental rendering.
    
    You should not normally call this method yourself.  Instead get the 
    nowShowing property.
    
    @returns {SC.IndexSet} new now showing range
  */
  computeNowShowing: function() {
    var r = this.contentIndexesInRect(this.get('clippingFrame'));
    if (!r) r = this.get('allContentIndexes'); // default show all

    // make sure the index set doesn't contain any indexes greater than the
    // actual content.
    else {
      var len = this.get('length'), 
          max = r.get('max');
      if (max > len) r = r.copy().remove(len, max-len).freeze();
    }
    
    return r ;
  },
  
  /** 
    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.ClassicView} 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, SC.DROP_AFTER, or SC.DROP_ON
    
    @returns {void}
  */
  showInsertionPoint: function(itemView, dropOperation) {
  },
  
  /**
    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() {
  },
  
  // ..........................................................
  // DELEGATE SUPPORT
  // 
  
  
  /**
    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.
    
    If you do not supply a delegate but the content object you set implements 
    the SC.CollectionViewDelegate mixin, then the content will be 
    automatically set as the delegate.  Usually you will work with a 
    CollectionView in this way rather than setting a delegate explicitly.
    
    @type {SC.CollectionViewDelegate}
  */
  delegate: null,
  
  /**
    The delegate responsible for handling selection changes.  This property
    will be either the delegate, content, or the collection view itself, 
    whichever implements the SC.CollectionViewDelegate mixin.
    
    @property
    @type {Object}
  */
  selectionDelegate: function() {
    var del = this.get('delegate'), content = this.get('content');
    return this.delegateFor('isCollectionViewDelegate', del, content);  
  }.property('delegate', 'content').cacheable(),
  
  /**
    The delegate responsible for providing additional display information 
    about the content.  If you bind a collection view to a controller, this
    the content will usually also be the content delegate, though you 
    could implement your own delegate if you prefer.
    
    @property
    @type {Object}
  */
  contentDelegate: function() {
    var del = this.get('delegate'), content = this.get('content');
    return this.delegateFor('isCollectionContent', del, content);
  }.property('delegate', 'content').cacheable(),
  
  // ..........................................................
  // CONTENT CHANGES
  // 
  
  /**
    Called whenever the content array or an item in the content array or a
    property on an item in the content array changes.  Reloads the appropriate
    item view when the content array itself changes or calls 
    contentPropertyDidChange() if a property changes.
    
    Normally you will not call this method directly though you may override
    it if you need to change the way changes to observed ranges are handled.
    
    @param {SC.Array} content the content array generating the change
    @param {Object} object the changed object
    @param {String} key the changed property or '[]' or an array change
    @param {SC.IndexSet} indexes affected indexes or null for all items
    @returns {void}
  */
  contentRangeDidChange: function(content, object, key, indexes) {
    if (!object && (key === '[]')) {
      this.reload(indexes); // note: if indexes == null, reloads all
    } else {
      this.contentPropertyDidChange(object, key, indexes);
    }
  },

  /**
    Called whenever a property on an item in the content array changes.  This
    is only called if you have set observesContentProperties to YES.
    
    Override this property if you want to do some custom work whenever a 
    property on a content object changes.

    The default implementation does nothing.
    
    @param {Object} target the object that changed
    @param {String} key the property that changed value
    @param {SC.IndexSet} indexes the indexes in the content array affected
    @returns {void}
  */
  contentPropertyDidChange: function(target, key, indexes) {
    // Default Does Nothing
  },
  
  /**
    Called whenever the view needs to updates it's contentRangeObserver to 
    reflect the current nowShowing index set.  You will not usually call this
    method yourself but you may override it if you need to provide some 
    custom range observer behavior.

    Note that if you do implement this method, you are expected to maintain
    the range observer object yourself.  If a range observer has not been
    created yet, this method should create it.  If an observer already exists
    this method should udpate it.
    
    When you create a new range observer, the oberver must eventually call
    contentRangeDidChange() for the collection view to function properly.
    
    If you override this method you probably also need to override 
    destroyRangeObserver() to cleanup any existing range observer.
    
    @returns {void}
  */
  updateContentRangeObserver: function() {
    var nowShowing = this.get('nowShowing'),
        observer   = this._cv_contentRangeObserver,
        content    = this.get('content');
    
    if (!content) return ; // nothing to do
    
    if (observer) {
      content.updateRangeObserver(observer, nowShowing);
    } else {
      var func = this.contentRangeDidChange;
      observer = content.addRangeObserver(nowShowing, this, func, null);      
      this._cv_contentRangeObserver = observer ;
    }
    
  },
  
  /**
    Called whever the view needs to invalidate the current content range 
    observer.  This is called whenever the content array changes.  You will 
    not usually call this method yourself but you may override it if you 
    provide your own range observer behavior.

    Note that if you override this method you should probably also override
    updateRangeObserver() to create or update a range oberver as needed.
    
    @returns {void}
  */
  removeContentRangeObserver: function() {
    var content  = this.get('content'),
        observer = this._cv_contentRangeObserver ;
        
    if (observer) {
      if (content) content.removeRangeObserver(observer);
      this._cv_contentRangeObserver = null ;
    }
  },
    
  /**
    Called whenever the content length changes.  This will invalidate the 
    length property of the view itself causing the nowShowing to recompute
    which will in turn update the UI accordingly.
    
    @returns {void}
  */
  contentLengthDidChange: function() {
    var content = this.get('content');
    this.set('length', content ? content.get('length') : 0);
  },
  
  /** @private
    Whenever content property changes to a new value:
    
     - remove any old observers 
     - setup new observers (maybe wait until end of runloop to do this?)
     - recalc height/reload content
     - set content as delegate if delegate was old content
     - reset selection
     
    Whenever content array mutates:
    
     - possibly stop observing property changes on objects, observe new objs
     - reload effected item views
     - update layout for receiver
  */
  _cv_contentDidChange: function() {
    var content = this.get('content'),
        lfunc   = this.contentLengthDidChange ;

    if (content === this._content) return this; // nothing to do

    // cleanup old content
    this.removeContentRangeObserver();
    if (this._content) {
      this._content.removeObserver('length', this, lfunc);
    }
    
    // cache
    this._content = content;
    
    // add new observers - range observer will be added lazily
    if (content) {
      content.addObserver('length', this, lfunc);
    }
    
    // notify all items changed
    this.contentLengthDidChange();
    this.contentRangeDidChange(content, null, '[]', null);
    
  }.observes('content'),
  
  // ..........................................................
  // ITEM VIEWS
  // 
  
  /** @private
  
    The indexes that need to be reloaded.  Must be one of YES, NO, or an
    SC.IndexSet.
  
  */
  _invalidIndexes: NO,
  
  /** 
    Regenerates the item views for the content items at the specified indexes.
    If you pass null instead of an index set, regenerates all item views.
    
    This method is called automatically whenever the content array changes in
    an observable way, but you can call its yourself also if you need to 
    refresh the collection view for some reason.
    
    Note that if the length of the content is shorter than the child views
    and you call this method, then the child views will be removed no matter
    what the index.
    
    @param {SC.IndexSet} indexes
    @returns {SC.CollectionView} receiver
  */
  reload: function(indexes) {
    var invalid = this._invalidIndexes ;
    if (indexes && invalid !== YES) {
      if (invalid) invalid.add(indexes);
      else invalid = this._invalidIndexes = indexes.clone();

    } else this._invalidIndexes = YES ; // force a total reload
    
    if (this.get('isVisibleInWindow')) this.invokeOnce(this.reloadIfNeeded);
    
    return this ;
  },

  /** 
    Invoked once per runloop to actually reload any needed item views.
    You can call this method at any time to actually force the reload to
    happen immediately if any item views need to be reloaded.
    
    Note that this method will also invoke two other callback methods if you
    define them on your subclass:
    
    - *willReload()* is called just before the items are reloaded
    - *didReload()* is called jsut after items are reloaded
    
    You can use these two methods to setup and teardown caching, which may
    reduce overall cost of a reload.  Each method will be passed an index set
    of items that are reloaded or null if all items are reloaded.
    
    @returns {SC.CollectionView} receiver
  */
  reloadIfNeeded: function() {
    var invalid = this._invalidIndexes;
    if (!invalid || !this.get('isVisibleInWindow')) return this ; // delay
    this._invalidIndexes = NO ;
    
    var content = this.get('content'),
        len     = content ? content.get('length'): 0,
        layout  = this.computeLayout(),
        bench   = SC.BENCHMARK_RELOAD,
        nowShowing = this.get('nowShowing'),
        itemViews  = this._sc_itemViews,
        containerView = this.get('containerView') || this,
        views, idx, cvlen, view, childViews, layer ;

    // if the set is defined but it contains the entire nowShowing range, just
    // replace
    if (invalid.isIndexSet && invalid.contains(nowShowing)) invalid = YES ;
    if (this.willReload) this.willReload(invalid === YES ? null : invalid);

    // if an index set, just update indexes
    if (invalid.isIndexSet) {
      childViews = containerView.get('childViews');
      cvlen = childViews.get('length');
      
      if (bench) {
        SC.Benchmark.start(bench="%@#reloadIfNeeded (Partial)".fmt(this),YES);
      }
      
      invalid.forEach(function(idx) {
        
        // get the existing item view, if there is one
        var existing = itemViews ? itemViews[idx] : null;
        
        // if nowShowing, then reload the item view.
        if (nowShowing.contains(idx)) {
          view = this.itemViewForContentIndex(idx, YES);
          if (existing && existing.parentView === containerView) {
    
            // if the existing view has a layer, remove it immediately from
            // the parent.  This is necessary because the old and new views 
            // will use the same layerId
            layer = existing.get('layer');
            if (layer && layer.parentNode) {
              layer.parentNode.removeChild(layer);  
            } 
            layer = null ; // avoid leaks
            
            containerView.replaceChild(view, existing);
          } else {
            containerView.appendChild(view);
          }
          
        // if not nowShowing, then remove the item view if needed
        } else if (existing && existing.parentView === containerView) {
          delete itemViews[idx];
          
          // if the existing view has a layer, remove it immediately from
          // the parent...
          layer = existing.get('layer');
          if (layer && layer.parentNode) {
            layer.parentNode.removeChild(layer);
          }
          layer = null ; // avoid leaks
          
          containerView.removeChild(existing);
        }
      },this);

      if (bench) SC.Benchmark.end(bench);
      
    // if set is NOT defined, replace entire content with nowShowing
    } else {

      if (bench) {
        SC.Benchmark.start(bench="%@#reloadIfNeeded (Full)".fmt(this),YES);
      }

      // truncate cached item views since they will all be removed from the
      // container anyway.
      if (itemViews) itemViews.length = 0 ; 
      
      views = [];
      nowShowing.forEach(function(idx) {
        views.push(this.itemViewForContentIndex(idx, YES));
      }, this);

      // below is an optimized version of:
      //this.replaceAllChildren(views);
      containerView.beginPropertyChanges();
      containerView.destroyLayer().removeAllChildren();
      containerView.set('childViews', views); // quick swap
      containerView.replaceLayer();
      containerView.endPropertyChanges();
      
      if (bench) SC.Benchmark.end(bench);
      
    }
    
    // adjust my own layout if computed
    if (layout) this.adjust(layout);
    if (this.didReload) this.didReload(invalid === YES ? null : invalid);
    
    
    return this ;
  },
  
  displayProperties: 'isFirstResponder isEnabled isActive'.w(),
  
  /** @private
    If we're asked to render the receiver view for the first time but the 
    child views still need to be added, go ahead and add them.
  */
  render: function(context, firstTime) {
    if (firstTime && this._needsReload) this.reloadIfNeeded ;
    
    // add classes for other state.
    context.setClass('focus', this.get('isFirstResponder'));
    context.setClass('disabled', !this.get('isEnabled'));
    context.setClass('active', this.get('isActive'));

    return arguments.callee.base.apply(this,arguments);
  },
    

  _TMP_ATTRS: {},
  _COLLECTION_CLASS_NAMES: 'sc-collection-item'.w(),
  _GROUP_COLLECTION_CLASS_NAMES: 'sc-collection-item sc-group-item'.w(),
  
  /**
    Returns the item view for the content object at the specified index. Call
    this method instead of accessing child views directly whenever you need 
    to get the view associated with a content index.

    Although this method take two parameters, you should almost always call
    it with just the content index.  The other two parameters are used 
    internally by the CollectionView.
    
    If you need to change the way the collection view manages item views
    you can override this method as well.  If you just want to change the
    default options used when creating item views, override createItemView()
    instead.
  
    Note that if you override this method, then be sure to implement this 
    method so that it uses a cache to return the same item view for a given
    index unless "force" is YES.  In that case, generate a new item view and
    replace the old item view in your cache with the new item view.

    @param {Number} idx the content index
    @param {Boolean} rebuild internal use only
    @returns {SC.View} instantiated view
  */
  itemViewForContentIndex: function(idx, rebuild) {

    // return from cache if possible
    var content   = this.get('content'),
        itemViews = this._sc_itemViews,
        item = content.objectAt(idx),
        del  = this.get('contentDelegate'),
        groupIndexes = del.contentGroupIndexes(this, content),
        isGroupView = NO,
        key, ret, E, layout, layerId;

    // use cache if available
    if (!itemViews) itemViews = this._sc_itemViews = [] ;
    if (!rebuild && (ret = itemViews[idx])) return ret ; 

    // otherwise generate...
    
    // first, determine the class to use
    isGroupView = groupIndexes && groupIndexes.contains(idx);
    if (isGroupView) isGroupView = del.contentIndexIsGroup(this, content,idx);
    if (isGroupView) {
      key  = this.get('contentGroupExampleViewKey');
      if (key && item) E = item.get(key);
      if (!E) E = this.get('groupExampleView') || this.get('exampleView');

    } else {
      key  = this.get('contentExampleViewKey');
      if (key && item) E = item.get(key);
      if (!E) E = this.get('exampleView');
    }

    // collect some other state
    var attrs = this._TMP_ATTRS;
    attrs.contentIndex = idx;
    attrs.content      = item ;
    attrs.owner        = attrs.displayDelegate = this;
    attrs.parentView   = this.get('containerView') || this ;
    attrs.page         = this.page ;
    attrs.layerId      = this.layerIdFor(idx, item);
    attrs.isEnabled    = del.contentIndexIsEnabled(this, content, idx);
    attrs.isSelected   = del.contentIndexIsSelected(this, content, idx);
    attrs.outlineLevel = del.contentIndexOutlineLevel(this, content, idx);
    attrs.disclosureState = del.contentIndexDisclosureState(this, content, idx);
    attrs.isGroupView  = isGroupView;
    attrs.isVisibleInWindow = this.isVisibleInWindow;
    if (isGroupView) attrs.classNames = this._GROUP_COLLECTION_CLASS_NAMES;
    else attrs.classNames = this._COLLECTION_CLASS_NAMES;
    
    layout = this.layoutForContentIndex(idx);
    if (layout) {
      attrs.layout = layout;
    } else {
      delete attrs.layout ;
    }
    
    ret = this.createItemView(E, idx, attrs);
    itemViews[idx] = ret ;
    return ret ;
  },
  
  /**
    Helper method for getting the item view of a specific content object
    
    @param {Object} object
  */
  itemViewForContentObject: function(object) {
    return this.itemViewForContentIndex(this.get('content').indexOf(object));
  },
  
  _TMP_LAYERID: [],
  
  /**
    Primitive to instantiate an item view.  You will be passed the class 
    and a content index.  You can override this method to perform any other
    one time setup.

    Note that item views may be created somewhat frequently so keep this fast.

    *IMPORTANT:* The attrs hash passed is reused each time this method is 
    called.   If you add properties to this hash be sure to delete them before
    returning from this method.
    
    @param {Class} exampleClass example view class
    @param {Number} idx the content index
    @param {Hash} attrs expected attributes
    @returns {SC.View} item view instance
  */ 
  createItemView: function(exampleClass, idx, attrs) {
    return exampleClass.create(attrs);
  },

  /**
    Generates a layerId for the passed index and item.  Usually the default
    implementation is suitable.
    
    @param {Number} idx the content index
    @returns {String} layer id, must be suitable for use in HTML id attribute
  */
  layerIdFor: function(idx) {  
    var ret = this._TMP_LAYERID;
    ret[0] = SC.guidFor(this);
    ret[1] = idx;
    return ret.join('-');
  },
  
  /**
    Extracts the content index from the passed layerID.  If the layer id does
    not belong to the receiver or if no value could be extracted, returns NO.
    
    @param {String} id the layer id
  */
  contentIndexForLayerId: function(id) {
    if (!id || !(id = id.toString())) return null ; // nothing to do
    
    var base = this._baseLayerId;
    if (!base) base = this._baseLayerId = SC.guidFor(this)+"-";
    
    // no match
    if ((id.length <= base.length) || (id.indexOf(base) !== 0)) return null ; 
    var ret = Number(id.slice(id.lastIndexOf('-')+1));
    return isNaN(ret) ? null : ret ;
  },
  

  /** 
    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 {SC.Event} evt An event
    @returns {SC.View} the item view or null
  */
  itemViewForEvent: function(evt) {
    var responder = this.getPath('pane.rootResponder') ;
    if (!responder) return null ; // fast path
    
    var base    = SC.guidFor(this) + '-',
        baseLen = base.length,
        element = evt.target,
        layer   = this.get('layer'),
        contentIndex = null,
        id, itemView, ret ;
        
    // walk up the element hierarchy until we find this or an element with an
    // id matching the base guid (i.e. a collection item)
    while (element && element !== document && element !== layer) {
      id = element ? SC.$(element).attr('id') : null ;
      if (id && (contentIndex = this.contentIndexForLayerId(id)) !== null) {
          break;
      }
      element = element.parentNode ; 
    }
    
    // no matching element found? 
    if (contentIndex===null || (element === layer)) {
      element = layer = null; // avoid memory leaks 
      return null;    
    }
    
    // okay, found the DOM node for the view, go ahead and create it
    // first, find the contentIndex
    if (contentIndex >= this.get('length')) {
      throw "layout for item view %@ was found when item view does not exist (%@)".fmt(id, this);
    }
    
    return this.itemViewForContentIndex(contentIndex);
  },
  
  // ..........................................................
  // DISCLOSURE SUPPORT
  // 
  
  /**
    Expands any items in the passed selection array that have a disclosure
    state.
    
    @param {SC.IndexSet} indexes the indexes to expand
    @returns {SC.CollectionView} receiver
  */
  expand: function(indexes) {
    if (!indexes) return this; // nothing to do
    var del     = this.get('contentDelegate'),
        content = this.get('content');
        
    indexes.forEach(function(i) { 
      var state = del.contentIndexDisclosureState(this, content, i);
      if (state === SC.BRANCH_CLOSED) del.contentIndexExpand(this,content,i);
    }, this);
    return this;
  },

  /**
    Collapses any items in the passed selection array that have a disclosure
    state.
    
    @param {SC.IndexSet} indexes the indexes to expand
    @returns {SC.CollectionView} receiver
  */
  collapse: function(indexes) {
    if (!indexes) return this; // nothing to do
    var del     = this.get('contentDelegate'),
        content = this.get('content');
        
    indexes.forEach(function(i) { 
      var state = del.contentIndexDisclosureState(this, content, i);
      if (state === SC.BRANCH_OPEN) del.contentIndexCollapse(this,content,i);
    }, this);
    return this;
  },
  
  // ..........................................................
  // SELECTION SUPPORT
  // 
  
  /** @private 

    Called whenever the selection object is changed to a new value.  Begins
    observing the selection for changes.
    
  */
  _cv_selectionDidChange: function() {  
    var sel  = this.get('selection'),
        last = this._cv_selection,
        func = this._cv_selectionContentDidChange;
        
    if (sel === last) return this; // nothing to do
    if (last) last.removeObserver('[]', this, func);
    if (sel) sel.addObserver('[]', this, func);
    
    this._cv_selection = sel ;
    this._cv_selectionContentDidChange();
  }.observes('selection'),

  /** @private
  
    Called whenever the selection object or its content changes.  This will
    repaint any items that changed their selection state.
  
  */
  _cv_selectionContentDidChange: function() {
    var sel  = this.get('selection'),
        last = this._cv_selindexes, // clone of last known indexes
        content = this.get('content'),
        diff ;

    // save new last
    this._cv_selindexes = sel ? sel.frozenCopy() : null;

    // determine which indexes are now invalid
    if (last) last = last.indexSetForSource(content);
    if (sel) sel = sel.indexSetForSource(content);
    
    if (sel && last) diff = sel.without(last).add(last.without(sel));
    else diff = sel || last;

    if (diff && diff.get('length')>0) this.reloadSelectionIndexes(diff);
  },
  
  /** @private
    Contains the current item views that need their selection to be repainted.
    This may be either NO, YES, or an IndexSet.
  */
  _invalidSelection: NO,
  
  /**
    Called whenever the selection changes.  The passed index set will contain
    any affected indexes including those indexes that were previously 
    selected and now should be deselected.
    
    Pass null to reload the selection state for all items.
    
    @param {SC.IndexSet} indexes affected indexes
    @returns {SC.CollectionView} reciever
  */
  reloadSelectionIndexes: function(indexes) {
    var invalid = this._invalidSelection ;
    if (indexes && (invalid !== YES)) {
      if (invalid) { invalid.add(indexes) ; }
      else { invalid = this._invalidSelection = indexes.copy(); }

    } else this._invalidSelection = YES ; // force a total reload
    
    if (this.get('isVisibleInWindow')) {
      this.invokeOnce(this.reloadSelectionIndexesIfNeeded);
    } 
    
    return this ;
  },

  /**
    Reloads the selection state if needed on any dirty indexes.  Normally this
    will run once at the end of the runloop, but you can force the item views
    to reload their selection immediately by calling this method.
    
    You can also override this method if needed to change the way the 
    selection is reloaded on item views.  The default behavior will simply
    find any item views in the nowShowing range that are affected and 
    modify them.
    
    @returns {SC.CollectionView} receiver
  */
  reloadSelectionIndexesIfNeeded: function() {
    var invalid = this._invalidSelection;
    if (!invalid || !this.get('isVisibleInWindow')) return this ; 

    var nowShowing = this.get('nowShowing'),
        reload     = this._invalidIndexes,
        content    = this.get('content'),
        sel        = this.get('selection');
    
    this._invalidSelection = NO; // reset invalid
    
    // fast path.  if we are going to reload everything anyway, just forget
    // about it.  Also if we don't have a nowShowing, nothing to do.
    if (reload === YES || !nowShowing) return this ;
    
    // if invalid is YES instead of index set, just reload everything 
    if (invalid === YES) invalid = nowShowing;

    // if we will reload some items anyway, don't bother
    if (reload && reload.isIndexSet) invalid = invalid.without(reload);

    // iterate through each item and set the isSelected state.
    invalid.forEach(function(idx) {
      if (!nowShowing.contains(idx)) return; // not showing
      var view = this.itemViewForContentIndex(idx, NO);
      if (view) view.set('isSelected', sel ? sel.contains(content, idx) : NO);
    },this);
    
    return this ;
  },
  
  /** 
    Selection primitive.  Selects the passed IndexSet of items, optionally 
    extending the current selection.  If extend is NO or not passed then this
    will replace the selection with the passed value.  Otherwise the indexes
    will be added to the current selection.
    
    @param {Number|SC.IndexSet} indexes index or indexes to select
    @param extend {Boolean} optionally extend the selection
    @returns {SC.CollectionView} receiver
  */
  select: function(indexes, extend) {

    var content = this.get('content'),
        del     = this.get('selectionDelegate'),
        cdel    = this.get('contentDelegate'),
        groupIndexes = cdel.contentGroupIndexes(this, content),
        sel;
        
    if(!this.get('isSelectable')) return this;

    // normalize
    if (SC.typeOf(indexes) === SC.T_NUMBER) {
      indexes = SC.IndexSet.create(indexes, 1);
    }

    // if we are passed an empty index set or null, clear the selection.
    if (indexes && indexes.get('length')>0) {

      // first remove any group indexes - these can never be selected
      if (groupIndexes && groupIndexes.get('length')>0) {
        indexes = indexes.copy().remove(groupIndexes);
      }
      
      // give the delegate a chance to alter the items
      indexes = del.collectionViewShouldSelectIndexes(this, indexes, extend);
      if (!indexes || indexes.get('length')===0) return this; // nothing to do
    
    } else indexes = null;

    // build the selection object, merging if needed
    if (extend && (sel = this.get('selection'))) sel = sel.copy();
    else sel = SC.SelectionSet.create();
    
    if (indexes && indexes.get('length')>0) {

      // when selecting only one item, always select by content
      if (indexes.get('length')===1) {
        sel.addObject(content.objectAt(indexes.get('firstObject')));
        
      // otherwise select an index range
      } else sel.add(content, indexes);
      
    }

    // give delegate one last chance
    sel = del.collectionViewSelectionForProposedSelection(this, sel);
    if (!sel) sel = SC.SelectionSet.create(); // empty
    
    // if we're not extending the selection, clear the selection anchor
    this._selectionAnchor = null ;
    this.set('selection', sel.freeze()) ;  
    return this;
  },
  
  /** 
    Primtive to remove the indexes from the selection.  
    
    @param {Number|SC.IndexSet} indexes index or indexes to select
    @returns {SC.CollectionView} receiver
  */
  deselect: function(indexes) {

    var sel     = this.get('selection'),
        content = this.get('content'),
        del     = this.get('selectionDelegate');
        
    if(!this.get('isSelectable')) return this;
    if (!sel || sel.get('length')===0) return this; // nothing to do
        
    // normalize
    if (SC.typeOf(indexes) === SC.T_NUMBER) {
      indexes = SC.IndexSet.create(indexes, 1);
    }

    // give the delegate a chance to alter the items
    indexes = del.collectionViewShouldDeselectIndexes(this, indexes) ;
    if (!indexes || indexes.get('length')===0) return this; // nothing to do

    // now merge change - note we expect sel && indexes to not be null
    sel = sel.copy().remove(content, indexes);
    sel = del.collectionViewSelectionForProposedSelection(this, sel);
    if (!sel) sel = SC.SelectionSet.create(); // empty

    this.set('selection', sel.freeze()) ;
    return this ;
  },
  
  /** @private
   Finds the next selectable item, up to content length, by asking the
   delegate. If a non-selectable item is found, the index is skipped. If
   no item is found, selection index is returned unmodified.

   Return value will always be in the range of the bottom of the current 
   selection index and the proposed index.   
   
   @param {Number} proposedIndex the desired index to select
   @param {Number} bottom optional bottom of selection use as fallback
   @returns {Number} next selectable index. 
  */
  _findNextSelectableItemFromIndex: function(proposedIndex, bottom) {
    
    var lim     = this.get('length'),
        range   = SC.IndexSet.create(), 
        content = this.get('content'),
        del     = this.get('selectionDelegate'),
        cdel    = this.get('contentDelegate'),
        groupIndexes = cdel.contentGroupIndexes(this, content),
        ret, sel ;

    // fast path
    if (!groupIndexes && (del.collectionViewShouldSelectIndexes === this.collectionViewShouldSelectIndexes)) {
      return proposedIndex;
    }

    // loop forwards looking for an index that is allowed by delegate
    // we could alternatively just pass the whole range but this might be 
    // slow for the delegate
    while (proposedIndex < lim) {
      if (!groupIndexes || !groupIndexes.contains(proposedIndex)) {
        range.add(proposedIndex);
        ret = del.collectionViewShouldSelectIndexes(this, range);
        if (ret && ret.get('length') >= 1) return proposedIndex ;
        range.remove(proposedIndex);
      }
      proposedIndex++;      
    }

    // if nothing was found, return top of selection
    if (bottom === undefined) {
      sel = this.get('selection');
      bottom = sel ? sel.get('max') : -1 ;
    }
    return bottom ;
  },
  
  /** @private
   Finds the previous selectable item, up to the first item, by asking the
   delegate. If a non-selectable item is found, the index is skipped. If
   no item is found, selection index is returned unmodified.
   
   @param {Integer} proposedIndex the desired index to select
   @returns {Integer} the previous selectable index. This will always be in the range of the top of the current selection index and the proposed index.
   @private
  */
  _findPreviousSelectableItemFromIndex: function(proposedIndex, top) {
    var range   = SC.IndexSet.create(), 
        content = this.get('content'),
        del     = this.get('selectionDelegate'),
        cdel    = this.get('contentDelegate'),
        groupIndexes = cdel.contentGroupIndexes(this, content),
        ret ;

    if (SC.none(proposedIndex)) proposedIndex = -1;
    
    // fast path
    if (!groupIndexes && (del.collectionViewShouldSelectIndexes === this.collectionViewShouldSelectIndexes)) {
      return proposedIndex;
    }

    // loop backwards looking for an index that is allowed by delegate
    // we could alternatively just pass the whole range but this might be 
    // slow for the delegate
    while (proposedIndex >= 0) {
      if (!groupIndexes || !groupIndexes.contains(proposedIndex)) {
        range.add(proposedIndex);
        ret = del.collectionViewShouldSelectIndexes(this, range);
        if (ret && ret.get('length') >= 1) return proposedIndex ;
        range.remove(proposedIndex);
      }
      proposedIndex--;      
    }

    // if nothing was found, return top of selection
    if (top === undefined) {
      var sel = this.get('selection');
      top = sel ? sel.get('min') : -1 ;
    }
    if (SC.none(top)) top = -1;
    return top ;
  },
  
  /**
    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 {SC.CollectionView} receiver
  */
  selectPreviousItem: function(extend, numberOfItems) {
    if (SC.none(numberOfItems)) numberOfItems = 1 ;
    if (SC.none(extend)) extend = false ;
    
    var sel     = this.get('selection'),
        content = this.get('content');
    if (sel) sel = sel.indexSetForSource(content);
    
    var selTop    = sel ? sel.get('min') : -1,
        selBottom     = sel ? sel.get('max')-1 : -1,
        anchor        = this._selectionAnchor;
    if (SC.none(anchor)) anchor = selTop;

    // if extending, then we need to do some fun stuff to build the array
    if (extend) {

      // 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 = this._findPreviousSelectableItemFromIndex(selTop - numberOfItems);
      }
      
      // Ensure we are not out of bounds
      if (SC.none(selTop) || (selTop < 0)) selTop = 0 ;
      if (selBottom < selTop) selBottom = selTop ;
      
    // if not extending, just select the item previous to the selTop
    } else {
      selTop = this._findPreviousSelectableItemFromIndex(selTop - numberOfItems);
      if (SC.none(selTop) || (selTop < 0)) selTop = 0 ;
      selBottom = selTop ;
      anchor = null ;
    }
    
    var scrollToIndex = selTop ;
    
    // now build new selection
    sel = SC.IndexSet.create(selTop, selBottom+1-selTop);
    
    // ensure that the item is visible and set the selection
    this.scrollToContentIndex(scrollToIndex) ;
    this.select(sel) ;
    this._selectionAnchor = anchor ;
    return this ;
  },
  
  /**
    Select one or more items following 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 {SC.CollectionView} receiver
  */
  selectNextItem: function(extend, numberOfItems) {
    if (SC.none(numberOfItems)) numberOfItems = 1 ;
    if (SC.none(extend)) extend = false ;

    var sel     = this.get('selection'),
        content = this.get('content');
    if (sel) sel = sel.indexSetForSource(content);
    
    var selTop    = sel ? sel.get('min') : -1,
        selBottom = sel ? sel.get('max')-1 : -1,
        anchor    = this._selectionAnchor,
        lim       = this.get('length');
        
    if (SC.none(anchor)) anchor = selTop;

    // if extending, then we need to do some fun stuff to build the array
    if (extend) {
      
      // If the selTop is before the anchor, then reduce the selection
      if (selTop < anchor) {
        selTop = selTop + numberOfItems ;
        
      // otherwise, select the next item after the bottom 
      } else {
        selBottom = this._findNextSelectableItemFromIndex(selBottom + numberOfItems, selBottom);
      }
      
      // Ensure we are not out of bounds
      if (selBottom >= lim) selBottom = lim-1;
      if (selTop > selBottom) selTop = selBottom ;
      
    // if not extending, just select the item next to the selBottom
    } else {
      selBottom = this._findNextSelectableItemFromIndex(selBottom + numberOfItems, selBottom);
      
      if (selBottom >= lim) selBottom = lim-1;
      selTop = selBottom ;
      anchor = null ;
    }
    
    var scrollToIndex = selBottom ;
    
    // now build new selection
    sel = SC.IndexSet.create(selTop, selBottom-selTop+1);
    
    // ensure that the item is visible and set the selection
    this.scrollToContentIndex(scrollToIndex) ;
    this.select(sel) ;
    this._selectionAnchor = anchor ;
    return this ;
  },
    
  /**
    Deletes the selected content if canDeleteContent is YES.  This will invoke 
    delegate methods to provide fine-grained control.  Returns YES if the 
    deletion was possible, even if none actually occurred.
    
    @returns {Boolean} YES if deletion is possible.
  */
  deleteSelection: function() {
    // perform some basic checks...
    if (!this.get('canDeleteContent')) return NO;  

    var sel     = this.get('selection'),
        content = this.get('content'),
        del     = this.get('selectionDelegate'),
        indexes = sel&&content ? sel.indexSetForSource(content) : null;
        
    if (!content || !indexes || indexes.get('length') === 0) return NO ;
    
    // let the delegate decide what to actually delete.  If this returns an
    // empty index set or null, just do nothing.
    indexes = del.collectionViewShouldDeleteIndexes(this, indexes);
    if (!indexes || indexes.get('length') === 0) return NO ;
    
    // now have the delegate (or us) perform the deletion. The default 
    // delegate implementation just uses standard SC.Array methods to do the
    // right thing.
    del.collectionViewDeleteContent(this, this.get('content'), indexes);

    return YES ;
  },
  
  // ..........................................................
  // SCROLLING
  // 
  
  /**
    Scroll the rootElement (if needed) to ensure that the item is visible.
    
    @param {Number} contentIndex The index of the item to scroll to
    @returns {SC.CollectionView} receiver
  */
  scrollToContentIndex: function(contentIndex) {
    var itemView = this.itemViewForContentIndex(contentIndex) ;
    if (itemView) this.scrollToItemView(itemView) ;
    return this; 
  },
  
  /**
    Scroll to the passed item view.  If the item view is not visible on screen
    this method will not work.

    @param {SC.View} view The item view to scroll to
    @returns {SC.CollectionView} receiver
  */
  scrollToItemView: function(view) {
    if (view) view.scrollToVisible();
    return this ;
  },

  // ..........................................................
  // KEYBOARD EVENTS
  // 
  
  /** @private */
  keyDown: function(evt) {
    var ret = this.interpretKeyEvents(evt) ;
    return !ret ? NO : ret ;
  },
  
  /** @private */
  keyUp: function() { return true; },
  
  /** @private
    Handle space key event.  Do action
  */
  insertText: function(chr, evt) {
    if (chr === ' ') {
      var sel = this.get('selection');
      if (sel && sel.get('length')>0) {
        this.invokeLater(this._cv_action, 0, null, evt);
      } 
      return YES ;
    } else return NO ;
  },
  
  /** @private
    Handle select all keyboard event.
  */
  selectAll: function(evt) {
    var content = this.get('content'),
        sel = content ? SC.IndexSet.create(0, content.get('length')) : null;
    this.select(sel, 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) ;
    this._cv_performSelectAction(null, evt, this.ACTION_DELAY);
    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) ;
    this._cv_performSelectAction(null, evt, this.ACTION_DELAY);
    return true ;
  },

  /** @private
    Selects the previous item if itemsPerRow > 1.  Otherwise does nothing.
    If item is expandable, will collapse.
  */
  moveLeft: function(sender, evt) {
    if ((this.get('itemsPerRow') || 1) > 1) {
      this.selectPreviousItem(false, 1);
      this._cv_performSelectAction(null, evt, this.ACTION_DELAY);
    
    } else {
      var sel     = this.get('selection'),
          content = this.get('content'),
          indexes = sel ? sel.indexSetForSource(content) : null;
    
      // Collapse the element if it is expanded.  However, if there is exactly
      // one item selected and the item is already collapsed or is a leaf
      // node, then select the (expanded) parent element instead as a
      // convenience to the user.
      if ( indexes ) {
        var del          = undefined,     // We'll load it lazily
            selectParent = false,
            index        = undefined;

        if ( indexes.get('length') === 1 ) {
          index = indexes.get('firstObject');
          del = this.get('contentDelegate');
          var state = del.contentIndexDisclosureState(this, content, index);
          if (state !== SC.BRANCH_OPEN) selectParent = true;
        }
    
        if ( selectParent ) {
          // TODO:  PERFORMANCE:  It would be great to have a function like
          //        SC.CollectionView.selectParentItem() or something similar
          //        for performance reasons.  But since we don't currently
          //        have such a function, let's just iterate through the
          //        previous items until we find the first one with a outline
          //        level of one less than the selected item.
          var desiredOutlineLevel = del.contentIndexOutlineLevel(this, content, index) - 1;
          if ( desiredOutlineLevel >= 0 ) {
            var parentIndex = -1;
            while ( parentIndex < 0 ) {
              var previousItemIndex = this._findPreviousSelectableItemFromIndex(index - 1);
              if (previousItemIndex < 0 ) return false;    // Sanity-check.
              index = previousItemIndex;
              var outlineLevel = del.contentIndexOutlineLevel(this, content, index);
              if ( outlineLevel === desiredOutlineLevel ) {
                parentIndex = previousItemIndex;
              }
            }
          
            // If we found the parent, select it now.
            if ( parentIndex !== -1 ) {
              this.select(index);
            }
          }
        }
        else {
          this.collapse(indexes);
        }
      }
    }
  
    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) ;
      this._cv_performSelectAction(null, evt, this.ACTION_DELAY);
    } else {
      var sel     = this.get('selection'),
          content = this.get('content'),
          indexes = sel ? sel.indexSetForSource(content) : null;
      if (indexes) this.expand(indexes);
    }
    
    return true ;
  },
  
  /** @private */
  moveDownAndModifySelection: function(sender, evt) {
    this.selectNextItem(true, this.get('itemsPerRow') || 1) ;
    this._cv_performSelectAction(null, evt, this.ACTION_DELAY);
    return true ;
  },
  
  /** @private */
  moveUpAndModifySelection: function(sender, evt) {
    this.selectPreviousItem(true, this.get('itemsPerRow') || 1) ;
    this._cv_performSelectAction(null, evt, this.ACTION_DELAY);
    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) ;
      this._cv_performSelectAction(null, evt, this.ACTION_DELAY);
    }
    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) ;
      this._cv_performSelectAction(null, evt, this.ACTION_DELAY);
    }
    return true ;
  },

  
  
  /** @private
    if content value is editable and we have one item selected, then edit.
    otherwise, invoke action.
  */
  insertNewline: function(sender, evt) {
    var canEdit = this.get('isEditable') && this.get('canEditContent'),
        sel, content, set, idx, itemView;
    
    // first make sure we have a single item selected; get idx 
    if (canEdit) {
      sel     = this.get('selection') ;
      content = this.get('content');
      if (sel && sel.get('length') === 1) {
        set = sel.indexSetForSource(content);
        idx = set ? set.get('min') : -1;
        canEdit = idx>=0;
      }
    }
    
    // next find itemView and ensure it supports editing
    if (canEdit) {
      itemView = this.itemViewForContentIndex(idx);
      canEdit = itemView && SC.typeOf(itemView.beginEditing)===SC.T_FUNCTION;
    }
      
    // ok, we can edit..
    if (canEdit) {
      this.scrollToContentIndex(idx);
      itemView = this.itemViewForContentIndex(idx); // just in case
      itemView.beginEditing();
      
    // invoke action 
    } else {
      this.invokeLater(this._cv_action, 0, itemView, null) ;
    }
    
    return YES ; // always handle
  },

  // ..........................................................
  // MOUSE EVENTS
  // 
  
  /** @private
    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.
    
    // Toggle selection only triggers on mouse up.  Do nothing.
    if (this.get('useToggleSelection')) return true;
    
    // 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 itemView      = this.itemViewForEvent(ev),
        content       = this.get('content'),
        contentIndex  = itemView ? itemView.get('contentIndex') : -1, 
        info, anchor ;
        
    info = this.mouseDownInfo = {
      event:        ev,  
      itemView:     itemView,
      contentIndex: contentIndex,
      at:           Date.now()
    };
      
    // 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 (!itemView) {
      if (this.get('allowDeselectAll')) this.select(null, false);
      return YES ;
    }
    
    // collection some basic setup info
    var sel = this.get('selection'), isSelected, modifierKeyPressed;
    if (sel) sel = sel.indexSetForSource(content);
    
    isSelected = sel ? sel.contains(contentIndex) : NO;
    info.modifierKeyPressed = modifierKeyPressed = ev.ctrlKey || ev.metaKey ;
    
    // holding down a modifier key while clicking a selected item should 
    // deselect that item...deselect and bail.
    if (modifierKeyPressed && isSelected) {
      info.shouldDeselect = contentIndex >= 0;

    // if the shiftKey was pressed, then we want to extend the selection
    // from the last selected item
    } else if (ev.shiftKey && sel && sel.get('length') > 0) {
      sel = this._findSelectionExtendedByShift(sel, contentIndex);
      anchor = this._selectionAnchor ; 
      this.select(sel) ;
      this._selectionAnchor = anchor; //save the anchor
      
    // 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) {
      info.shouldReselect = contentIndex >= 0;
      
    // 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.select(contentIndex, modifierKeyPressed);
      } else {
        info.shouldSelect = contentIndex >= 0 ;
      }
    }
    
    // saved for extend by shift ops.
    info.previousContentIndex = contentIndex;
    
    return YES;
  },
  
  /** @private */
  mouseUp: function(ev) {
    
    var view   = this.itemViewForEvent(ev),
        info   = this.mouseDownInfo,
        contentIndex, sel, isSelected, canEdit, itemView, content, idx;
        
    if (this.get('useToggleSelection')) {
      if (!view) return ; // do nothing when clicked outside of elements
      
      // determine if item is selected. If so, then go on.
      sel = this.get('selection') ;
      contentIndex = (view) ? view.get('contentIndex') : -1 ;
      isSelected = sel && sel.include(contentIndex) ;

      if (isSelected) this.deselect(contentIndex) ;
      else this.select(contentIndex, YES) ;
      
    } else if(info) {
      idx = info.contentIndex;
      contentIndex = (view) ? view.get('contentIndex') : -1 ;
      
      // this will be set if the user simply clicked on an unselected item and 
      // selectOnMouseDown was NO.
      if (info.shouldSelect) this.select(idx, info.modifierKeyPressed);
      
      // This is true if the user clicked on a selected item with a modifier
      // key pressed.
      if (info.shouldDeselect) this.deselect(idx);
      
      // 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 (info.shouldReselect) {
        
        //debugger ;
        // - contentValueIsEditable is true
        canEdit = this.get('isEditable') && this.get('canEditContent') ;
        
        // - the user clicked on an item that was already selected
        //   ^ this is the only way shouldReset is set to YES
        
        // - is the only item selected
        if (canEdit) {
          sel = this.get('selection') ;
          canEdit = sel && (sel.get('length') === 1);
        }
        
        // - the item view responds to contentHitTest() and returns YES.
        // - the item view responds to beginEditing and returns YES.
        if (canEdit) {
          itemView = this.itemViewForContentIndex(idx) ;
          canEdit = itemView && (!itemView.contentHitTest || itemView.contentHitTest(ev)) ;
          canEdit = (canEdit && itemView.beginEditing) ? itemView.beginEditing() : NO ;
        }
        
        // if cannot edit, schedule a reselect (but give doubleClick a chance)
        if (!canEdit) {
          if (this._cv_reselectTimer) this._cv_reselectTimer.invalidate() ;
          this._cv_reselectTimer = this.invokeLater(this.select, 300, idx, false) ;
        }
      }
      
      this._cleanupMouseDown() ;
    }

    // handle actions on editing
    this._cv_performSelectAction(view, ev, 0, ev.clickCount);
    
    return NO;  // bubble event to allow didDoubleClick to be called...
  },
  
  /** @private */
  _cleanupMouseDown: function() {
    
    // delete items explicitly to avoid leaks on IE
    var info = this.mouseDownInfo, key;
    if (info) {
      for(key in info) {
        if (!info.hasOwnProperty(key)) continue;
        delete info[key];
      }
    }
    this.mouseDownInfo = null;
  },
  
  /** @private */
  mouseMoved: function(ev) {
    var view = this.itemViewForEvent(ev), 
        last = this._lastHoveredItem ;

    // handle hover events.
    if (view !== last) {
      if (last && last.mouseOut) last.mouseOut(ev);
      if (view && view.mouseOver) view.mouseOver(ev);
    }
    this._lastHoveredItem = view ;

    if (view && view.mouseMoved) view.mouseMoved(ev);
    return YES;
  },
  
  /** @private */
  mouseOut: function(ev) {
    var view = this._lastHoveredItem ;
    this._lastHoveredItem = null ;
    if (view && view.mouseOut) view.mouseOut(ev) ;
    return YES ;
  },
  
  /** @private */
  _findSelectionExtendedByShift: function(sel, contentIndex) {
    
    // fast path.  if we don't have a selection, just select index
    if (!sel || sel.get('length')===0) {
      return SC.IndexSet.create(contentIndex);
    }
    
    // if we do have a selection, then figure out how to extend it.
    var content = this.get('content'),
        lim     = content.get('length')-1,
        min     = sel.get('min'),
        max     = sel.get('max')-1,
        info    = this.mouseDownInfo,
        anchor  = this._selectionAnchor ;
    if (SC.none(anchor)) anchor = -1;

    // clicked before the current selection set... extend it's beginning...
    if (contentIndex < min) {
      min = contentIndex;
      if (anchor<0) this._selectionAnchor = anchor = max; //anchor at end
    
    // clicked after the current selection set... extend it's ending...
    } else if (contentIndex > max) {
      max = contentIndex;
      if (anchor<0) this._selectionAnchor = anchor = min; // anchor at start
    
    // clicked inside the selection set... need to determine where the last
    // selection was and use that as an anchor.
    } else if (contentIndex >= min && contentIndex <= max) {
      if (anchor<0) this._selectionAnchor = anchor = min; //anchor at start
      
      if (contentIndex === anchor) min = max = contentIndex ;
      else if (contentIndex > anchor) {
        min = anchor;
        max = contentIndex ;
      } else if (contentIndex < anchor) {
        min = contentIndex;
        max = anchor ;
      }
    }

    return SC.IndexSet.create(min, max - min + 1);
  },
  
  // ......................................
  // 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.
    
    @property
    @type String
  */
  reorderDataType: function() {
    return 'SC.CollectionView.Reorder.%@'.fmt(SC.guidFor(this)) ;
  }.property().cacheable(),
  
  /**
    This property is set to the IndexSet 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.

    @type SC.IndexSet
  */
  dragContent: null,
  
  /**
    This property is set to the proposed insertion index during a call to
    collectionViewValidateDragOperation().  Your delegate implementations can 
    change the value of this property to enforce a drop some in some other 
    location.
    
    @type Number
  */
  proposedInsertionIndex: null,
  
  /**
    This property is set to the proposed drop operation during a call to
    collectionViewValidateDragOperation().  Your delegate implementations can 
    change the value of this property to enforce a different type of drop 
    operation.
    
    @type Number
    @property
  */
  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) {
    
    var del     = this.get('selectionDelegate'),
        content = this.get('content'),
        sel     = this.get('selection'),
        info    = this.mouseDownInfo,
        cdel    = this.get('contentDelegate'),
        groupIndexes = cdel.contentGroupIndexes(this, content),
        dragContent, dragDataTypes, dragView;
    
    // if the mouse down event was cleared, there is nothing to do; return.
    if (!info || info.contentIndex<0) return YES ;
    
    // Don't do anything unless the user has been dragging for 123msec
    if ((Date.now() - info.at) < 123) return YES ;
    
    // OK, they must be serious, decide if a drag will be allowed.
    if (del.collectionViewShouldBeginDrag(this)) {
      
      // First, get the selection to drag.  Drag an array of selected
      // items appearing in this collection, in the order of the 
      // collection.
      //
      // Compute the dragContent - the indexes we will be dragging.      
      // 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 = SC.IndexSet.create(info.contentIndex);
      } else dragContent = sel ? sel.indexSetForSource(content) : null;
      
      // remove any group indexes.  groups cannot be dragged.
      if (dragContent && groupIndexes && groupIndexes.get('length')>0) {
        dragContent = dragContent.copy().remove(groupIndexes);
        if (dragContent.get('length')===0) dragContent = null;
        else dragContent.freeze();
      }
      
      if (!dragContent) return YES; // nothing to drag
      else dragContent = dragContent.frozenCopy(); // so it doesn't change
      
      dragContent = { content: content, indexes: dragContent };
      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.
      dragDataTypes = this.get('dragDataTypes');
      if (dragDataTypes && dragDataTypes.get('length') > 0) {
        
        // Build the drag view to use for the ghost drag.  This 
        // should essentially contain any visible drag items.
        dragView = del.collectionViewDragViewFor(this, dragContent.indexes);
        if (!dragView) dragView = this._cv_dragViewFor(dragContent.indexes);
        
        // Make sure the dragView has created its layer.
        dragView.createLayer();
        
        // Initiate the drag
        SC.Drag.start({
          event: info.event,
          source: this,
          dragView: dragView,
          ghost: NO,
          ghostActsLikeCursor: del.ghostActsLikeCursor,
          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 ;
    }
  },

  /** @private
    Compute a default drag view by grabbing the raw layers and inserting them
    into a drag view.
  */
  _cv_dragViewFor: function(dragContent) {
    // find only the indexes that are in both dragContent and nowShowing.
    var indexes = this.get('nowShowing').without(dragContent);
    indexes = this.get('nowShowing').without(indexes);
    
    var dragLayer = this.get('layer').cloneNode(false); 
    var view = SC.View.create({ layer: dragLayer, parentView: this });

    // cleanup weird stuff that might make the drag look out of place
    SC.$(dragLayer).css('backgroundColor', 'transparent')
      .css('border', 'none')
      .css('top', 0).css('left', 0);
    
    indexes.forEach(function(i) {
      var itemView = this.itemViewForContentIndex(i),
          isSelected, layer;
        
      // render item view without isSelected state.  
      if (itemView) {
        isSelected = itemView.get('isSelected');
        itemView.set('isSelected', NO);
        
        itemView.updateLayerIfNeeded();
        layer = itemView.get('layer');
        if (layer) layer = layer.cloneNode(true);
        
        itemView.set('isSelected', isSelected);
        itemView.updateLayerIfNeeded();
      }

      if (layer) dragLayer.appendChild(layer);
      layer = null;
      
    }, this);

    dragLayer = null;
    return view ;
  },

  
  /**
    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.
    
    @property 
    @type Array
  */
  dragDataTypes: function() {
    // consult delegate.
    var del = this.get('selectionDelegate'),
        ret = del.collectionViewDragDataTypes(this),
        key ;

    if (this.get('canReorderContent')) {
      ret = ret ? ret.copy() : [];
      key = this.get('reorderDataType');
      if (ret.indexOf(key) < 0) ret.push(key);          
    }
        
    return ret ? ret : [];
  }.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(drag, dataType) {
    
    // 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
    var del = this.get('selectionDelegate');
    return del.collectionViewDragDataForType(this, drag, dataType);
  },
  
  /**
    Implements the SC.DropTarget interface.  The default implementation will
    consult the collection view delegate, if you implement those methods.
    
    This method is called once when the drag enters the view area.  It's 
    return value will be stored on the drag object as allowedDragOperations,
    possibly further constrained by the drag source.
    
    @param {SC.Drag} drag the drag object
    @param {SC.Event} evt the event triggering this change, if available
    @returns {Number} logical OR'd mask of allowed drag operations.
  */
  computeDragOperations: 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,
        del = this.get('selectionDelegate');

    if (this.get('canReorderContent')) {
      if (drag.get('dataTypes').indexOf(this.get('reorderDataType')) >= 0) {
        op = SC.DRAG_REORDER ;
      }
    }
    
    // Now pass this onto the delegate.
    op = del.collectionViewComputeDragOperations(this, drag, op);
    if (op & SC.DRAG_REORDER) op = SC.DRAG_MOVE ;
    
    return op ;
  },
  
  /** @private
    Determines the allowed drop operation insertion point, operation type,
    and the drag operation to be performed.  Used by dragUpdated() and 
    performDragOperation().

    @param {SC.Drag} drag the drag object
    @param {SC.Event} evt source of this request, if available
    @param {Number} dragOp allowed drag operation mask
    Returns three params: [drop index, drop operation, allowed drag ops]
  */
  _computeDropOperationState: function(drag, evt, dragOp) {
    
    // 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    = this.convertFrameFromView(drag.get('location'), null),
        dropOp = SC.DROP_BEFORE,
        del    = this.get('selectionDelegate'),
        canReorder = this.get('canReorderContent'),
        objects, content, isPreviousInDrag, isNextInDrag, len, tmp;
    
    // 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 (SC.typeOf(idx) === SC.T_ARRAY) {
      dropOp = idx[1] ; // order matters here
      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) ;
      tmp = del.collectionViewValidateDragOperation(this, drag, dragOp, idx, dropOp) ;
      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 (tmp !== SC.DRAG_NONE) return [idx, dropOp, tmp] ;
        
      // 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 (SC.typeOf(idx) === SC.T_ARRAY) {
          dropOp = idx[1] ; // order matters here
          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 or DROP_AFTER.  DROP_ON is not handled by reordering 
    // content.
    if ((idx >= 0) && canReorder && (dropOp !== SC.DROP_ON)) {
      
      objects = drag.dataForType(this.get('reorderDataType')) ;
      if (objects) {
        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.
        if (dropOp === SC.DROP_BEFORE) {
          isPreviousInDrag = objects.indexes.contains(idx-1);
          isNextInDrag     = objects.indexes.contains(idx);
        } else {
          isPreviousInDrag = objects.indexes.contains(idx);
          isNextInDrag     = objects.indexes.contains(idx-1);
        }
        
        if (isPreviousInDrag && isNextInDrag) {
          if (SC.none(this._lastInsertionIndex)) {
            if (dropOp === SC.DROP_BEFORE) {
              while ((idx >= 0) && objects.indexes.contains(idx)) idx--;
            } else {
              len = content ? content.get('length') : 0;
              while ((idx < len) && objects.indexes.contains(idx)) 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 = del.collectionViewValidateDragOperation(this, drag, dragOp, idx, dropOp) ;
    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 op     = drag.get('allowedDragOperations'),
        state  = this._computeDropOperationState(drag, evt, op),
        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.itemViewForContentIndex(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.
  */
  acceptDragOperation: function(drag, op) { 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(drag, op) { 
        
    // Get the correct insertion point, drop operation, etc.
    var state = this._computeDropOperationState(drag, null, op),
        idx   = state[0], dropOp = state[1], dragOp = state[2],
        del   = this.get('selectionDelegate'),
        performed, objects, data, content, shift, indexes;
        
    // 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.
    performed = del.collectionViewPerformDragOperation(this, drag, op, idx, dropOp) ;
    
    // 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)) {
      
      data = drag.dataForType(this.get('reorderDataType')) ;
      if (!data) return SC.DRAG_NONE ;
      
      content = this.get('content') ;
      
      // check for special case - inserting BEFORE ourself...
      // in this case just pretend the move happened since it's a no-op 
      // anyway
      indexes = data.indexes;
      if (indexes.get('length')===1) {
        if (((dropOp === SC.DROP_BEFORE) || (dropOp === SC.DROP_AFTER)) &&
            (indexes.get('min')===idx)) return SC.DRAG_MOVE;
      }
      
      content.beginPropertyChanges(); // suspend notifications

      // get each object, then remove it from the content. they will be 
      // added again later.
      objects = [];
      shift = 0;
      data.indexes.forEach(function(i) {  
        objects.push(content.objectAt(i-shift));
        content.removeAt(i-shift);
        shift++;
        if (i < idx) idx--;
      }, this);
      
      // now insert objects into new insertion locaiton
      if (dropOp === SC.DROP_AFTER) idx++;
      content.replace(idx, 0, objects, dropOp);
      this.select(SC.IndexSet.create(idx, objects.length));
      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') ;
  },

  
  // ..........................................................
  // INSERTION POINT
  // 
  
  
  /**
    Get the preferred insertion point for the given location, including 
    an insertion preference of before, after or on 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, SC.DROP_AFTER, or SC.DROP_ON. 
    
    The preferred drop operation passed in should be used as a hint as to 
    the type of operation the view would prefer to receive. If the 
    dropOperation 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) { 
    return -1; 
  },
  
  // ..........................................................
  // INTERNAL SUPPORT
  // 

  /** @private - when we become visible, reload if needed. */
  _cv_isVisibleInWindowDidChange: function() {
    if (this.get('isVisibleInWindow')) {
      if (this._invalidIndexes) this.invokeOnce(this.reloadIfNeeded);
      if (this._invalidSelection) {
        this.invokeOnce(this.reloadSelectionIndexesIfNeeded);
      } 
    }
  }.observes('isVisibleInWindow'),


  /**
    Default delegate method implementation, returns YES if isSelectable
    is also true.
  */
  collectionViewShouldSelectItem: function(view, item) {
    return this.get('isSelectable') ;
  },
  
  _TMP_DIFF1: SC.IndexSet.create(),
  _TMP_DIFF2: SC.IndexSet.create(),
  
  /** @private
  
    Whenever the nowShowing range changes, update the range observer on the 
    content item and instruct the view to reload any indexes that are not in
    the previous nowShowing range.

  */
  _cv_nowShowingDidChange: function() {
    var nowShowing  = this.get('nowShowing'),
        last        = this._sccv_lastNowShowing,
        diff, diff1, diff2;

    // find the differences between the two
    // NOTE: reuse a TMP IndexSet object to avoid creating lots of objects
    // during scrolling
    if (last !== nowShowing) {
      if (last && nowShowing) {
        diff1 = this._TMP_DIFF1.add(last).remove(nowShowing);
        diff2 = this._TMP_DIFF2.add(nowShowing).remove(last);
        diff = diff1.add(diff2);
      } else diff = last || nowShowing ;
    }

    // if nowShowing has actually changed, then update
    if (diff && diff.get('length') > 0) {
      this._sccv_lastNowShowing = nowShowing ? nowShowing.frozenCopy() : null;
      this.updateContentRangeObserver();
      this.reload(diff);
    }
    
    // cleanup tmp objects
    if (diff1) diff1.clear();
    if (diff2) diff2.clear();
    
  }.observes('nowShowing'),
  
  init: function() {
     arguments.callee.base.apply(this,arguments);
     if (this.get('canReorderContent')) this._cv_canReorderContentDidChange();
     this._sccv_lastNowShowing = this.get('nowShowing').clone();
     if (this.content) this._cv_contentDidChange();
     if (this.selection) this._cv_selectionDidChange();
  },
  
  /** @private
    Become a drop target whenever reordering content is enabled.
  */
  _cv_canReorderContentDidChange: function() {
    if (this.get('canReorderContent')) {
      if (!this.get('isDropTarget')) this.set('isDropTarget', YES);
      SC.Drag.addDropTarget(this);
    }
  }.observes('canReorderContent'),
  
  /** @private
    Fires an action after a selection if enabled.
    
    if actOnSelect is YES, then try to invoke the action, passing the 
    current selection (saved as a separate array so that a change in sel
    in the meantime will not be lost)
  */
  _cv_performSelectAction: function(view, ev, delay, clickCount) {
    var sel;
    if (delay === undefined) delay = 0 ;
    if (clickCount === undefined) clickCount = 1;
    if ((clickCount>1) || this.get('actOnSelect')) {
      if (this._cv_reselectTimer) this._cv_reselectTimer.invalidate() ;
      sel = this.get('selection');
      sel = sel ? sel.toArray() : [];
      if (this._cv_actionTimer) this._cv_actionTimer.invalidate();
      this._cv_actionTimer = this.invokeLater(this._cv_action, delay, view, ev, sel) ;
    }
  },
  
  /** @private
    Perform the action.  Supports legacy behavior as well as newer style
    action dispatch.
  */
  _cv_action: function(view, evt, context) {
    var action = this.get('action');
    var target = this.get('target') || null;

    this._cv_actionTimer = null;
    if (action) {
      // if the action is a function, just call it
      if (SC.typeOf(action) === SC.T_FUNCTION) return this.action(view, evt) ;
      
      // otherwise, use the new sendAction style
      var pane = this.get('pane') ;
      if (pane) {
        pane.rootResponder.sendAction(action, target, this, pane, context);
      }
      // 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 (SC.typeOf(view._action) == SC.T_FUNCTION) {
      return view._action(evt) ;
      
    // otherwise call the action method to support older styles.
    } else if (SC.typeOf(view.action) == SC.T_FUNCTION) {
      return view.action(evt) ;
    }
  }
  
  
});

/* >>>>>>>>>> BEGIN source/views/disclosure.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class
  
  Disclosure triangle button.
  
  @extends SC.ButtonView
  @since SproutCore
*/
SC.DisclosureView = SC.ButtonView.extend(
/** @scope SC.DisclosureView.prototype */ {
  
  classNames: ['sc-disclosure-view'],
  
  theme: 'disclosure',
  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,
  
  /** @private */
  valueBindingDefault: SC.Binding.bool() ,
  
  /** @private */
  render: function(context, firstTime) {
    context.push('<img src="', SC.BLANK_IMAGE_URL, '" class="button" alt="" />');
    if(this.get('needsEllipsis')){
      context.push('<label class="ellipsis">',this.get('displayTitle'),'</label>');
    }else{
        context.push('<label>',this.get('displayTitle'),'</label>');  
    }
  },
  
  keyDown: function(evt) {
    if (evt.which === 37 || evt.which === 38) {  
      this.set('value', this.get('toggleOffValue')) ;
      return YES;
    }
    if (evt.which === 39 || evt.which === 40) {  
      this.set('value', this.get('toggleOnValue')) ;
      return YES;
    }     
    arguments.callee.base.apply(this,arguments); 
  }
  
});

/* >>>>>>>>>> BEGIN source/views/list.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/collection');
sc_require('mixins/collection_row_delegate');

/** @class
  
  A list view renders vertical lists of items.  It is a specialized form of
  collection view that is simpler than the table view, but more refined than
  a generic collection.
  
  You can use a list view just like a collection view, except that often you
  also should provide a default rowHeight.  Setting this value will allow 
  the ListView to optimize its rendering.
  
  h2. Variable Row Heights

  Normally you set the row height through the rowHeight property.  You can 
  also support custom row heights by implementing the 
  contentCustomRowHeightIndexes property to return an index set.
  
  h2. Using ListView with Very Large Data Sets
  
  ListView implements incremental rendering, which means it will only render
  HTML for the items that are current visible on the screen.  You can use it
  to efficiently render lists with 100K+ items very efficiently.  
  
  If you need to work with very large lists of items, however, be aware that
  calculate variable rows heights can become very expensive since the list 
  view will essentially have to iterate over every item in the collection to
  collect its row height.  
  
  To work with very large lists, you should consider making your row heights
  uniform.  This will allow the list view to efficiently render content 
  without worrying about the overall performance.
  
  Alternatively, you may want to consider overriding the 
  offsetForRowAtContentIndex() and heightForRowAtContentIndex() methods to 
  perform some faster calculations that do not require inspecting every 
  item in the collection.
  
  Note that row heights and offsets are cached so once they are calculated
  the list view will be able to display very quickly.
  
  (Can we also have an 'estimate row heights' property that will simply 
  cheat for very long data sets to make rendering more efficient?)
  
  @extends SC.CollectionView
  @extends SC.CollectionRowDelegate
  @since SproutCore 1.0
*/
SC.ListView = SC.CollectionView.extend(
  SC.CollectionRowDelegate,
/** @scope SC.ListView.prototype */ {
  
  classNames: ['sc-list-view'],

  acceptsFirstResponder: YES,
  
  /**
  * If set to YES, the default theme will show alternating rows
  * for the views this ListView created through exampleView property.
  *
  * @property {Boolean} 
  */
  showAlternatingRows: NO,
  
  // ..........................................................
  // METHODS
  //
  
  render: function(context, firstTime) {
    context.setClass('alternating', this.get('showAlternatingRows'));
    
    return arguments.callee.base.apply(this,arguments);
  },

  // ..........................................................
  // COLLECTION ROW DELEGATE SUPPORT
  // 
  
  
  /**
    Returns the current collectionRowDelegate.  This property will recompute
    everytime the content changes.
  */
  rowDelegate: function() {
    var del     = this.delegate,
        content = this.get('content');
    return this.delegateFor('isCollectionRowDelegate', del, content);
  }.property('delegate', 'content').cacheable(),
  
  /** @private 
    Whenever the rowDelegate changes, begin observing important properties
  */
  _sclv_rowDelegateDidChange: function() {
    var last = this._sclv_rowDelegate,
        del  = this.get('rowDelegate'),
        func = this._sclv_rowHeightDidChange,
        func2 = this._sclv_customRowHeightIndexesDidChange;
        
    if (last === del) return this; // nothing to do
    this._sclv_rowDelegate = del; 

    // last may be null on a new object
    if (last) {
      last.removeObserver('rowHeight', this, func);
      last.removeObserver('customRowHeightIndexes', this, func2);
    }
    
    if (!del) {
      throw "Internal Inconsistancy: ListView must always have CollectionRowDelegate";
    }
    
    del.addObserver('rowHeight', this, func);
    del.addObserver('customRowHeightIndexes', this, func2);
    this._sclv_rowHeightDidChange()._sclv_customRowHeightIndexesDidChange();
    return this ;
  }.observes('rowDelegate'),

  /** @private 
    called whenever the rowHeight changes.  If the property actually changed
    then invalidate all row heights.
  */
  _sclv_rowHeightDidChange: function() {
    var del = this.get('rowDelegate'),
        height = del.get('rowHeight'), 
        indexes;
        
    if (height === this._sclv_rowHeight) return this; // nothing to do
    this._sclv_rowHeight = height;

    indexes = SC.IndexSet.create(0, this.get('length'));
    this.rowHeightDidChangeForIndexes(indexes);
    return this ;
  },

  /** @private 
    called whenever the customRowHeightIndexes changes.  If the property 
    actually changed then invalidate affected row heights.
  */
  _sclv_customRowHeightIndexesDidChange: function() {
    var del     = this.get('rowDelegate'),
        indexes = del.get('customRowHeightIndexes'), 
        last    = this._sclv_customRowHeightIndexes,
        func    = this._sclv_customRowHeightIndexesContentDidChange;
        
    // nothing to do
    if ((indexes===last) || (last && last.isEqual(indexes))) return this;

    // if we were observing the last index set, then remove observer
    if (last && this._sclv_isObservingCustomRowHeightIndexes) {
      last.removeObserver('[]', this, func);
    }
    
    // only observe new index set if it exists and it is not frozen.
    if (this._sclv_isObservingCustomRowHeightIndexes = indexes && !indexes.get('isFrozen')) {
      indexes.addObserver('[]', this, func);
    }
    
    this._sclv_customRowHeightIndexesContentDidChange();
    return this ;
  },

  /** @private
    Called whenever the customRowHeightIndexes set is modified.
  */
  _sclv_customRowHeightIndexesContentDidChange: function() {
    var del     = this.get('rowDelegate'),
        indexes = del.get('customRowHeightIndexes'), 
        last    = this._sclv_customRowHeightIndexes, 
        changed;

    // compute the set to invalidate.  the union of cur and last set
    if (indexes && last) {
      changed = indexes.copy().add(last);
    } else changed = indexes || last ;
    this._sclv_customRowHeightIndexes = indexes ? indexes.frozenCopy() : null; 

    // invalidate
    this.rowHeightDidChangeForIndexes(changed);
    return this ;
  },
  
  // ..........................................................
  // ROW PROPERTIES
  // 
  
  /**
    Returns the top offset for the specified content index.  This will take
    into account any custom row heights and group views.
    
    @param {Number} idx the content index
    @returns {Number} the row offset
  */
  rowOffsetForContentIndex: function(idx) {
    if (idx === 0) return 0 ; // fastpath

    var del       = this.get('rowDelegate'),
        rowHeight = del.get('rowHeight'),
        ret, custom, cache, delta, max, content ;
        
    ret = idx * rowHeight;

		if(this.get('rowSpacing')){ 
      ret += idx * this.get('rowSpacing'); 
    } 

    if (del.customRowHeightIndexes && (custom=del.get('customRowHeightIndexes'))) {
      
      // prefill the cache with custom rows.
      cache = this._sclv_offsetCache;
      if (!cache) {
        cache = this._sclv_offsetCache = [];
        delta = max = 0 ;
        custom.forEach(function(idx) {
          delta += this.rowHeightForContentIndex(idx)-rowHeight;
          cache[idx+1] = delta;
          max = idx ;
        }, this);
        this._sclv_max = max+1;
      }
      
      // now just get the delta for the last custom row before the current 
      // idx.
      delta = cache[idx];
      if (delta === undefined) {
        delta = cache[idx] = cache[idx-1];
        if (delta === undefined) {
          max = this._sclv_max;
          if (idx < max) max = custom.indexBefore(idx)+1;
          delta = cache[idx] = cache[max] || 0;
        }
      }

      ret += delta ;
    }
    
    return ret ;
  },
  
  /**
    Returns the row height for the specified content index.  This will take
    into account custom row heights and group rows.
    
    @param {Number} idx content index
    @returns {Number} the row height
  */
  rowHeightForContentIndex: function(idx) {
    var del = this.get('rowDelegate'),
        ret, cache, content, indexes;
    
    if (del.customRowHeightIndexes && (indexes=del.get('customRowHeightIndexes'))) {
      cache = this._sclv_heightCache ;
      if (!cache) {
        cache = this._sclv_heightCache = [];
        content = this.get('content');
        indexes.forEach(function(idx) {
          cache[idx] = del.contentIndexRowHeight(this, content, idx);
        }, this);
      }
      
      ret = cache[idx];
      if (ret === undefined) ret = del.get('rowHeight');
    } else ret = del.get('rowHeight');
    
    return ret ;
  },
  
  /**
    Call this method whenever a row height has changed in one or more indexes.
    This will invalidate the row height cache and reload the content indexes.
    Pass either an index set or a single index number.

    This method is called automatically whenever you change the rowHeight
    or customRowHeightIndexes properties on the collectionRowDelegate.
    
    @param {SC.IndexSet|Number} indexes 
    @returns {SC.ListView} receiver
  */  
  rowHeightDidChangeForIndexes: function(indexes) {
    var len     = this.get('length');

    // clear any cached offsets
    this._sclv_heightCache = this._sclv_offsetCache = null;
    
    // find the smallest index changed; invalidate everything past it
    if (indexes && indexes.isIndexSet) indexes = indexes.get('min');
    this.reload(SC.IndexSet.create(indexes, len-indexes));
    return this ;
  },
  
  // ..........................................................
  // SUBCLASS IMPLEMENTATIONS
  // 
  
  /**
    The layout for a ListView is computed from the total number of rows 
    along with any custom row heights.
  */
  computeLayout: function() {
    // default layout
    var ret = this._sclv_layout;
    if (!ret) ret = this._sclv_layout = {};
    ret.minHeight = this.rowOffsetForContentIndex(this.get('length'))+4;
    this.calculatedHeight = ret.minHeight;
    return ret ;
  },
  
  /**
  
    Computes the layout for a specific content index by combining the current
    row heights.
  
  */
  layoutForContentIndex: function(contentIndex) {
    return {
      top:    this.rowOffsetForContentIndex(contentIndex),
      height: this.rowHeightForContentIndex(contentIndex),
      left:   0, 
      right:  0
    };
  },
  
  /**
    Override to return an IndexSet with the indexes that are at least 
    partially visible in the passed rectangle.  This method is used by the 
    default implementation of computeNowShowing() to determine the new 
    nowShowing range after a scroll.
    
    Override this method to implement incremental rendering.
    
    The default simply returns the current content length.
    
    @param {Rect} rect the visible rect or a point
    @returns {SC.IndexSet} now showing indexes
  */
  contentIndexesInRect: function(rect) {
    var rowHeight = this.get('rowDelegate').get('rowHeight'),
        top       = SC.minY(rect),
        bottom    = SC.maxY(rect),
        height    = rect.height || 0,
        len       = this.get('length'),
        offset, start, end;
    
    // estimate the starting row and then get actual offsets until we are 
    // right.
    start = (top - (top % rowHeight)) / rowHeight;
    offset = this.rowOffsetForContentIndex(start);
    
    // go backwards until top of row is before top edge
    while(start>0 && offset>=top) {
      start--;
      offset -= this.rowHeightForContentIndex(start);
    }
    
    // go forwards until bottom of row is after top edge
    offset += this.rowHeightForContentIndex(start);
    while(start<len && offset<top) {
      offset += this.rowHeightForContentIndex(start);
      start++ ;
    }
    if (start<0) start = 0;
    if (start>=len) start=len;
    
    
    // estimate the final row and then get the actual offsets until we are 
    // right. - look at the offset of the _following_ row
    end = start + ((height - (height % rowHeight)) / rowHeight) ;
    if (end > len) end = len;
    offset = this.rowOffsetForContentIndex(end);
    
    // walk backwards until top of row is before or at bottom edge
    while(end>=start && offset>=bottom) {
      end-- ;
      offset -= this.rowHeightForContentIndex(end);
    }
    
    // go forwards until bottom of row is after bottom edge
    offset += this.rowHeightForContentIndex(end);
    while(end<len && offset<=bottom) {
      offset += this.rowHeightForContentIndex(end);
      end++ ;
    }
    
    end++; // end should be after start
    
    // if height is greater than 0, on some platforms we should just render
    // to specific windows in order to minimize render time.
    // if (height > 0 && !SC.browser.msie) {
    //   start = start - (start % 50);
    //   if (start < 0) start = 0 ;
    //   end   = end - (end % 50) + 50;
    // }
    
    if (end<start) end = start;
    if (end>len) end = len ;
    
    // convert to IndexSet and return
    return SC.IndexSet.create(start, end-start);
  },
  
  // ..........................................................
  // DRAG AND ROP SUPPORT
  // 
  
  
  /**
    Default view class used to draw an insertion point.  The default 
    view will show a vertical line.  Any view you create
    should expect an outlineLevel property set, which should impact your left
    offset.
    
    @property 
    @type {SC.View}
  */
  insertionPointView: SC.View.extend({
    classNames: 'sc-list-insertion-point',
    
    render: function(context, firstTime) {
      if (firstTime) context.push('<div class="anchor"></div>');
    }
    
  }),

  /**
    Default implementation will show an insertion point
  */
  showInsertionPoint: function(itemView, dropOperation) {
    var view = this._insertionPointView;
    if (!view) {
      view = this._insertionPointView 
           = this.get('insertionPointView').create();
    }
    
    var index  = itemView.get('contentIndex'),
        len    = this.get('length'),
        layout = SC.clone(itemView.get('layout')),
        level  = itemView.get('outlineLevel'),
        indent = itemView.get('outlineIndent') || 0,
        group;

    // show item indented if we are inserting at the end and the last item
    // is a group item.  This is a special case that should really be 
    // converted into a more general protocol.
    if ((index >= len) && index>0) {
      group = this.itemViewForContentIndex(len-1);
      if (group.get('isGroupView')) {
        level = 1;
        indent = group.get('outlineIndent');
      }
    }
    
    if (SC.none(level)) level = -1;
    
    if (dropOperation & SC.DROP_ON) {
      this.hideInsertionPoint();
      itemView.set('isSelected', YES);
      this._lastDropOnView = itemView;
    } else {

      if (this._lastDropOnView) {
        this._lastDropOnView.set('isSelected', NO);
        this._lastDropOnView = null;
      }
      
      if (dropOperation & SC.DROP_AFTER) layout.top += layout.height;
      
      layout.height = 2;
      layout.right  = 0;
      layout.left   = ((level+1) * indent) + 12;
      delete layout.width;

      view.set('layout', layout);
      this.appendChild(view);
    }
  },
  
  hideInsertionPoint: function() {
    if (this._lastDropOnView) {
      this._lastDropOnView.set('isSelected', NO);
      this._lastDropOnView = null;
    }
    
    var view = this._insertionPointView;
    if (view) view.removeFromParent().destroy();
    this._insertionPointView = null;
  },

  /**
    Compute the insertion index for the passed location.  The location is 
    a point, relative to the top/left corner of the receiver view.  The return
    value is an index plus a dropOperation, which is computed as such:
    
    - if outlining is not used and you are within 5px of an edge, DROP_BEFORE
      the item after the edge.
      
    - if outlining is used and you are within 5px of an edge and the previous
      item has a different outline level then the next item, then DROP_AFTER
      the previous item if you are closer to that outline level.
      
    - if dropOperation = SC.DROP_ON and you are over the middle of a row, then
      use DROP_ON.
  */
  insertionIndexForLocation: function(loc, dropOperation) { 
    var indexes = this.contentIndexesInRect(loc),
        index   = indexes.get('min'),
        len     = this.get('length'),
        min, max, diff, clevel, cindent, plevel, pindent, itemView, pgroup;

    // if there are no indexes in the rect, then we need to either insert
    // before the top item or after the last item.  Figure that out by 
    // computing both.
    if (SC.none(index) || index<0) {
      if ((len===0) || (loc.y <= this.rowOffsetForContentIndex(0))) index = 0;
      else if (loc.y >= this.rowOffsetForContentIndex(len)) index = len;
    }

    // figure the range of the row the location must be within.
    min = this.rowOffsetForContentIndex(index);
    max = min + this.rowHeightForContentIndex(index);
    
    // now we know which index we are in.  if dropOperation is DROP_ON, figure
    // if we can drop on or not.
    if (dropOperation == SC.DROP_ON) {
      // editable size - reduce height by a bit to handle dropping
      if (this.get('isEditable')) diff=Math.min(Math.floor((max-min)*0.2),5);
      else diff = 0;
      
      // if we're inside the range, then DROP_ON
      if (loc.y >= (min+diff) || loc.y <= (max+diff)) {
        return [index, SC.DROP_ON];
      }
    }
    
    
    
    // ok, now if we are in last 10px, go to next item.
    if ((index<len) && (loc.y >= max-10)) index++;
    
    // finally, let's decide if we want to actually insert before/after.  Only
    // matters if we are using outlining.
    if (index>0) {

      itemView = this.itemViewForContentIndex(index-1);
      pindent  = (itemView ? itemView.get('outlineIndent') : 0) || 0;
      plevel   = itemView ? itemView.get('outlineLevel') : 0;
      
      if (index<len) {
        itemView = this.itemViewForContentIndex(index);
        clevel   = itemView ? itemView.get('outlineLevel') : 0;
        cindent  = (itemView ? itemView.get('outlineIndent') : 0) || 0;
        cindent  *= clevel;
      } else {
        clevel = itemView.get('isGroupView') ? 1 : 0; // special case...
        cindent = pindent * clevel;  
      }

      pindent  *= plevel;

      // if indent levels are different, then try to figure out which level 
      // it should be on.
      if ((clevel !== plevel) && (cindent !== pindent)) {
        
        // use most inner indent as boundary
        if (pindent > cindent) {
          index--;
          dropOperation = SC.DROP_AFTER;
        }
      }
    }

    // we do not support dropping before a group item.  If dropping before 
    // a group item, always try to instead drop after the previous item.  If
    // the previous item is also a group then, well, dropping is just not 
    // allowed.  Note also that dropping at 0, first item must not be group
    // and dropping at length, last item must not be a group
    //
    if (dropOperation === SC.DROP_BEFORE) {
      itemView = (index<len) ? this.itemViewForContentIndex(index) : null;
      if (!itemView || itemView.get('isGroupView')) {
        if (index>0) {
          itemView = this.itemViewForContentIndex(index-1);
          
          // don't allow a drop if the previous item is a group view and we're
          // insert before the end.  For the end, allow the drop if the 
          // previous item is a group view but OPEN.
          if (!itemView.get('isGroupView') || (itemView.get('disclosureState') === SC.BRANCH_OPEN)) {
            index = index-1;
            dropOperation = SC.DROP_AFTER;
          } else index = -1;

        } else index = -1;
      }
      
      if (index<0) dropOperation = SC.DRAG_NONE ;
    } 
    
    // return whatever we came up with
    return [index, dropOperation];
  },
  
  // ..........................................................
  // INTERNAL SUPPORT
  // 

  init: function() {
    arguments.callee.base.apply(this,arguments);
    this._sclv_rowDelegateDidChange();
  }  
  
});

/* >>>>>>>>>> BEGIN source/views/grid.js */
// ==========================================================================
// SC.GridView
// ==========================================================================

sc_require('views/list') ;

/** @class

  A grid view renders a collection of items in a grid of rows and columns.

  @extends SC.CollectionView
  @author    Charles Jolley  
  @version 1.0
*/
SC.GridView = SC.ListView.extend(
/** @scope SC.GridView.prototype */ {
    classNames: ['sc-grid-view'],
  
  layout: { left:0, right:0, top:0, bottom:0 },

  /** 
    The common row height for grid items.
    
    The value should be an integer expressed in pixels.
  */
  rowHeight: 48,
  
  /**
    The minimum column width for grid items.  Items will actually
    be laid out as needed to completely fill the space, but the minimum
    width of each item will be this value.
  */
  columnWidth: 64,

  /**
    The default example item view will render text-based items.
    
    You can override this as you wish.
  */
  exampleView: SC.LabelView,
  
  insertionOrientation: SC.HORIZONTAL_ORIENTATION,
  
  /** @private */
  itemsPerRow: function() {
    var f = this.get('frame') ;
    var columnWidth = this.get('columnWidth') || 0 ;

    return (columnWidth <= 0) ? 1 : Math.floor(f.width / columnWidth) ;
  }.property('clippingFrame', 'columnWidth').cacheable(),
  
  /** @private
    Find the contentIndexes to display in the passed rect. Note that we 
    ignore the width of the rect passed since we need to have a single
    contiguous range.
  */
  contentIndexesInRect: function(rect) {
    var rowHeight = this.get('rowHeight') || 48 ;
    var itemsPerRow = this.get('itemsPerRow') ;
    
    var min = Math.floor(SC.minY(rect) / rowHeight) * itemsPerRow  ;
    var max = Math.ceil(SC.maxY(rect) / rowHeight) * itemsPerRow ;
    return SC.IndexSet.create(min, max-min);
  },
  
  /** @private */
  layoutForContentIndex: function(contentIndex) {
    var rowHeight = this.get('rowHeight') || 48 ;
    var frameWidth = this.get('clippingFrame').width ;
    var itemsPerRow = this.get('itemsPerRow') ;
    var columnWidth = Math.floor(frameWidth/itemsPerRow);
    
    var row = Math.floor(contentIndex / itemsPerRow) ;
    var col = contentIndex - (itemsPerRow*row) ;
    return { 
      left: col * columnWidth,
      top: row * rowHeight,
      height: rowHeight,
      width: columnWidth
    };
  },
  
  /** @private
    Overrides default CollectionView method to compute the minimim height
    of the list view.
  */
  computeLayout: function() {
    var content = this.get('content') ;
    var count = (content) ? content.get('length') : 0 ;
    var rowHeight = this.get('rowHeight') || 48 ;
    var itemsPerRow = this.get('itemsPerRow') ;
    var rows = Math.ceil(count / itemsPerRow) ;
  
    // use this cached layout hash to avoid allocing memory...
    var ret = this._cachedLayoutHash ;
    if (!ret) ret = this._cachedLayoutHash = {};
    
    // set minHeight
    ret.minHeight = rows * rowHeight ;
    this.calculatedHeight = ret.minHeight;
    return ret; 
  },
  
  insertionPointClass: SC.View.extend({
    classNames: ['grid-insertion-point'],
    
    render: function(context, firstTime) {
      if (firstTime) context.push('<span class="anchor"></span>') ;
    }

  }),
  
  showInsertionPoint: function(itemView, dropOperation) {
    if (!itemView) return ;
    
    // if drop on, then just add a class...
    if (dropOperation === SC.DROP_ON) {
      if (itemView !== this._dropOnInsertionPoint) {
        this.hideInsertionPoint() ;
        itemView.addClassName('drop-target') ;
        this._dropOnInsertionPoint = itemView ;
      }
      
    } else {
      
      if (this._dropOnInsertionPoint) {
        this._dropOnInsertionPoint.removeClassName('drop-target') ;
        this._dropOnInsertionPoint = null ;
      }
    
      if (!this._insertionPointView) {
        this._insertionPointView = this.insertionPointClass.create() ;
      }
    
      var insertionPoint = this._insertionPointView ;
      var itemViewFrame = itemView.get('frame') ;
      var f = { height: itemViewFrame.height - 6, 
            x: itemViewFrame.x, 
            y: itemViewFrame.y + 6, 
            width: 0 
          };

      if (!SC.rectsEqual(insertionPoint.get('frame'), f)) {
        insertionPoint.set('frame', f) ;
      }

      if (insertionPoint.parentNode != itemView.parentNode) {
        itemView.parentNode.appendChild(insertionPoint) ;
      }
    }
    
  },
    
  hideInsertionPoint: function() {
    var insertionPoint = this._insertionPointView ;
    if (insertionPoint) insertionPoint.removeFromParent() ;

    if (this._dropOnInsertionPoint) {
      this._dropOnInsertionPoint.removeClassName('drop-target') ;
      this._dropOnInsertionPoint = null ;
    }
  },
  
  // // We can do this much faster programatically using the rowHeight
  insertionIndexForLocation: function(loc, dropOperation) {  
    var f = this.get('frame') ;
    var sf = this.get('scrollFrame') ;
    
    var itemsPerRow = this.get('itemsPerRow') ; 
    var columnWidth = Math.floor(f.width / itemsPerRow) ;
    var row = Math.floor((loc.y - f.y - sf.y) / this.get('rowHeight')) ;

    var retOp = SC.DROP_BEFORE ;
    
    var offset = (loc.x - f.x - sf.x) ;
    var col = Math.floor(offset / columnWidth) ;
    var percentage = (offset / columnWidth) - col ;
    
    // if the dropOperation is SC.DROP_ON and we are in the center 60%
    // then return the current item.
    if (dropOperation === SC.DROP_ON) {
      if (percentage > 0.80) col++ ;
      if ((percentage >= 0.20) && (percentage <= 0.80)) {
        retOp = SC.DROP_ON;
      }
    } else {
      if (percentage > 0.45) col++ ;
    }
    
    // convert to index
    var ret= (row*itemsPerRow) + col ;
    return [ret, retOp] ;
  },

  /** @private
    If the size of the clipping frame changes, all of the item views
    on screen are potentially in the wrong position.  Update all of their
    layouts if different.
  */
  _gv_clippingFrameDidChange: function() {
    var nowShowing = this.get('nowShowing'), itemView, idx, len;
    this.notifyPropertyChange('itemsPerRow');

    len = nowShowing.get('length');

    for (idx=0; idx < len; idx++) {
      itemView = this.itemViewForContentIndex(idx);
      itemView.adjust(this.layoutForContentIndex(idx));
    }
  }.observes('clippingFrame')
}) ;

/* >>>>>>>>>> BEGIN source/views/scroller.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** Vary based on current platform. */
SC.NATURAL_SCROLLER_THICKNESS = 16;

/** @class

  Displays a horizontal or vertical scroller.  You will not usually need to
  work with scroller views directly, but you may override this class to 
  implement your own custom scrollers. 
  
  @extends SC.View
  @since SproutCore 1.0
*/
SC.ScrollerView = SC.View.extend({
  
  classNames: ['sc-scroller-view'],
  
  // ..........................................................
  // PROPERTIES
  // 
  
  /**
   Used by the SC.ScrollView to properly layout the scrollers.
  */
  scrollerThickness: SC.NATURAL_SCROLLER_THICKNESS,
  
  /** 
    The scroller offset value.  This value will adjust between the minimum
    and maximum values that you set. Default is 0.
    
    @property
  */
  value: function(key, val) {
    if (val !== undefined) {
      // Don't enforce the maximum now, because the scroll view could change
      // height and we want our content to stay put when it does.
      if (val >= 0) {
        this._value = val ;
      }
    } else {
      var value = this._value || 0 ; // default value is at top/left
      return Math.min(value, this.get('maximum')) ;
    }
  }.property('maximum').cacheable(),
  
  /**
    The maximum offset value for the scroller.  This will be used to calculate
    the internal height/width of the scroller itself. It is not necessarily
    the same as the height of a scroll view's content view.
    
    When set less than the height of the scroller, the scroller is disabled.
    
    @property {Number}
  */
  maximum: 0,
  
  /**
    YES if enable scrollbar, NO to disable it.  Scrollbars will automatically 
    disable if the maximum scroll width does not exceed their capacity.
    
    @property
  */
  isEnabled: YES,
  
  /**
    Determine the layout direction.  Determines whether the scrollbar should 
    appear horizontal or vertical.  This must be set when the view is created.
    Changing this once the view has been created will have no effect.
    
    @property
  */
  layoutDirection: SC.LAYOUT_VERTICAL,
  
  /**
    Returns the owner view property the scroller should modify.  If this 
    property is non-null and the owner view defines this property, then the 
    scroller will automatically update this property whenever its own value 
    changes.
    
    The default value of this property is computed based on the 
    layoutDirection.  You can override this property to provide your own 
    calculation if necessary or to return null if you want to disable this 
    behavior.
    
    @property {String}
  */
  ownerScrollValueKey: function() {
    var key = null ;
    switch(this.get('layoutDirection')) {
      case SC.LAYOUT_VERTICAL:
        key = 'verticalScrollOffset' ;
        break ;
      case SC.LAYOUT_HORIZONTAL:
        key = 'horizontalScrollOffset' ;
        break ;
      default:
        key = null ;
    }
    return key ;  
  }.property('layoutDirection').cacheable(),
  
  // ..........................................................
  // INTERNAL SUPPORT
  // 
  
  displayProperties: 'maximum isEnabled layoutDirection'.w(),
  
  /** @private
    Update the scroll location or inner height/width if needed.
  */
  render: function(context, firstTime) {
    var max = this.get('maximum') ;
    
    switch (this.get('layoutDirection')) {
      case SC.LAYOUT_VERTICAL:
        context.addClass('sc-vertical') ;
        if (firstTime) {
          context.push('<div class="sc-inner" style="height: %@px;">&nbsp;</div>'.fmt(max)) ;
        } else {
          this.$('div')[0].style.height = max + "px";
        }
        break ;
      case SC.LAYOUT_HORIZONTAL:
        context.addClass('sc-horizontal') ;
        if (firstTime) {  
          context.push('<div class="sc-inner" style="width: %@px;">&nbsp;</div>'.fmt(max)) ;
        } else {
          this.$('div')[0].style.width = max + "px";
        }
        break ;
      default:
        throw "You must set a layoutDirection for your scroller class." ;
    }
    
    context.setClass('disabled', !this.get('isEnabled')) ;
  },
  
  didCreateLayer: function() {
    var callback = this._sc_scroller_scrollDidChange ;
    SC.Event.add(this.$(), 'scroll', this, callback) ;
    
    // set scrollOffset first time
    var amt = this.get('value') ;
    var layer = this.get('layer') ;
    
    switch (this.get('layoutDirection')) {
      case SC.LAYOUT_VERTICAL:
        layer.scrollTop = amt ;
        break;
        
      case SC.LAYOUT_HORIZONTAL:
        layer.scrollLeft = amt ;
        break;
    }
  },
  
  willDestroyLayer: function() {
    var callback = this._sc_scroller_scrollDidChange ;
    SC.Event.remove(this.$(), 'scroll', this, callback) ;
  },
  
  // after 50msec, fire event again
  _sc_scroller_armScrollTimer: function() {
    if (!this._sc_scrollTimer) {
      SC.RunLoop.begin() ;
      var method = this._sc_scroller_scrollDidChange ;
      this._sc_scrollTimer = this.invokeLater(method, 50) ;
      SC.RunLoop.end() ;
    }
  },
  
  _sc_scroller_scrollDidChange: function() {
    var now = Date.now(), last = this._sc_lastScroll ;
    if (last && (now-last)<50) return this._sc_scroller_armScrollTimer() ;
    this._sc_scrollTimer = null ;
    this._sc_lastScroll = now ;
    
    SC.RunLoop.begin();
    
    if (!this.get('isEnabled')) return ; // nothing to do.
    
    var layer = this.get('layer'), scroll = 0 ;
    switch (this.get('layoutDirection')) {
      case SC.LAYOUT_VERTICAL:
        this._sc_scrollValue = scroll = layer.scrollTop ;
        break ;
        
      case SC.LAYOUT_HORIZONTAL:
        this._sc_scrollValue = scroll = layer.scrollLeft ;
        break ;
    }
    this.set('value', scroll) ; // will now enforce minimum and maximum
    
    SC.RunLoop.end();
  },
  
  /** @private */
  _sc_scroller_valueDidChange: function() {
    var v = this.get('value') ;
        
    if (v !== this._sc_scrollValue) {
      var layer = this.get('layer') ;
      if (layer) {
        switch (this.get('layoutDirection')) {
          case SC.LAYOUT_VERTICAL:
            layer.scrollTop = v;
            break ;
          
          case SC.LAYOUT_HORIZONTAL:
            layer.scrollLeft = v ;
            break ;
        }
      }
    }
  }.observes('value')
  
});

/* >>>>>>>>>> BEGIN source/views/scroll.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/scroller');
sc_require('mixins/border');

/** @class

  Implements a complete scroll view.  This class uses a manual implementation
  of scrollers in order to properly support clipping frames.
  
  Important Events:
  
  - contentView frame size changes (to autoshow/hide scrollbar - adjust scrollbar size)
  - horizontalScrollOffset change
  - verticalScrollOffsetChanges
  - scroll wheel events
  
  @extends SC.View
  @since SproutCore 1.0
*/
SC.ScrollView = SC.View.extend(SC.Border, {

  classNames: ['sc-scroll-view'],
  
  // ..........................................................
  // PROPERTIES
  // 
  
  isScrollable: YES,
  
  /** 
    The content view you want the scroll view to manage. This will be assigned to the contentView of the clipView also.
  */
  contentView: null,

  /**
    The current horizontal scroll offset. Changing this value will update both the contentView and the horizontal scroller, if there is one.
  */
  horizontalScrollOffset: 0,
  
  /**
    The current vertical scroll offset.  Changing this value will update both the contentView and the vertical scroller, if there is one.
  */
  verticalScrollOffset: 0,
  
  /**
    The maximum horizontal scroll offset allowed given the current contentView 
    size and the size of the scroll view.  If horizontal scrolling is 
    disabled, this will always return 0.
    
    @property {Number}
  */
  maximumHorizontalScrollOffset: function() {
    if (!this.get('canScrollHorizontal')) return 0 ;
    var view = this.get('contentView') ;
    var contentWidth = view ? view.get('frame').width : 0 ;
    
    // The following code checks if there is a calculatedWidth (collections)
    // to avoid looking at the incorrect value calculated by frame.
    if(view.calculatedWidth && view.calculatedWidth!==0){
      contentWidth = view.calculatedWidth; 
    }
    var containerWidth = this.get('containerView').get('frame').width ;
    return Math.max(0, contentWidth-containerWidth) ;
  }.property(),
  
  /**
    The maximum vertical scroll offset allowed given the current contentView 
    size and the size of the scroll view.  If vertical scrolling is disabled,
    this will always return 0.
    
    @property {Number}
  */
  maximumVerticalScrollOffset: function() {
    if (!this.get('canScrollVertical')) return 0 ;
    var view = this.get('contentView') ;
    var contentHeight = (view && view.get('frame')) ? view.get('frame').height : 0 ;
    
    // The following code checks if there is a calculatedWidth (collections)
    // to avoid looking at the incorrect value calculated by frame.
    if(view.calculatedHeight && view.calculatedHeight!==0){
      contentHeight = view.calculatedHeight; 
    }
    var containerHeight = this.get('containerView').get('frame').height ;
    return Math.max(0, contentHeight-containerHeight) ;
  }.property(),
  
  /** 
    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 frame height.
  */
  verticalPageScroll: function() {
    return this.get('frame').height ;
  }.property('frame'),
  
  /**
    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('frame').width ;  
  }.property('frame'),
    
  // ..........................................................
  // SCROLLERS
  // 
  
  /** 
    YES if the view should maintain a horizontal scroller.   This property 
    must be set when the view is created.
    
    @property {Boolean}
  */
  hasHorizontalScroller: YES,
  
  /**
    The horizontal scroller view class. This will be replaced with a view 
    instance when the ScrollView is created unless hasHorizontalScroller is 
    NO.
    
    @property {SC.View}
  */
  horizontalScrollerView: SC.ScrollerView,
  
  /**
    YES if the horizontal scroller should be visible.  You can change this 
    property value anytime to show or hide the horizontal scroller.  If you 
    do not want to use a horizontal scroller at all, you should instead set 
    hasHorizontalScroller to NO to avoid creating a scroller view in the 
    first place.
    
    @property {Boolean}
  */
  isHorizontalScrollerVisible: YES,

  /**
    Returns YES if the view both has a horizontal scroller, the scroller is
    visible.
    
    @property {Boolean}
  */
  canScrollHorizontal: function() {
    return !!(this.get('hasHorizontalScroller') && 
      this.get('horizontalScrollerView') && 
      this.get('isHorizontalScrollerVisible')) ;
  }.property('isHorizontalScrollerVisible').cacheable(),
  
  /**
    If YES, the horizontal scroller will autohide if the contentView is
    smaller than the visible area.  You must set hasHorizontalScroller to YES 
    for this property to have any effect.  
  */
  autohidesHorizontalScroller: YES,
  
  /** 
    YES if the view shuld maintain a vertical scroller.   This property must 
    be set when the view is created.
    
    @property {Boolean}
  */
  hasVerticalScroller: YES,
  
  /**
    The vertical scroller view class. This will be replaced with a view 
    instance when the ScrollView is created unless hasVerticalScroller is NO.
    
    @property {SC.View}
  */
  verticalScrollerView: SC.ScrollerView,
  
  /**
    YES if the vertical scroller should be visible.  You can change this 
    property value anytime to show or hide the vertical scroller.  If you do 
    not want to use a vertical scroller at all, you should instead set 
    hasVerticalScroller to NO to avoid creating a scroller view in the first 
    place.
    
    @property {Boolean}
  */
  isVerticalScrollerVisible: YES,

  /**
    Returns YES if the view both has a horizontal scroller, the scroller is
    visible.
    
    @property {Boolean}
  */
  canScrollVertical: function() {
    return !!(this.get('hasVerticalScroller') && 
      this.get('verticalScrollerView') && 
      this.get('isVerticalScrollerVisible')) ;
  }.property('isVerticalScrollerVisible').cacheable(),

  /**
    If YES, the vertical scroller will autohide if the contentView is
    smaller than the visible area.  You must set hasVerticalScroller to YES 
    for this property to have any effect.  
  */
  autohidesVerticalScroller: YES,
  
  /**
    Use this property to set the 'bottom' offset of your vertical scroller, 
    to make room for a thumb view or other accessory view. Default is 0.
    
    @property {Number}
  */
  verticalScrollerBottom: 0,
  
  
  // ..........................................................
  // CUSTOM VIEWS
  // 
  
  /**
    The container view that will contain your main content view.  You can 
    replace this property with your own custom subclass if you prefer.
    
    @type {SC.ContainerView}
  */
  containerView: SC.ContainerView,
  
  // ..........................................................
  // METHODS
  // 
  
  /**
    Scrolls the receiver to the specified x,y coordinate.  This should be the
    offset into the contentView you want to appear at the top-left corner of
    the scroll view.
    
    This method will contrain the actual scroll based on whether the view
    can scroll in the named direction and the maximum distance it can
    scroll.
    
    If you only want to scroll in one direction, pass null for the other 
    direction.  You can also optionally pass a Hash for the first parameter 
    with x and y coordinates.
    
    @param x {Number} the x scroll location
    @param y {Number} the y scroll location
    @returns {SC.ScrollView} receiver
  */
  scrollTo: function(x,y) {
    // normalize params
    if (y===undefined && SC.typeOf(x) === SC.T_HASH) {
      y = x.y; x = x.x;
    }
    
    if (!SC.none(x)) {
      x = Math.max(0,Math.min(this.get('maximumHorizontalScrollOffset'), x)) ;
      this.set('horizontalScrollOffset', x) ;
    }
    
    if (!SC.none(y)) {
      y = Math.max(0,Math.min(this.get('maximumVerticalScrollOffset'), y)) ;
      this.set('verticalScrollOffset', y) ;
    }
    
    return this ;
  },
  
  /**
    Scrolls the receiver in the horizontal and vertical directions by the 
    amount specified, if allowed.  The actual scroll amount will be 
    constrained by the current scroll view settings.
    
    If you only want to scroll in one direction, pass null or 0 for the other 
    direction.  You can also optionally pass a Hash for the first parameter 
    with x and y coordinates.
    
    @param x {Number} change in the x direction (or hash)
    @param y {Number} change in the y direction
    @returns {SC.ScrollView} receiver
  */
  scrollBy: function(x , y) {
    // normalize params
    if (y===undefined && SC.typeOf(x) === SC.T_HASH) {
      y = x.y; x = x.x;
    }
    
    // if null, undefined, or 0, pass null; otherwise just add current offset
    x = (x) ? this.get('horizontalScrollOffset')+x : null ;
    y = (y) ? this.get('verticalScrollOffset')+y : null ;
    return this.scrollTo(x,y) ;
  },
  
  /**
    Scroll the view to make the view's frame visible.  For this to make sense,
    the view should be a subview of the contentView.  Otherwise the results
    will be undefined.
    
    @param {SC.View} view view to scroll or null to scroll receiver visible
    @returns {Boolean} YES if scroll position was changed
  */
  scrollToVisible: function(view) {
    
    // if no view is passed, do default
    if (arguments.length === 0) return arguments.callee.base.apply(this,arguments); 
    
    var contentView = this.get('contentView') ;
    if (!contentView) return NO; // nothing to do if no contentView.

    // get the frame for the view - should work even for views with static 
    // layout, assuming it has been added to the screen.
    var vf = view.get('frame');
    if (!vf) return NO; // nothing to do
    
    // convert view's frame to an offset from the contentView origin.  This
    // will become the new scroll offset after some adjustment.
    vf = contentView.convertFrameFromView(vf, view.get('parentView')) ;
    
    // find current visible frame.
    var vo = SC.cloneRect(this.get('containerView').get('frame')) ;
    
    vo.x = this.get('horizontalScrollOffset') ;
    vo.y = this.get('verticalScrollOffset') ;

    var origX = vo.x, origY = vo.y;
    
    // 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.
    if ((origX !== vo.x) || (origY !== vo.y)) {
      this.scrollTo(vo.x, vo.y);
      return YES ;
    } else return NO;
  },
  
  /**
    Scrolls the receiver down one or more lines if allowed.  If number of
    lines is not specified, scrolls one line.
    
    @param lines {Number} options number of lines
    @returns {SC.ScrollView} receiver
  */
  scrollDownLine: function(lines) {
    if (lines === undefined) lines = 1 ;
    return this.scrollBy(null, this.get('verticalLineScroll')*lines) ;
  },
  
  /**
    Scrolls the receiver up one or more lines if allowed.  If number of
    lines is not specified, scrolls one line.
    
    @param lines {Number} options number of lines
    @returns {SC.ScrollView} receiver
  */
  scrollUpLine: function(lines) {
    if (lines === undefined) lines = 1 ;
    return this.scrollBy(null, 0-this.get('verticalLineScroll')*lines) ;
  },
  
  /**
    Scrolls the receiver right one or more lines if allowed.  If number of
    lines is not specified, scrolls one line.
    
    @param lines {Number} options number of lines
    @returns {SC.ScrollView} receiver
  */
  scrollRightLine: function(lines) {
    if (lines === undefined) lines = 1 ;
    return this.scrollTo(this.get('horizontalLineScroll')*lines, null) ;
  },
  
  /**
    Scrolls the receiver left one or more lines if allowed.  If number of
    lines is not specified, scrolls one line.
    
    @param lines {Number} options number of lines
    @returns {SC.ScrollView} receiver
  */
  scrollLeftLine: function(lines) {
    if (lines === undefined) lines = 1 ;
    return this.scrollTo(0-this.get('horizontalLineScroll')*lines, null) ;
  },
  
  /**
    Scrolls the receiver down one or more page if allowed.  If number of
    pages is not specified, scrolls one page.  The page size is determined by
    the verticalPageScroll value.  By default this is the size of the current
    scrollable area.
    
    @param pages {Number} options number of pages
    @returns {SC.ScrollView} receiver
  */
  scrollDownPage: function(pages) {
    if (pages === undefined) pages = 1 ;
    return this.scrollBy(null, this.get('verticalPageScroll')*pages) ;
  },
  
  /**
    Scrolls the receiver up one or more page if allowed.  If number of
    pages is not specified, scrolls one page.  The page size is determined by
    the verticalPageScroll value.  By default this is the size of the current
    scrollable area.
    
    @param pages {Number} options number of pages
    @returns {SC.ScrollView} receiver
  */
  scrollUpPage: function(pages) {
    if (pages === undefined) pages = 1 ;
    return this.scrollBy(null, 0-(this.get('verticalPageScroll')*pages)) ;
  },
  
  /**
    Scrolls the receiver right one or more page if allowed.  If number of
    pages is not specified, scrolls one page.  The page size is determined by
    the verticalPageScroll value.  By default this is the size of the current
    scrollable area.
    
    @param pages {Number} options number of pages
    @returns {SC.ScrollView} receiver
  */
  scrollRightPage: function(pages) {
    if (pages === undefined) pages = 1 ;
    return this.scrollBy(this.get('horizontalPageScroll')*pages, null) ;
  },
  
  /**
    Scrolls the receiver left one or more page if allowed.  If number of
    pages is not specified, scrolls one page.  The page size is determined by
    the verticalPageScroll value.  By default this is the size of the current
    scrollable area.
    
    @param pages {Number} options number of pages
    @returns {SC.ScrollView} receiver
  */
  scrollLeftPage: function(pages) {
    if (pages === undefined) pages = 1 ;
    return this.scrollBy(0-(this.get('horizontalPageScroll')*pages), null) ;
  },
  
  /**
    Adjusts the layout for the various internal views.  This method is called
    once when the scroll view is first configured and then anytime a scroller
    is shown or hidden.  You can call this method yourself as well to retile.
    
    You may also want to override this method to handle layout for any
    additional controls you have added to the view.
  */
  tile: function() {
    // get horizontal scroller/determine if we should have a scroller
    var hscroll = this.get('hasHorizontalScroller') ? this.get('horizontalScrollerView') : null ;
    var hasHorizontal = hscroll && this.get('isHorizontalScrollerVisible');
    
    // get vertical scroller/determine if we should have a scroller
    var vscroll = this.get('hasVerticalScroller') ? this.get('verticalScrollerView') : null ;
    var hasVertical = vscroll && this.get('isVerticalScrollerVisible') ;
    
    // get the containerView
    var clip = this.get('containerView') ;
    var clipLayout = { left: 0, top: 0 } ;
    var t ;
    
    var ht = ((hasHorizontal) ? hscroll.get('scrollerThickness') : 0) ;
    var vt = (hasVertical) ?   vscroll.get('scrollerThickness') : 0 ;
    
    if (hasHorizontal) {
      hscroll.set('layout', { left: 0, bottom: 0, right: vt-1, height: ht }) ;
      clipLayout.bottom = ht-1 ;
    } else {
      clipLayout.bottom = 0 ;
    }
    if (hscroll) hscroll.set('isVisible', hasHorizontal) ;
    
    if (hasVertical) {
      ht = ht + this.get('verticalScrollerBottom') ;
      vscroll.set('layout', { top: 0, bottom: ht, right: 0, width: vt }) ;
      clipLayout.right = vt-1 ;
    } else {
      clipLayout.right = 0 ;
    }
    if (vscroll) vscroll.set('isVisible', hasVertical) ;
    
    clip.set('layout', clipLayout) ;
  },
  
  /** @private
    Called whenever a scroller visibility changes.  Calls the tile() method.
  */
  scrollerVisibilityDidChange: function() {
    this.tile();
  }.observes('isVerticalScrollerVisible', 'isHorizontalScrollerVisible'),
  
  // ..........................................................
  // SCROLL WHEEL SUPPORT
  // 
  
  /** @private */ _scroll_wheelDeltaX: 0,
  /** @private */ _scroll_wheelDeltaY: 0,
  
  // save adjustment and then invoke the actual scroll code later.  This will
  // keep the view feeling smooth.
  mouseWheel: function(evt) {
    this._scroll_wheelDeltaX += evt.wheelDeltaX;
    this._scroll_wheelDeltaY += evt.wheelDeltaY;
    this.invokeLater(this._scroll_mouseWheel, 10) ;
    return this.get('canScrollHorizontal') || this.get('canScrollVertical') ;  
  },
  
  /** @private */
  _scroll_mouseWheel: function() {
    this.scrollBy(this._scroll_wheelDeltaX, this._scroll_wheelDeltaY);
    this._scroll_wheelDeltaX = this._scroll_wheelDeltaY = 0;
  },
  
  
  // ..........................................................
  // INTERNAL SUPPORT
  // 
  
  /** @private
    Instantiate scrollers & container views as needed.  Replace their classes
    in the regular properties.
  */
  createChildViews: function() {
    var childViews = [] , view; 
       
    // create the containerView.  We must always have a container view. 
    // also, setup the contentView as the child of the containerView...
    if (SC.none(view = this.containerView)) view = SC.ContainerView;
    
    childViews.push(this.containerView = this.createChildView(view, {
      contentView: this.contentView
    }));
    
    // and replace our own contentView...
    this.contentView = this.containerView.get('contentView');
    
    // create a horizontal scroller view if needed...
    if (view=this.horizontalScrollerView) {
      if (this.get('hasHorizontalScroller')) {
        view = this.horizontalScrollerView = this.createChildView(view, {
          layoutDirection: SC.LAYOUT_HORIZONTAL,
          valueBinding: '*owner.horizontalScrollOffset'
        }) ;
        childViews.push(view);
      } else this.horizontalScrollerView = null ;
    }
    
    // create a vertical scroller view if needed...
    if (view=this.verticalScrollerView) {
      if (this.get('hasVerticalScroller')) {
        view = this.verticalScrollerView = this.createChildView(view, {
          layoutDirection: SC.LAYOUT_VERTICAL,
          valueBinding: '*owner.verticalScrollOffset'
        }) ;
        childViews.push(view);
      } else this.verticalScrollerView = null ;
    }
    
    // set childViews array.
    this.childViews = childViews ;
    
    this.contentViewDidChange() ; // setup initial display...
    this.tile() ; // set up initial tiling
  },
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    
    // start observing initial content view.  The content view's frame has
    // already been setup in prepareDisplay so we don't need to call 
    // viewFrameDidChange...
    this._scroll_contentView = this.get('contentView') ;
    var contentView = this._scroll_contentView ;

    if (contentView) {
      contentView.addObserver('frame', this, this.contentViewFrameDidChange) ;
    }

    if (this.get('isVisibleInWindow')) this._scsv_registerAutoscroll() ;
  },
  
  /** @private Registers/deregisters view with SC.Drag for autoscrolling */
  _scsv_registerAutoscroll: function() {
    if (this.get('isVisibleInWindow')) SC.Drag.addScrollableView(this);
    else SC.Drag.removeScrollableView(this);
  }.observes('isVisibleInWindow'),
  
  /** @private
    Whenever the contentView is changed, we need to observe the content view's
    frame to be notified whenever it's size changes.
  */
  contentViewDidChange: function() {
    var newView = this.get('contentView'), oldView = this._scroll_contentView;
    var f = this.contentViewFrameDidChange ;
    if (newView !== oldView) {
      
      // stop observing old content view
      if (oldView) oldView.removeObserver('frame', this, f);
      
      // update cache
      this._scroll_contentView = newView;
      if (newView) newView.addObserver('frame', this, f);
      
      // replace container
      this.containerView.set('contentView', newView);
      
      this.contentViewFrameDidChange();
    }
  }.observes('contentView'),
  
  /** @private
    Invoked whenever the contentView's frame changes.  This will update the 
    scroller maxmimum and optionally update the scroller visibility if the
    size of the contentView changes.  We don't care about the origin since
    that is tracked separately from the offset values.
  */
  
  oldMaxHOffset: 0,
  oldMaxVOffset: 0,
  
  contentViewFrameDidChange: function() {
        
    var view   = this.get('contentView'), 
        f      = (view) ? view.get('frame') : null,
        width  = (f) ? f.width : 0,  
        height = (f) ? f.height : 0,
        dim    = this.get('frame') ;
    
    // cache out scroll settings...
    //if ((width === this._scroll_contentWidth) && (height === this._scroll_contentHeight)) return ;
    this._scroll_contentWidth = width;
    this._scroll_contentHeight = height ;
    
    if (this.get('hasHorizontalScroller') && (view = this.get('horizontalScrollerView'))) {
      width -= 1 ; // accurately account for our layout
      // decide if it should be visible or not
      if (this.get('autohidesHorizontalScroller')) {
        this.set('isHorizontalScrollerVisible', width > dim.width);
      }
      view.setIfChanged('maximum', width) ;
    }
    
    if (this.get('hasVerticalScroller') && (view = this.get('verticalScrollerView'))) {
      height -= 1 ; // accurately account for our layout
      // decide if it should be visible or not
      if (this.get('autohidesVerticalScroller')) {
        this.set('isVerticalScrollerVisible', height > dim.height);
      }
      height -= this.get('verticalScrollerBottom') ;
      view.setIfChanged('maximum', height) ;
    }
    
    // If there is no vertical scroller and auto hiding is on, make
    // sure we are at the top if not already there
    if (!this.get('isVerticalScrollerVisible') && (this.get('verticalScrollOffset') !== 0) && 
       this.get('autohidesVerticalScroller')) {
      this.set('verticalScrollOffset', 0);
    }
    
    // Same thing for horizontal scrolling.
    if (!this.get('isHorizontalScrollerVisible') && (this.get('horizontalScrollOffset') !== 0) && 
       this.get('autohidesHorizontalScroller')) {
      this.set('horizontalScrollOffset', 0);
    }
    
    // This forces to recalculate the height of the frame when is at the bottom
    // of the scroll and the content dimension are smaller that the previous one
    
    var mxVOffSet = this.get('maximumVerticalScrollOffset'),
        vOffSet = this.get('verticalScrollOffset'),
        mxHOffSet = this.get('maximumHorizontalScrollOffset'),
        hOffSet = this.get('horizontalScrollOffset');
    var forceHeight = mxVOffSet<vOffSet;
    var forceWidth = mxHOffSet<hOffSet;
    if(forceHeight || forceWidth){
      this.forceDimensionsRecalculation(forceWidth, forceHeight, vOffSet, hOffSet);
    }
  },
  
  /** @private
    Whenever the horizontal scroll offset changes, update the scrollers and 
    edit the location of the contentView.
  */
  _scroll_horizontalScrollOffsetDidChange: function() {
    var offset = this.get('horizontalScrollOffset');
    
    offset = Math.max(0,Math.min(this.get('maximumHorizontalScrollOffset'), offset)) ;
    
    // update the offset for the contentView...
    var contentView = this.get('contentView');
    if (contentView) contentView.adjust('left', 0-offset);
     
  }.observes('horizontalScrollOffset'),
  
  /** @private
    Whenever the vertical scroll offset changes, update the scrollers and 
    edit the location of the contentView.
  */
  _scroll_verticalScrollOffsetDidChange: function() {
    var offset = this.get('verticalScrollOffset') ;
    
    offset = Math.max(0,Math.min(this.get('maximumVerticalScrollOffset'), offset)) ;
    
    // update the offset for the contentView...
    var contentView = this.get('contentView');
    var containerView = this.get('containerView');
    
    // Optimization when not using collections. We need to reimplement clippingFrame
    // and scrolling to be able to scroll using scrolltop. For now I just
    // detect if the content to scroll is a class of collectionView.
    if (contentView) contentView.adjust('top', 0-offset) ;
    
  }.observes('verticalScrollOffset'),
  
  forceDimensionsRecalculation: function (forceWidth, forceHeight, vOffSet, hOffSet) {
    var oldScrollHOffset = hOffSet;
    var oldScrollVOffset = vOffSet;
    this.scrollTo(0,0);
    if(forceWidth && forceHeight){
      this.scrollTo(this.get('maximumHorizontalScrollOffset'), this.get('maximumVerticalScrollOffset'));
    }
    if(forceWidth && !forceHeight){
      this.scrollTo(this.get('maximumHorizontalScrollOffset'), oldScrollVOffset);
    }
    if(!forceWidth && forceHeight){
      this.scrollTo(oldScrollHOffset ,this.get('maximumVerticalScrollOffset'));
    }
  }
  
});

/* >>>>>>>>>> BEGIN source/views/menu_scroll.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/scroll');

/** @class

  Implements a complete scroller view for menus.  This class implements the
  arrows displayed in a menu to scroll.
  
  The main difference with SC.ScrollerView is that there is only vertical 
  scrollers. Value Syncing between SC.MenuScrollView and SC.MenuScrollerView
  is done using valueBinding.
  
  @extends SC.ScrollerView
  @since SproutCore 1.0
*/

SC.MenuScrollerView = SC.ScrollerView.extend({
  classNames: ['sc-menu-scroller-view'],
  
  // ..........................................................
  // PROPERTIES
  // 
  
  /**
   Used to set the scrolling direction of the scroller.
  */
  scrollDown: NO,
  
  /** 
    The scroller offset value.  This value will adjust between the minimum
    and maximum values that you set. Default is 0.
    
    @property
  */
  value: function(key, val) {
    if (val !== undefined) {
      // Don't enforce the maximum now, because the scroll view could change
      // height and we want our content to stay put when it does.
      this._value = val ;
    } else {
      var value = this._value || 0 ; // default value is at top/left
      return Math.min(value, this.get('maximum')) ;
    }
  }.property('maximum').cacheable(),
  
  /**
    The maximum offset value for the scroller.  This will be used to calculate
    the internal height/width of the scroller itself. It is not necessarily
    the same as the height of a scroll view's content view.
    
    When set less than the height of the scroller, the scroller is disabled.
    
    @property {Number}
  */
  maximum: 0,
  
  /**
    YES if enable scrollbar, NO to disable it.  Scrollbars will automatically 
    disable if the maximum scroll width does not exceed their capacity.
    
    @property
  */
  isEnabled: YES,
  
  /**
    Determine the layout direction.  Determines whether the scrollbar should 
    appear horizontal or vertical.  This must be set when the view is created.
    Changing this once the view has been created will have no effect.
    
    @property
  */
  layoutDirection: SC.LAYOUT_VERTICAL,
  
  /** 
     Amount to scroll one vertical line.
     Defaults to 20px.
  */
  verticalLineScroll: 20,
  
  /**
    This function overrides the default function in SC.Scroller as 
    menus only have vertical scrolling.
    
    @property {String}
  */
  ownerScrollValueKey: function() {
    return 'verticalScrollOffset' ;  
  }.property('layoutDirection').cacheable(),
  
  // ..........................................................
  // INTERNAL SUPPORT
  // 
  
  render: function(context, firstTime) {
    context.addClass('sc-vertical') ;
    if (firstTime) {
      if(this.get('scrollDown')){
        context.push('<span class="arrowDown">&nbsp;</span>') ;
      }else{
        context.push('<span class="arrowUp">&nbsp;</span>') ;
      }
    } 
    context.setClass('disabled', !this.get('isEnabled')) ;
  },
  
  didCreateLayer: function() {
    var callback, amt, layer;
    
    callback = this._sc_scroller_scrollDidChange ;
    SC.Event.add(this.$(), 'scroll', this, callback) ;
    
    // set scrollOffset first time
    amt = this.get('value') ;
    layer = this.get('layer') ;
    
    layer.scrollTop = amt ;
  },
  
  willDestroyLayer: function() {
    var callback = this._sc_scroller_scrollDidChange ;
    SC.Event.remove(this.$(), 'scroll', this, callback) ;
  },
  
  mouseEntered: function(evt) {
    this.set('isMouseOver', YES);
    this._invokeScrollOnMouseOver();
  },
  
  mouseExited: function(evt) {
    this.set('isMouseOver', NO);
  },
  
  /** @private */
  
  /**
    This function overrides the default function in SC.Scroller. 
    SC.MenuScroller and SC.MenuScroll use valueBinding so this function is
    not neccesary.
  */
  _sc_scroller_valueDidChange: function() {
    
  }.observes('value'),
  

  // after 50msec, fire event again
  _sc_scroller_armScrollTimer: function() {
    if (!this._sc_scrollTimer) {
      SC.RunLoop.begin() ;
      var method = this._sc_scroller_scrollDidChange ;
      this._sc_scrollTimer = this.invokeLater(method, 50) ;
      SC.RunLoop.end() ;
    }
  },
  
  _sc_scroller_scrollDidChange: function() {
    var now = Date.now(), 
        last = this._sc_lastScroll, 
        layer = this.get('layer'), 
        scroll = 0 ;
    
    if (last && (now-last)<50) return this._sc_scroller_armScrollTimer() ;
    this._sc_scrollTimer = null ;
    this._sc_lastScroll = now ;
    
    SC.RunLoop.begin();
    
    if (!this.get('isEnabled')) return ; // nothing to do.
    
    this._sc_scrollValue = scroll = layer.scrollTop ;
    this.set('value', scroll) ; // will now enforce minimum and maximum
    
    SC.RunLoop.end();
  },
  
  
  /**
    Scroll the menu if it is is an up or down arrow. This is called by
    the function that simulates mouseOver.
  */
  _scrollMenu: function(){
    var val = this.get('value'), newval;
    if(this.get('scrollDown')) {
      newval = val+this.verticalLineScroll;
      if(newval<=this.get('maximum')){
        this.set('value', newval);
      }
    }
    else {
      newval = val-this.verticalLineScroll;
      if(newval>=0){
        this.set('value', newval);
      }else if(val<=this.verticalLineScroll && val>0){
        this.set('value', 0);
      }
    }
    return YES;
  },
  
  /**
    We use this function to simulate mouseOver. It checks for the flag 
    isMouseOver which is turned on when mouseEntered is called and turned off
    when mouseExited is called. 
  */
  _invokeScrollOnMouseOver: function(){
    this._scrollMenu();
    if(this.get('isMouseOver')){
      this.invokeLater(this._invokeScrollOnMouseOver, 50);
    }
  }
  
});

/** @class

  Implements a scroll view for menus.  This class extends SC.ScrollView for 
  menus. 
  
  The main difference with SC.ScrollView is that there is only vertical 
  scrolling. Value Syncing between SC.MenuScrollView and SC.MenuScrollerView
  is done using valueBinding.
  
  @extends SC.ScrollView
  @since SproutCore 1.0
*/
SC.MenuScrollView = SC.ScrollView.extend({

  classNames: ['sc-menu-scroll-view'],
  
  // ..........................................................
  // PROPERTIES
  // 
  
  
  /**
    The maximum horizontal scroll offset allowed given the current contentView 
    size and the size of the scroll view.  If horizontal scrolling is 
    disabled, this will always return 0.
    
    @property {Number}
  */
  maximumHorizontalScrollOffset: function() {
  }.property(),
    
       
  // ..........................................................
  // SCROLLERS
  // 
  
  /** 
    YES if the view should maintain a horizontal scroller.   This property 
    must be set when the view is created.
    
    @property {Boolean}
  */
  hasHorizontalScroller: NO,
  
  /**
    The horizontal scroller view class. This will be replaced with a view 
    instance when the ScrollView is created unless hasHorizontalScroller is 
    NO.
    
    @property {SC.View}
  */
  horizontalScrollerView: SC.MenuScrollerView,
  
  /**
    YES if the horizontal scroller should be visible.  You can change this 
    property value anytime to show or hide the horizontal scroller.  If you 
    do not want to use a horizontal scroller at all, you should instead set 
    hasHorizontalScroller to NO to avoid creating a scroller view in the 
    first place.
    
    @property {Boolean}
  */
  isHorizontalScrollerVisible: NO,

  /**
    Returns YES if the view both has a horizontal scroller, the scroller is
    visible.
    
    @property {Boolean}
  */
  canScrollHorizontal: function() {
    return false; 
  }.property('isHorizontalScrollerVisible').cacheable(),
   
  /**
    If YES, the horizontal scroller will autohide if the contentView is
    smaller than the visible area.  You must set hasHorizontalScroller to YES 
    for this property to have any effect.  
  */
  autohidesHorizontalScroller: NO,
  
  /** 
    YES if the view shuld maintain a vertical scroller.   This property must 
    be set when the view is created.
    
    @property {Boolean}
  */
  hasVerticalScroller: YES,
  
  /**
    The vertical scroller view class. This will be replaced with a view 
    instance when the ScrollView is created unless hasVerticalScroller is NO.
    
    @property {SC.View}
  */
  verticalScrollerView: SC.MenuScrollerView,
  verticalScrollerView2: SC.MenuScrollerView,
  
  /**
    YES if the vertical scroller should be visible.  For SC.MenuScroll the
    vertical scroller is always there we just hide the arrows to scroll.
    
    @property {Boolean}
  */
  isVerticalScrollerVisible: YES,

  
  canScrollVertical: function() {
    return YES;
  }.property('isVerticalScrollerVisible').cacheable(),

  /**
    If YES, the vertical scroller will autohide if the contentView is
    smaller than the visible area.  You must set hasVerticalScroller to YES 
    for this property to have any effect.  
  */
  autohidesVerticalScroller: YES,
  
  /**
    Use this property to set the 'bottom' offset of your vertical scroller, 
    to make room for a thumb view or other accessory view. Default is 0.
    
    @property {Number}
  */
  verticalScrollerBottom: 0,
  
  
  // ..........................................................
  // CUSTOM VIEWS
  // 
  
  /**
    The container view that will contain your main content view.  You can 
    replace this property with your own custom subclass if you prefer.
    
    @type {SC.ContainerView}
  */
  containerView: SC.ContainerView,
  
  // ..........................................................
  // METHODS
  // 

  
  /**
    Adjusts the layout for the various internal views.  This method is called
    once when the scroll view is first configured and then anytime a scroller
    is shown or hidden.  You can call this method yourself as well to retile.
    
    You may also want to override this method to handle layout for any
    additional controls you have added to the view.
  */
  tile: function() {
    // get vertical scroller/determine if we should have a scroller
    var hasScroller, vscroll, vscroll2, hasVertical, clip, clipLayout, viewportHeight;
    hasScroller = this.get('hasVerticalScroller');
    vscroll = hasScroller ? this.get('verticalScrollerView') : null ;
    vscroll2 = hasScroller ? this.get('verticalScrollerView2') : null ;
    hasVertical = vscroll && this.get('isVerticalScrollerVisible') ;
    
    // get the containerView
    clip = this.get('containerView') ;
    clipLayout = { left: 0, top: 0 } ;
    
    if (hasVertical) {
      viewportHeight =0;
      var view   = this.get('contentView'), view2, 
            f      = (view) ? view.get('frame') : null, 
            height = (f) ? f.height : 0,
            elem = this.containerView.$()[0],
            verticalOffset = this.get('verticalScrollOffset'),
            topArrowInvisible = { height: 0, top: 0, right: 0, left: 0 },
            topArrowVisible = { height: this.verticalLineScroll, top: 0, right: 0, left: 0 },
            bottomArrowVisible = { height: this.verticalLineScroll, bottom: 0, right: 0, left: 0 },
            bottomArrowInvisible = { height: 0, bottom: 0, right: 0, left: 0 };
      
      if(elem) viewportHeight = elem.offsetHeight;
      
      if(verticalOffset===0){
        clipLayout.top = 0 ;
        clipLayout.bottom = this.verticalLineScroll;
        vscroll.set('layout', topArrowInvisible) ;
        vscroll2.set('layout', bottomArrowVisible) ;
      }else if(verticalOffset>=(height-viewportHeight-this.verticalLineScroll)){
        clipLayout.top = this.verticalLineScroll ;
        clipLayout.bottom = 0 ;
        vscroll.set('layout', topArrowVisible) ;
        vscroll2.set('layout', bottomArrowInvisible) ;
      }else{
        clipLayout.top = this.verticalLineScroll ;
        clipLayout.bottom = this.verticalLineScroll ;
        vscroll.set('layout', topArrowVisible) ;
        vscroll2.set('layout', bottomArrowVisible) ;
      }
    } 
    if (vscroll){
     vscroll.set('isVisible', hasVertical) ;
     vscroll2.set('isVisible', hasVertical) ;
    }
    clip.set('layout', clipLayout) ;
  },
  
  /** @private
    Called whenever a scroller visibility changes.  Calls the tile() method.
  */
  scrollerVisibilityDidChange: function() {
    this.tile();
  }.observes('isVerticalScrollerVisible', 'isHorizontalScrollerVisible', 'verticalScrollOffset'),
    
  
  // ..........................................................
  // INTERNAL SUPPORT
  // 
  
  /** @private
    Instantiate scrollers & container views as needed.  Replace their classes
    in the regular properties.
  */
  createChildViews: function() {
    var childViews = [], view, view2 ;
    
    // create the containerView.  We must always have a container view. 
    // also, setup the contentView as the child of the containerView...
    if (SC.none(view = this.containerView)) view = SC.ContainerView;
    
    childViews.push(this.containerView = this.createChildView(view, {
      contentView: this.contentView
    }));
    
    // and replace our own contentView...
    this.contentView = this.containerView.get('contentView');
    
    // create a vertical scroller 
    if ((view=this.verticalScrollerView) && (view2=this.verticalScrollerView2)) {
      if (this.get('hasVerticalScroller')) {
        view = this.verticalScrollerView = this.createChildView(view, {
          layout: {top: 0, left: 0, right: 0, height: this.verticalLineScroll},
          valueBinding: '*owner.verticalScrollOffset'
        }) ;
        childViews.push(view);
        view2 = this.verticalScrollerView2 = this.createChildView(view2, {
          scrollDown: YES,
          layout: {bottom: 0, left: 0, right: 0, height: this.verticalLineScroll},
          valueBinding: '*owner.verticalScrollOffset'
        }) ;
        childViews.push(view2);
      } else {
        this.verticalScrollerView = null ;
        this.verticalScrollerView2 = null ;
      }
    }
    
    // set childViews array.
    this.childViews = childViews ;
    
    this.contentViewFrameDidChange() ; // setup initial display...
    this.tile() ; // set up initial tiling
  },
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    
    // start observing initial content view.  The content view's frame has
    // already been setup in prepareDisplay so we don't need to call 
    // viewFrameDidChange...
    this._scroll_contentView = this.get('contentView') ;
    var contentView = this._scroll_contentView ;

    if (contentView) {
      contentView.addObserver('frame', this, this.contentViewFrameDidChange) ;
    }

    if (this.get('isVisibleInWindow')) this._scsv_registerAutoscroll() ;
  },
  
  /** @private Registers/deregisters view with SC.Drag for autoscrolling */
  _scsv_registerAutoscroll: function() {
    if (this.get('isVisibleInWindow')) SC.Drag.addScrollableView(this);
    else SC.Drag.removeScrollableView(this);
  }.observes('isVisibleInWindow'),
  
  /** @private
    Whenever the contentView is changed, we need to observe the content view's
    frame to be notified whenever it's size changes.
  */
  contentViewDidChange: function() {
    var newView = this.get('contentView'), 
        oldView = this._scroll_contentView,
        f = this.contentViewFrameDidChange ;
      
    if (newView !== oldView) {
      
      // stop observing old content view
      if (oldView) oldView.removeObserver('frame', this, f);
      
      // update cache
      this._scroll_contentView = newView;
      if (newView) newView.addObserver('frame', this, f);
      
      // replace container
      this.containerView.set('content', newView);
      
      this.contentViewFrameDidChange();
    }
  }.observes('contentView'),
  
  /** @private
    Invoked whenever the contentView's frame changes.  This will update the 
    scroller maxmimum and optionally update the scroller visibility if the
    size of the contentView changes.  We don't care about the origin since
    that is tracked separately from the offset values.
  */
  contentViewFrameDidChange: function() {
    var view   = this.get('contentView'), view2, 
        f      = (view) ? view.get('frame') : null,
        width  = (f) ? f.width : 0,  
        height = (f) ? f.height : 0,
        dim    = this.get('frame'),
        viewportHeight, elem ;
        
    // cache out scroll settings...
    //if ((width === this._scroll_contentWidth) && (height === this._scroll_contentHeight)) return ;
    this._scroll_contentWidth = width;
    this._scroll_contentHeight = height ;
    
    if (this.get('hasVerticalScroller') && (view = this.get('verticalScrollerView')) && (view2 = this.get('verticalScrollerView2'))) {
      height -= 1 ; // accurately account for our layout
      // decide if it should be visible or not
      if (this.get('autohidesVerticalScroller')) {
        this.set('isVerticalScrollerVisible', height > dim.height);
      }
      height -= this.get('verticalScrollerBottom') ;
      viewportHeight = 0;
      elem = this.containerView.$()[0];
      if(elem) viewportHeight = elem.offsetHeight;
      height = height - viewportHeight;
      view.setIfChanged('maximum', height) ;
      view2.setIfChanged('maximum', height) ;
    }
  },
  
  /** @private
    Whenever the horizontal scroll offset changes, update the scrollers and 
    edit the location of the contentView.
  */
  _scroll_horizontalScrollOffsetDidChange: function() {
  }.observes('horizontalScrollOffset'),
   
  /** @private
    Whenever the vertical scroll offset changes, update the scrollers and 
    edit the location of the contentView.
  */
  _scroll_verticalScrollOffsetDidChange: function() {
    var offset = this.get('verticalScrollOffset') ;
    
    // update the offset for the contentView...
    var contentView = this.get('contentView');
    if (contentView) contentView.adjust('top', 0-offset) ;
    
  }.observes('verticalScrollOffset')

});
/* >>>>>>>>>> BEGIN source/views/popup_button.js */
/**
  @class

  @extends SC.ButtonView
  @author Santosh Shanbhogue
  @copyright 2008-2009, Sprout Systems, Inc. and contributors.
  @version 1.0
*/
sc_require('views/button');
SC.PopupButtonView = SC.ButtonView.extend({
  /**
    This property shows whether the menu is currently displayed or not
  */
  keyEquivalent: null,
  classNames: ['sc-popup-button'],
  
  /**
    Prefer matrix to pass the offsets to position the pane popped up by this 
    button.
    
    @property
  */
  preferMatrix: null,
    
  /**private */
  acceptsFirstResponder: YES,
  /**
    Overriding the default SC.ButtonView#performKeyEquivalent method to pass 
    it onto the menu
    
    @param {string} keystring method name corresponding to the keys pressed 
    (i.e alt_shift_z)
    @param {DOMMouseEvent} evt mousedown event
  */
  isSelected: NO,
  performKeyEquivalent: function( charCode, evt )
  {
    if (!this.get('isEnabled')) return NO ;
    var menu = this.get('menu') ;
    return (!!menu && menu.performKeyEquivalent(charCode, evt)) ;
  },
  
  /**
    Menu attached to the popupButton
    @default SC.MenuView
  */
  menu : null,
  
  /**
    Binds the button's selection state to the menu's visibility.
    @private
  */
  isSelectedBinding: '*menu.isVisibleInWindow',
  
  /**
    Button action handler
    @param {DOMMouseEvent} evt mouseup event that triggered the action
  */
  action: function( evt )
  {
    var menu = this.get('menu') ;
    // no menu to toggle... bail...
    if (!menu) return NO ;
    menu.popup(this, this.preferMatrix) ;
    return YES;
  },
  
  /**
    @private

    Holding down the button should display the menu pane.
  */
  mouseDown: function(evt) {
    if (!this.get('isEnabled')) return YES ; // handled event, but do nothing
    this.set('isActive', YES);
    this._isMouseDown = YES;
    this._action() ;
    return YES ;
  }
  
});

/* >>>>>>>>>> BEGIN source/views/progress.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  Displays a progress bar.  You can display both a defined and an 
  indeterminate progressbar.  The progress bar itself is designed to be styled
  using CSS classes with the following structure:
  
  <div class="sc-progress-view"><div class="inner"></div></div>
  
  The outer can form the boundary of the bar while the inner will be adjusted 
  to fit the percentage of the progress.
  
  Creating a ProgressView accepts a number of properties, for example:
  {
    value: 50, 
    minimum: 0, 
    maximum: 100,
    isIndeterminate: NO,
    isEnabled: YES
  }
  
  Default isEnabled value is YES.

  @extends SC.View
  @extends SC.Control
  @since SproutCore 1.0
*/
SC.ProgressView = SC.View.extend(SC.Control, {
  
  // ........................................
  // PROPERTIES
  //

  /**
    Bind this to the current value of the progress bar.  Note that by default 
    an empty value will disable the progress bar and a multiple value will make 
    it indeterminate.
  */
  value: 0.50,
  valueBindingDefault: SC.Binding.single().notEmpty(),
  
  /**
    The minimum value of the progress.
  */ 
  minimum: 0,
  minimumBindingDefault: SC.Binding.single().notEmpty(),

  /**
    Optionally specify the key used to extract the minimum progress value 
    from the content object.  If this is set to null then the minimum value
    will not be derived from the content object.
    
    @property {String}
  */
  contentMinimumKey: null,
  
  /**
    The maximum value of the progress bar.
  */
  maximum: 1.0,
  maximumBindingDefault: SC.Binding.single().notEmpty(),

  /**
    The value of the progress inner offset range. Should be the same as width 
    of image. Default it to 24

    @type Integer
  */
  offsetRange: 24,

  /**
    Optionally specify the key used to extract the maximum progress value 
    from the content object.  If this is set to null then the maximum value
    will not be derived from the content object.
    
    @property {String}
  */
  contentMaximumKey: null,

  /** 
    Set to true if the item in progress is indeterminate.  This may be 
    overridden by the actual value.
    @returns {Boolean} 
  */
  isIndeterminate: NO,
  isIndeterminateBindingDefault: SC.Binding.bool(),

  /**
    Set to YES when the process is currently running.  This will cause the 
    progress bar to animate, especially if it is indeterminate.  
  */
  isRunning: NO,
  isRunningBindingDefault: SC.Binding.bool(),

  /** 
    Set to the matrix used for background image position for animation.
    [1st image y-location, offset, total number of images]
    @property {Array}
  */
  animatedBackgroundMatrix: [],
  
  /**
    Optionally specify the key used to extract the isIndeterminate value 
    from the content object.  If this is set to null then the isIndeterminate 
    value will not be derived from the content object.
    
    @property {String}
  */
  contentIsIndeterminateKey: null,

  // ........................................
  // STRUCTURE
  //

  classNames: 'sc-progress-view',
  
  // ........................................
  // INTERNAL SUPPORT
  //

  _backgroundOffset: 0,
  _currentBackground: 1,
  _nextBackground: 1,
  
  // start animating at the end of the init() method.  note that we call this
  // here because we want this to make sure this function is called anytime 
  // the progress view is instantiated.
  init: function() {
    arguments.callee.base.apply(this,arguments);
    this.animateProgressBar(); // start animating...  
  },
  
  // start/stop running animation based on isRunning state.
  animateProgressBar: function() {
    if (this.get('isRunning') && this.get('isVisibleInWindow')) {
      this._animateProgressBar(500); // wait to start to avoid probs
    }
  }.observes('isRunning', 'isVisibleInWindow'),

  _animateProgressBar: function(delay) {  
    if (delay===0) delay = 1000/30;
    if (this.get('isRunning') && this.get('isVisibleInWindow')) {
      this.displayDidChange();
      this.invokeLater(this._animateProgressBar, delay, 0);
    }
  },
  
  displayProperties: 'value minimum maximum isIndeterminate'.w(),
  
  render: function(context, firstTime) {
    var inner, animatedBackground;
    var isIndeterminate = this.get('isIndeterminate');
    var isRunning = this.get('isRunning');
    var isEnabled = this.get('isEnabled');
    var offsetRange = this.get('offsetRange');
  
    var offset = (isIndeterminate && isRunning) ? (Math.floor(Date.now()/75)%offsetRange-offsetRange) : 0;
  
    // compute value for setting the width of the inner progress
    var value;
    if (!isEnabled) {
      value = "0%" ;
    } else if (isIndeterminate) {
      value = "120%";
    } else {
      var minimum = this.get('minimum') || 0.0;
      var maximum = this.get('maximum') || 1.0;
      value = this.get('value') || 0.0;
      value = (value - minimum) / (maximum - minimum);
      if (value > 1.0) value = 1.0;

      if(isNaN(value)) value = 0.0;
      // cannot be smaller then minimum
      if(value<minimum) value = 0.0;
      // cannot be larger then maximum
      if(value>maximum) value = 1.0;
      value = (value * 100) + "%";
    }

    var classNames = {
      'sc-indeterminate': isIndeterminate,
      'sc-empty': (value <= 0),
      'sc-complete': (value >= 100)
    };
    
    if(firstTime) {
      var classString = this._createClassNameString(classNames);
      context.push('<div class="sc-inner ', classString, '" style="width: ', value, ';left: ', offset, '">');
      context.push('<div class="sc-inner-head"></div><div class="sc-inner-tail"></div></div><div class="sc-outer-head"></div><div class="sc-outer-tail"></div>');
    }
    else {
      context.setClass(classNames);
      inner = this.$('.sc-inner');
      animatedBackground = this.get('animatedBackgroundMatrix');
      inner.css('width', value).css('left',offset);
      if (animatedBackground.length === 3 ) {
        inner.css('backgroundPosition', '0px -'+ 
        (animatedBackground[0] + 
        animatedBackground[1]*this._currentBackground)+'px');
        if(this._currentBackground===animatedBackground[2]-1
           || this._currentBackground===0){
          this._nextBackground *= -1;
        }
        this._currentBackground += this._nextBackground;
      }
    }
    
  },
  
  contentPropertyDidChange: function(target, key) {
    var content = this.get('content');
    this.beginPropertyChanges()
      .updatePropertyFromContent('value', key, 'contentValueKey', content)
      .updatePropertyFromContent('minimum', key, 'contentMinimumKey', content)
      .updatePropertyFromContent('maximum', key, 'contentMaximumKey', content)
      .updatePropertyFromContent('isIndeterminate', key, 'contentIsIndeterminateKey', content)
    .endPropertyChanges();
  },
  
  _createClassNameString: function(classNames) {
    var classNameArray = [], key;
    for(key in classNames) {
      if(!classNames.hasOwnProperty(key)) continue;
      if(classNames[key]) classNameArray.push(key);
    }
    return classNameArray.join(" ");
  }
  
}) ;
/* >>>>>>>>>> BEGIN source/views/radio.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @class

  A RadioView is used to create a group of radio buttons.  The user can use
  these buttons to pick from a choice of options.
  
  This view renders simulated radio buttons that can display a mixed state and 
  has other features not found in platform-native controls.
  
  The radio buttons themselves are designed to be styled using CSS classes with
  the following structure:
  
  <label class="sc-radio-button">
  <img class="button" src="some_image.gif"/>
  <input type="radio" name="<sc-guid>" value=""/>
  <span class="sc-button-label">Label for button1</span>
  </label>
  
  Setting up a RadioView accepts a number of properties, for example:
  {
    items: [{ title: "Red", 
              value: "red", 
              enabled: YES, 
              icon: "button_red" },
            { title: "Green", 
              value: "green", 
              enabled: YES, 
              icon: 'button_green' }],
    value: 'red',
    itemTitleKey: 'title',
    itemValueKey: 'value',
    itemIconKey: 'icon',
    itemIsEnabledKey: 'enabled',
    isEnabled: YES,
    layoutDirection: SC.LAYOUT_HORIZONTAL
  }
  
  Default layoutDirection is vertical. 
  Default isEnabled is YES.
  
  The value property can be either a string, as above, or an array of strings
  for pre-checking multiple values.
  
  The items array can contain either strings, or as in the example above a 
  hash. When using a hash, make sure to also specify the itemTitleKey
  and itemValueKey you are using. Similarly, you will have to provide 
  itemIconKey if you are using icons radio buttons. The individual items 
  enabled property is YES by default, and the icon is optional.
  
  @extends SC.FieldView
  @since SproutCore 1.0
*/
SC.RadioView = SC.FieldView.extend(
  /** @scope SC.RadioView.prototype */ {

  // HTML design options
  classNames: ['sc-radio-view'],

  /**
    The value of the currently selected item, and which will be checked in the 
    UI. This can be either a string or an array with strings for checking 
    multiple values.
  */
  value: null,
  
  layoutDirection: SC.LAYOUT_VERTICAL,
  
  // escape the HTML in label text
  escapeHTML: YES,
  
  /** 
    The items property can be either an array with strings, or a
    hash. When using a hash, make sure to also specify the appropriate
    itemTitleKey, itemValueKey, itemIsEnabledKey and itemIconKey.
  */
  items: [],

  /** 
    If items property is a hash, specify which property will function as
    the title with this itemTitleKey property.
  */
  itemTitleKey: null,
  
  /** 
    If items property is a hash, specify which property will function as
    the value with this itemValueKey property.
  */
  itemValueKey: null,
  
  /** 
    If items property is a hash, specify which property will function as
    the value with this itemIsEnabledKey property.
  */
  itemIsEnabledKey: null,
  
  /** 
    If items property is a hash, specify which property will function as
    the value with this itemIconKey property.
  */
  itemIconKey: null,
  
  /** @private - 
    Will iterate the items property to return an array with items that is 
    indexed in the following structure:
      [0] => Title (or label)
      [1] => Value
      [2] => Enabled (YES default)
      [3] => Icon (image URL)
  */
  displayItems: function() {
    var items = this.get('items'), loc = this.get('localize'),
      titleKey = this.get('itemTitleKey'), valueKey = this.get('itemValueKey'),
      isEnabledKey = this.get('itemIsEnabledKey'), 
      iconKey = this.get('itemIconKey');
    var ret = [], max = (items)? items.get('length') : 0 ;
    var item, title, value, idx, isArray, isEnabled, icon;
    
    for(idx=0;idx<max;idx++) {
      item = items.objectAt(idx); 
      
      // if item is an array, just use the items...
      if (SC.typeOf(item) === SC.T_ARRAY) {
        title = item[0];  value = item[1] ;
        
      // otherwise, possibly use titleKey,etc.
      } else if (item) {
        // get title.  either use titleKey or try to convert the value to a 
        // string.
        if (titleKey) {
          title = item.get ? item.get(titleKey) : item[titleKey] ;
        } else title = (item.toString) ? item.toString() : null;
        
        if (valueKey) {
          value = item.get ? item.get(valueKey) : item[valueKey] ;
        } else value = item ;
        
        if (isEnabledKey) {
          isEnabled = item.get ? item.get(isEnabledKey) : item[isEnabledKey];
        } else isEnabled = YES ;
        
        if (iconKey) {
          icon = item.get ? item.get(iconKey) : item[iconKey] ;
        } else icon = null ;
        
      // if item is nil, use somedefaults...
      } else { title = value = icon = null; isEnabled = NO; }

      // localize title if needed
      if (loc) title = title.loc();
      ret.push([title, value, isEnabled, icon]) ;
    }
    
    return ret; // done!
  }.property('items', 'itemTitleKey', 'itemValueKey', 'itemIsEnabledKey', 'localize', 'itemIconKey').cacheable(),
  
  /** If the items array itself changes, add/remove observer on item... */
  itemsDidChange: function() {
    if (this._items) {
      this._items.removeObserver('[]',this,this.itemContentDidChange) ;
    } 
    this._items = this.get('items');
    if (this._items) {
      this._items.addObserver('[]', this, this.itemContentDidChange) ;
    }
    this.itemContentDidChange();
  }.observes('items'),
  
  /** 
    Invoked whenever the item array or an item in the array is changed.
    This method will regenerate the list of items.
  */
  itemContentDidChange: function() {
    this.notifyPropertyChange('displayItems');
  },

  // ..........................................................
  // PRIVATE SUPPORT
  // 
  
  $input: function() { return this.$('input'); },
  
  displayProperties: ['value', 'displayItems'],
  
  render: function(context, firstTime) {
    // if necessary, regenerate the radio buttons
    var item, idx, icon, name, itemsLength, url, className, disabled, 
      labelText, selectionState, selectionStateClassNames, 
      items = this.get('displayItems'), 
      value = this.get('value'), isArray = SC.isArray(value);
    
    context.addClass(this.get('layoutDirection'));
    
    // isArray is set only when there are two active checkboxes 
    // which can only happen with mixed state
    if (isArray && value.length<=0) {
      value = value[0]; isArray = NO;
    }
    
    if (firstTime) {
      // generate tags from this.
      name = SC.guidFor(this); // name for this group
      itemsLength = items.length;
      for(idx=0;idx<itemsLength;idx++) {
        item = items[idx];
        
        // get the icon from the item, if one exists...
        icon = item[3];
        if (icon) {
          url = (icon.indexOf('/')>=0) ? icon : SC.BLANK_IMAGE_URL;
          className = (url === icon) ? '' : icon ;
          icon = '<img src="%@" class="icon %@" alt="" />'.fmt(url, className);
        } else icon = '';
        
        selectionStateClassNames = this._getSelectionState(item, value, isArray, false);
        disabled = (!item[2]) || (!this.get('isEnabled')) ? 'disabled="disabled" ' : '';
        
        labelText = this.escapeHTML ? SC.RenderContext.escapeHTML(item[0]) : item[0];
        
        context.push('<label class="sc-radio-button ', selectionStateClassNames, '">');
        context.push('<input type="radio" value="', idx, '" name="', name, '" ', disabled, '/>');
        context.push('<span class="button"></span>');
        context.push('<span class="sc-button-label">', icon, labelText, '</span></label>');
      }
      
      // first remove listener on existing radio buttons
      this._field_setFieldValue(this.get('value'));
    }
    else {
      // update the selection state on all of the DOM elements.  The options are
      // sel or mixed.  These are used to display the proper setting...
      this.$input().forEach(function(input) {
        
        input = this.$(input);
        idx = parseInt(input.val(),0);
        item = (idx>=0) ? items[idx] : null;

        input.attr('disabled', (!item[2]) ? 'disabled' : null);
        selectionState = this._getSelectionState(item, value, isArray, true);

        // set class of label
        input.parent().setClass(selectionState);
        
        // avoid memory leaks
        input =  idx = selectionState = null;
      }, this);
    
    }
    
  },
  
  /** @private - 
    Will figure out what class names to assign each radio button.
    This method can be invoked either as part of render() either when:
    1. firstTime is set and we need to assign the class names as a string
    2. we already have the DOM rendered but we just need to update class names
       assigned to the the input field parent
  */
  _getSelectionState: function(item, value, isArray, shouldReturnObject) {
      var sel, classNames, key;
      
      // determine if the current item is selected
      if (item) {
        sel = (isArray) ? (value.indexOf(item[1])>=0) : (value===item[1]);
      } else {
        sel = NO;
      }
      
      // now set class names
      classNames = {
        sel: (sel && !isArray), mixed: (sel && isArray), disabled: (!item[2]) 
      };
      
      if(shouldReturnObject) {
        return classNames;
      } else {
        // convert object values to string
        var classNameArray = [];
        for(key in classNames) {
          if(!classNames.hasOwnProperty(key)) continue;
          if(classNames[key]) classNameArray.push(key);
        }
        return classNameArray.join(" ");
      }
      
  },

  getFieldValue: function() {
    var val = this.$input().filter(function() { return this.checked; }).val();
    var items = this.get('displayItems') ;
    val = items[parseInt(val,0)];
    
    // if no items are selected there is a saved mixed value, return that...
    return val ? val[1] : this._mixedValue;
  },
  
  setFieldValue: function(v) {
    // if setting a mixed value, actually clear everything and save mixed
    // value
    if (SC.isArray(v)) {
      if (v.get('length')>1) {
        this._mixedValue = v;
        v = undefined ;
      } else v = v.objectAt(0);
    }

    // v now contains one item only.  find the index in the display items
    // array matching that value.
    var items, idx;
    if (v === undefined) {
      idx = -1;
    } else {
      items = this.get('displayItems');
      idx = items.indexOf(items.find(function(x) { return x[1] === v; }));
    }
    
    // now loop through input elements. set their checked value accordingly
    this.$input().forEach(function(input) {
      input = SC.$(input);
      input.attr('checked', parseInt(input.val(),0) === idx);
      input = null;
    });
    
    return this;
  },
  

  
  didCreateLayer: function() {
     this.setFieldValue(this.get('fieldValue'));
     var inputElems=this.$input();
     for( var i=0, inputLen = inputElems.length; i<inputLen; i++){
       SC.Event.add(inputElems[i], 'click', this, this._field_fieldValueDidChange) ;
     }
   },

  willDestroyLayer: function() {
       var inputElems=this.$input();
        for( var i=0, inputLen = inputElems.length; i<inputLen; i++){
            SC.Event.remove(this.$input()[i], 'click', this, this._field_fieldValueDidChange); 
        }
   
  },
  
  mouseDown: function(evt) {  
    this.set('isActive', YES);
    this._field_isMouseDown = YES;
    return YES;
  }

});
/* >>>>>>>>>> BEGIN source/views/scene.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @class

  Displays several views as scenes that can slide on and off the screen.  The
  scene view is a nice way to provide a simple effect of moving from a 
  higher level screen to a more detailed level screen.  You will be able to
  optionally choose the kind of animation used to transition the two scenes 
  as well if supported on the web browser.
  
  h1. Using The View
  
  To setup the scene view, you should define the 'scenes' property with an 
  array of scene names.  These will be the properties on the scene view that
  you can shift in an out of view as needed.  You can edit the scenes property
  at any time.  It will only be used when you start to transition from one
  scene to another.
  
  Next you should set your nowShowing property to the name of the scene you 
  would like to display.  This will cause the view to transition scenes if it
  is visible on screen.  Otherwise, it will simply make the new scene view 
  the current content view and that's it.

  @extends SC.View
  @since SproutCore 1.0
*/
SC.SceneView = SC.ContainerView.extend(
  /** @scope SC.SceneView.prototype */ {

  /**
    Array of scene names.  Scenes will slide on and off screen in the order
    that you specifiy them here.  That is, if you shift from a scene at index
    2 to a scene at index 1, the scenes will animation backwards.  If you
    shift to a scene at index 3, the scenes will animate forwards.
    
    The default scenes defined are 'master' and 'detail'.  You can replace or 
    augment this array as you like.
    
    @property {Array}
  */
  scenes: ['master', 'detail'],

  /**
    The currently showing scene.  Changing this property will cause the 
    scene view to transition to the new scene.  If you set this property to 
    null, an empty string, or a non-existant scene, then the scene will appear
    empty.
  */
  nowShowing: null,
  
  /**
    Speed of transition.  Should be expressed in msec.
  */
  transitionDuration: 200,
  
  _state: 'NO_VIEW', // no view

  /** @private
  
    Whenever called to change the content, save the nowShowing state and 
    then animate in by adjusting the layout.
    
  */
  replaceContent: function(content) {
    if (content && this._state===this.READY) this.animateScene(content);
    else this.replaceScene(content);
    return this ;
  },

  /** @private
  
    Invoked whenever we just need to swap the scenes without playing an
    animation.
  */
  replaceScene: function(newContent) {
    var oldContent = this._targetView,
        layout     = this.STANDARD_LAYOUT,
        scenes     = this.get('scenes'),
        idx        = scenes ? scenes.indexOf(this.get('nowShowing')) : -1;

    // cleanup animation here too..
    this._targetView = newContent ;
    this._targetIndex  = idx;
    
    if (this._timer) this._timer.invalidate();
    this._leftView = this._rightView = this._start = this._end = null;
    this._timer = null;
    
    
    this.removeAllChildren();

    if (oldContent) oldContent.set('layout', layout);
    if (newContent) newContent.set('layout', layout);
    
    if (newContent) this.appendChild(newContent);
    this._state = newContent ? this.READY : this.NO_VIEW ;
  },

  /** @private
  
    Invoked whenever we need to animate in the new scene.
  */
  animateScene: function(newContent) {
    var oldContent = this._targetView,
        outIdx     = this._targetIndex,
        scenes     = this.get('scenes'),
        inIdx      = scenes ? scenes.indexOf(this.get('nowShowing')) : -1,
        layout;

    if (outIdx<0 || inIdx<0 || outIdx===inIdx) {
      return this.replaceScene(newContent);
    }

    this._targetView = newContent ;
    this._targetIndex = inIdx; 
    
    // save some info needed for animation
    if (inIdx > outIdx) {
      this._leftView  = oldContent;
      this._rightView = newContent;
      this._target    = -1;
    } else {
      this._leftView  = newContent ;
      this._rightView = oldContent ;
      this._target    = 1 ;
    }

    // setup views
    this.removeAllChildren();

    if (oldContent) this.appendChild(oldContent)
    if (newContent) this.appendChild(newContent);

    // setup other general state
    this._start   = Date.now();
    this._end     = this._start + this.get('transitionDuration');
    this._state   = this.ANIMATING;
    this.tick();
  },

  /** @private - called while the animation runs.  Compute the new layout for
    the left and right views based on the portion completed.  When we finish
    call replaceScene().
  */
  tick: function() {  
    this._timer = null ; // clear out
    
    var now    = Date.now(),
        pct    = (now-this._start)/(this._end-this._start),
        target = this._target,
        left   = this._leftView,
        right  = this._rightView,
        layout, adjust;
        
    if (pct<0) pct = 0;
    
    // if we're done or the view is no longer visible, just replace the 
    // scene.
    if (!this.get('isVisibleInWindow') || (pct>=1)) {
      return this.replaceScene(this._targetView);
    }

    // ok, now let's compute the new layouts for the two views and set them
    layout = SC.clone(this.get('frame'));
    adjust = Math.floor(layout.width * pct);
    
    // set the layout for the views, depending on the direction
    if (target>0) {
      layout.left = 0-(layout.width-adjust);
      left.set('layout', layout);

      layout = SC.clone(layout);
      layout.left = adjust ;
      right.set('layout', layout);
      
    } else {
      layout.left = 0-adjust ;
      left.set('layout', layout);
      
      layout = SC.clone(layout);
      layout.left = layout.width-adjust;
      right.set('layout', layout);
    }

    this._timer = this.invokeLater(this.tick, 20);
    return this;
  },
  

  // states for view animation
  NO_VIEW: 'NO_VIEW',
  ANIMATING: 'ANIMATING',
  READY: 'READY',

  /** @private - standard layout assigned to views at rest */
  STANDARD_LAYOUT: { top: 0, left: 0, bottom: 0, right: 0 }
  
  
});

/* >>>>>>>>>> BEGIN source/views/segmented.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  SegmentedView is a special type of button that can display multiple
  segments.  Each segment has a value assigned to it.  When the user clicks
  on the segment, the value of that segment will become the new value of 
  the control.
  
  You can also optionally configure a target/action that will fire whenever
  the user clicks on an item.  This will give your code an opportunity to take
  some action depending on the new value.  (of course, you can always bind to
  the value as well, which is generally the preferred approach.)
  
  h1. Defining Your Segments
  
  You define your segments by providing a items array, much like you provide
  to a RadioView.  Your items array can be as simple as an array of strings 
  or as complex as full model objects.  Based on how you configure your
  itemKey properties, the segmented view will read the properties it needs 
  from the array and construct the button.
  
  You can define the following properties on objects you pass in:
  
  | *itemTitleKey* | the title of the button |
  | *itemValueKey* | the value of the button |
  | *itemWidthKey* | the preferred width. if omitted, it autodetects |
  | *itemIconKey*  | an icon |
  | *itemActionKey* | an optional action to fire when pressed |
  | *itemTargetKey* | an optional target for the action |

  @extends SC.View
  @since SproutCore 1.0
*/
SC.SegmentedView = SC.View.extend(SC.Control,
/** @scope SC.SegmentedView.prototype */ {
  
  classNames: ['sc-segmented-view'],
  
  /**
    The value of the segmented view.
    
    The SegmentedView's value will always be the value of the currently
    selected button.  Setting this value will change the selected button. 
    If you set this value to something that has no matching button, then
    no buttons will be selected.
    
    @field {Object}
  */
  value: null,

  /**
    Set to YES to enabled the segmented view, NO to disabled it.
  */
  isEnabled: YES, 

  /**
    If YES, clicking a selected button again will deselect it, setting the
    segmented views value to null.  Defaults to NO.
  */
  allowsEmptySelection: NO,  
  
  /**
    If YES, then clicking on a tab will not deselect the other segments, it
    will simply add or remove it from the selection.
  */
  allowsMultipleSelection: NO,

  /**
    If YES, titles will be localized before display.
  */
  localize: YES,
  
  /**
    Change the layout direction to make this a vertical set of tabs instead
    of horizontal ones.
  */
  layoutDirection: SC.LAYOUT_HORIZONTAL,
  
  // ..........................................................
  // SEGMENT DEFINITION
  //
   
  /**
    The array of items to display.  This can be a simple array of strings,
    objects or hashes.  If you pass objects or hashes, you must also set the
    various itemKey properties to tell the SegmentedView how to extract the
    information it needs.
    
    @property {Array}
  */
  items: [],

  /** 
    The key that contains the title for each item.
    
    @property {String}
  */
  itemTitleKey: null,
  
  /** 
    The key that contains the value for each item.
    
    @property {String}
  */
  itemValueKey: null,
  
  /** 
    A key that determines if this item in particular is enabled.  Note if the
    control in general is not enabled, no items will be enabled, even if the
    item's enabled property returns YES.
    
    @property {String}
  */
  itemIsEnabledKey: null,

  /** 
    The key that contains the icon for each item.  If omitted, no icons will
    be displayed.
    
    @property {String}
  */
  itemIconKey: null,

  /** 
    The key that contains the desired width for each item.  If omitted, the
    width will autosize.
  
    @property {String}
  */
  itemWidthKey: null,
  
  /** 
    The key that contains the action for this item.  If defined, then 
    selecting this item will fire the action in addition to changing the 
    value.  See also itemTargetKey.
    
    @property {String}
  */
  itemActionKey: null,

  /** 
    The key that contains the target for this item.  If this and itemActionKey
    are defined, then this will be the target of the action fired. 
    
    @property {String}
  */
  itemTargetKey: null,

  /** 
    The key that contains the key equivalent for each item.  If defined then
    pressing that key equivalent will be like selecting the tab.  Also, 
    pressing the Alt or Option key for 3 seconds will display the key 
    equivalent in the tab.
  */
  itemKeyEquivalentKey: null,

  /**
    The array of itemKeys that will be searched to build the displayItems
    array.  This is used internally by the class.  You will not generally
    need to access or edit this array.
    
    @property {Array}
  */
  itemKeys: 'itemTitleKey itemValueKey itemIsEnabledKey itemIconKey itemWidthKey itemToolTipKey'.w(),
  
  /**
    This computed property is generated from the items array based on the 
    itemKey properties that you set.  The return value is an array of arrays
    that contain private information used by the SegmentedView to render. 
    
    You will not generally need to access or edit this property.
    
    @property {Array}
  */
  displayItems: function() {
    var items = this.get('items'), loc = this.get('localize') ;
    var keys=null, itemType, cur ;
    var ret = [], max = items.get('length'), idx, item ;
    var fetchKeys = SC._segmented_fetchKeys;
    var fetchItem = SC._segmented_fetchItem;
    
    // loop through items and collect data
    for(idx=0;idx<max;idx++) {
      item = items.objectAt(idx) ;
      if (SC.none(item)) continue; //skip is null or undefined
      
      // if the item is a string, build the array using defaults...
      itemType = SC.typeOf(item);
      if (itemType === SC.T_STRING) {
        cur = [item.humanize().titleize(), item, YES, null, null,  null, idx] ;
        
      // if the item is not an array, try to use the itemKeys.
      } else if (itemType !== SC.T_ARRAY) {
        
        // get the itemKeys the first time
        if (keys===null) {
          keys = this.itemKeys.map(fetchKeys,this);
        }
        
        // now loop through the keys and try to get the values on the item
        cur = keys.map(fetchItem, item);
        cur[cur.length] = idx; // save current index
        
        // special case 1...if title key is null, try to make into string
        if (!keys[0] && item.toString) cur[0] = item.toString();
        
        // special case 2...if value key is null, use item itself
        if (!keys[1]) cur[1] = item;
        
        // special case 3...if isEnabled is null, default to yes.
        if (!keys[2]) cur[2] = YES ; 
      }
      
      // finally, be sure to loc the title if needed
      if (loc && cur[0]) cur[0] = cur[0].loc();

      // finally, be sure to loc the toolTip if needed
      if (loc && cur[5] && SC.typeOf(cur[5]) === SC.T_STRING) cur[5] = cur[5].loc();
      
      // add to return array
      ret[ret.length] = cur;
    }
    
    // all done, return!
    return ret ;
  }.property('items', 'itemTitleKey', 'itemValueKey', 'itemIsEnabledKey', 'localize', 'itemIconKey', 'itemWidthKey', 'itemToolTipKey').cacheable(),
  
  /** If the items array itself changes, add/remove observer on item... */
  itemsDidChange: function() { 
    if (this._items) {
      this._items.removeObserver('[]',this,this.itemContentDidChange) ;
    } 
    this._items = this.get('items') ;
    if (this._items) {
      this._items.addObserver('[]', this, this.itemContentDidChange) ;
    }
    this.itemContentDidChange();
  }.observes('items'),
  
  /** 
    Invoked whenever the item array or an item in the array is changed.  This method will reginerate the list of items.
  */
  itemContentDidChange: function() {
    this.notifyPropertyChange('displayItems');
  },
  
  init: function() {
    arguments.callee.base.apply(this,arguments);
    this.itemsDidChange() ;
  },

  
  // ..........................................................
  // RENDERING/DISPLAY SUPPORT
  // 
  
  displayProperties: ['displayItems', 'value', 'activeIndex'],
  
  
  render: function(context, firstTime) { 
    
    // collect some data 
    var items = this.get('displayItems');
    
    // regenerate the buttons only if the new display items differs from the
    // last cached version of it needsFirstDisplay is YES.
    var last = this._seg_displayItems;
    if (firstTime || (items !== last)) {
      this._seg_displayItems = items; // save for future
      this.renderDisplayItems(context, items) ;
      context.addStyle('text-align', 'center');
    }else{
    // update selection and active state
      var activeIndex = this.get('activeIndex');
      var value = this.get('value');
      var isArray = SC.isArray(value);
      if (isArray && value.get('length')===1) {
        value = value.objectAt(0); isArray = NO ;
      }
      var names = {}; // reuse
    
      var loc = items.length, cq = this.$('a.sc-segment'), item;
      while(--loc>=0) {
        item = items[loc];
        names.sel = isArray ? (value.indexOf(item[1])>=0) : (item[1]===value);
        names.active = (activeIndex === loc);
        SC.$(cq.get(loc)).setClass(names);
      }
      names = items = value = items = null; // cleanup
    }
  },
  
  /**
    Actually generates the segment HTML for the display items.  This method 
    is called the first time a view is constructed and any time the display
    items change thereafter.  This will construct the HTML but will not set
    any "transient" states such as the global isEnabled property or selection.
  */
  renderDisplayItems: function(context, items) {
    var value       = this.get('value'),
        isArray     = SC.isArray(value),
        activeIndex = this.get('activeIndex'),
        len         = items.length,
        title, icon, url, className, ic, item, toolTip, width, i;

    for(i=0; i< len; i++){
      ic = context.begin('a').attr('href', 'javascript:;');
      item=items[i];
      title = item[0]; 
      icon = item[3];
      toolTip = item[5];
      ic.addStyle('display', 'inline-block');
      ic.addClass('sc-segment');
      if(!item[2]){
        ic.addClass('disabled');
      }
      if(i===0){
        ic.addClass('sc-first-segment');
      }
      if(i===(len-1)){
        ic.addClass('sc-last-segment');
      }
      if(i!==0 && i!==(len-1)){
        ic.addClass('sc-middle-segment');
      }      
      if( isArray ? (value.indexOf(item[1])>=0) : (item[1]===value)){
        ic.addClass('sel');
      }
      if(activeIndex === i) {
        ic.addClass('active') ;
      }
      if(item[4]){
        width=item[4];
        ic.addStyle('width', width+'px');
      }

      if(toolTip) {
        ic.attr('title', toolTip) ;
      }

      if (icon) {
        url = (icon.indexOf('/')>=0) ? icon : SC.BLANK_IMAGE_URL;
        className = (url === icon) ? '' : icon ;
        icon = '<img src="'+url+'" alt="" class="icon '+className+'" />';
      } else {
        icon = '';
      }
      ic.push('<span class="sc-button-inner"><label class="sc-button-label">');
      ic.push(icon+title);
      ic.push('</label></span>');
      ic.end();
    }   
  },  
  
  // ..........................................................
  // EVENT HANDLING
  // 
  
  /** 
    Determines the index into the displayItems array where the passed mouse
    event occurred.
  */
  displayItemIndexForEvent: function(evt) {
    var elem = SC.$(evt.target) ;
    if (!elem || elem===document) return -1; // nothing found

    // start at the target event and go upwards until we reach either the 
    // root responder or find an anchor.sc-segment.
    var root = this.$(), match = null ;
    while(!match && (elem.length>0) && (elem.get(0)!==root.get(0))) {
      if (elem.hasClass('sc-segment') && elem.attr('tagName')==='A') {
        match = elem;
      } else elem = elem.parent();
    }
    
    elem = root = null;
    
    // if a match was found, return the index of the match in subtags
    return (match) ? this.$('a.sc-segment').index(match) : -1;
  },
  
  keyDown: function(evt) {
    // handle tab key
    var i, item, items, len, value, isArray;
    if (evt.which === 9) {
      var view = evt.shiftKey ? this.get('previousValidKeyView') : this.get('nextValidKeyView');
      view.becomeFirstResponder();
      return YES ; // handled
    }    
    if (!this.get('allowsMultipleSelection') && !this.get('allowsEmptySelection')){
      items = this.get('displayItems');
      len = items.length;
      value = this.get('value');
      isArray = SC.isArray(value);
      if (evt.which === 39 || evt.which === 40) {  
        for(i=0; i< len-1; i++){
          item=items[i];
          if( isArray ? (value.indexOf(item[1])>=0) : (item[1]===value)){
            this.triggerItemAtIndex(i+1);
          }
        }
        return YES ; // handled
      }
      else if (evt.which === 37 || evt.which === 38) {
        for(i=1; i< len; i++){
          item=items[i];
          if( isArray ? (value.indexOf(item[1])>=0) : (item[1]===value)){
            this.triggerItemAtIndex(i-1);
          }
        }
        return YES ; // handled
      }
    }
    return YES; 
  },
  
  mouseDown: function(evt) {
    if (!this.get('isEnabled')) return YES; // nothing to do
    var idx = this.displayItemIndexForEvent(evt);
    
    // if mouse was pressed on a button, then start detecting pressed events
    if (idx>=0) {
      this._isMouseDown = YES ;
      this.set('activeIndex', idx);
    }
    
    return YES ;
  },
  
  mouseUp: function(evt) {
    var idx = this.displayItemIndexForEvent(evt);
    
    // if mouse was pressed on a button then detect where we where when we
    // release and use that one.
    if (this._isMouseDown && (idx>=0)) this.triggerItemAtIndex(idx);
    
    // cleanup
    this._isMouseDown = NO ;
    this.set('activeIndex', -1);
    return YES ;
  },
  
  mouseMoved: function(evt) {
    var idx = this.displayItemIndexForEvent(evt);
    if (this._isMouseDown) this.set('activeIndex', idx);
    return YES;
  },
  
  mouseOver: function(evt) {
    // if mouse was pressed down initially, start detection again
    var idx = this.displayItemIndexForEvent(evt);
    if (this._isMouseDown) this.set('activeIndex', idx);
    return YES;
  },
  
  mouseOut: function(evt) {
    // if mouse was down, hide active index
    if (this._isMouseDown) this.set('activeIndex', -1);
    return YES ;
  },
  
  /** 
    Simulates the user clicking on the segment at the specified index. This
    will update the value if possible and fire the action.
  */
  triggerItemAtIndex: function(idx) {
    var items = this.get('displayItems'),
        item  = items.objectAt(idx),
        sel, value, val, empty, mult;
        
    if (!item[2]) return this; // nothing to do!

    empty = this.get('allowsEmptySelection');
    mult = this.get('allowsMultipleSelection');
    
    
    // get new value... bail if not enabled. Also save original for later.
    sel = item[1];
    value = val = this.get('value') ;
    if (!SC.isArray(value)) value = [value]; // force to array
    
    // if we do not allow multiple selection, either replace the current
    // selection or deselect it
    if (!mult) {
      // if we allow empty selection and the current value is the same as
      // the selected value, then deselect it.
      if (empty && (value.get('length')===1) && (value.objectAt(0)===sel)){
        value = [];
      
      // otherwise, simply replace the value.
      } else value = [sel] ;
      
    // if we do allow multiple selection, then add or remove item to the
    // array.
    } else {
      if (value.indexOf(sel) >= 0) {
        if (value.get('length')>1 || (value.objectAt(0)!==sel) || empty) {
          value = value.without(sel);
        }
      } else value = value.concat([sel]) ;
    }
    
    // normalize back to non-array form
    switch(value.get('length')) {
      case 0:
        value = null;
        break;
      case 1:
        value = value.objectAt(0);
        break;
      default:
        break;
    }
    
    // also, trigger target if needed.
    var actionKey = this.get('itemActionKey');
    var targetKey = this.get('itemTargetKey');
    var action, target = null;
    var resp = this.getPath('pane.rootResponder');

    if (actionKey && (item = this.get('items').objectAt(item[6]))) {
      // get the source item from the item array.  use the index stored...
      action = item.get ? item.get(actionKey) : item[actionKey];
      if (targetKey) {
        target = item.get ? item.get(targetKey) : item[targetKey];
      }
      if (resp) resp.sendAction(action, target, this, this.get('pane'));
    }

    // Only set value if there is no action and a value is defined.
    if(!action && val !== undefined) {
      this.set('value', value);
    }
    
    // if an action/target is defined on self use that also
    action =this.get('action');
    if (action && resp) {
      resp.sendAction(action, this.get('target'), this, this.get('pane'));
    }
  },
  
  /** tied to the isEnabled state */
   acceptsFirstResponder: function() {
     return this.get('isEnabled');
   }.property('isEnabled'),

   willBecomeKeyResponderFrom: function(keyView) {
     // focus the text field.
     if (!this._isFocused) {
       this._isFocused = YES ;
       this.becomeFirstResponder();
       if (this.get('isVisibleInWindow')) {
         this.$()[0].focus();
       }
     }
   },
   
   willLoseKeyResponderTo: function(responder) {
     if (this._isFocused) this._isFocused = NO ;
   }
    
}) ;

// Helpers defined here to avoid creating lots of closures...
SC._segmented_fetchKeys = function(k) { return this.get(k); };
SC._segmented_fetchItem = function(k) { 
  if (!k) return null;
  return this.get ? this.get(k) : this[k]; 
};





/* >>>>>>>>>> BEGIN source/views/select_field.js */
// ========================================================================
// SproutCore -- JavaScript Application Framework
// Copyright ©2006-2008, Sprout Systems, Inc. and contributors.
// Portions copyright ©2008 Apple Inc.  All rights reserved.
// ========================================================================

/**
  @class

  SelectFieldView displays browser-native popup menu.  To use this view,
  you should either bake into the HTML the preset list of options, or 
  you can set the -objects property to an array of items to show.  The
  value is current value of the select.
  
  @extends SC.FieldView
  @author Charles Jolley
  @author Mike Ball
  @since SproutCore 1.0
*/
SC.SelectFieldView = SC.FieldView.extend(
/** @scope SC.SelectFieldView.prototype */ {

  tagName: 'select',
  classNames: ['sc-select-field-view'],
 
  /**
    An array of items that will form the menu you want to show.
  */ 
  objects: [],
  
  /**
    Binding default for an array of objects
  */ 
  objectsBindingDefault: SC.Binding.multiple(),

  /**
    If you set this to a non-null value, then the name shown for each 
    menu item will be pulled from the object using the named property.
    if this is null, the collection objects themselves will be used.
  */
  nameKey: null,

  /**
   If you set this to a non-null value, then the value of this key will
   be used to sort the objects.  If this is not set, then nameKey will
   be used.
  */ 
  sortKey: null,

  /**
     Set this to a non-null value to use a key from the passed set of objects
     as the value for the options popup.  If you don't set this, then the
     objects themselves will be used as the value.
  */ 
  valueKey: null,

  /**
    set this to non-null to place an empty option at the top of the menu.   
  */
  emptyName: null,

  /**
    if true, the empty name will be localized.
  */
  localize: false,
  
  /**
    if true, it means that the nameKey, valueKey or objects changed
  */
  cpDidChange: YES,
  
  /**
    if true, it means that no sorting will occur, objects will appear 
    in the same order as in the array
  */
  disableSort: NO,
  
  
  
  /**
    override this to change the enabled/disabled state of menu items as they
    are built.  Return false if you want the menu item to be disabled.
    
    @param itemValue the value for the item to validate
    @param itemName the name of the menu item to validate
    @returns YES if the item should be enabled, NO otherwise
  */  
  validateMenuItem: function(itemValue, itemName) {
    return true ;
  },

  /**
    override this method to implement your own sorting of the menu. By
    default, menu items are sorted using the value shown or the sortKey
    
    @param objects the unsorted array of objects to display.
    @returns sorted array of objects
  */
  sortObjects: function(objects) {
    if(!this.get('disableSort')){
      var nameKey = this.get('sortKey') || this.get('nameKey') ;
      objects = objects.sort(function(a,b) {
        if (nameKey) {
          a = a.get ? a.get(nameKey) : a[nameKey] ;
          b = b.get ? b.get(nameKey) : b[nameKey] ;
        }
        return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
      }) ;
    }
    return objects ;
  },

  render: function(context, firstTime) {
    if (this.get('cpDidChange')) {
      this.set('cpDidChange', NO);
      // get list of objects.
      var nameKey = this.get('nameKey') ;
      var valueKey = this.get('valueKey') ;
      var objects = this.get('objects') ;
      var fieldValue = this.get('value') ;
      var el, selectElement;
    
      // get the localization flag.
      var shouldLocalize = this.get('localize'); 
   
      // convert fieldValue to guid, if it is an object.
      if (!valueKey && fieldValue) fieldValue = SC.guidFor(fieldValue) ;
      if ((fieldValue === null) || (fieldValue === '')) fieldValue = '***' ;
    
      if (objects) {
        objects = this.sortObjects(objects) ; // sort'em.
        // var html = [] ;       
        if(!firstTime){
          selectElement=this.$input()[0];
          selectElement.innerHTML='';
        } 
      
        var emptyName = this.get('emptyName') ;
        if (emptyName) {
          if (shouldLocalize) emptyName = emptyName.loc() ;
          if(firstTime){
            context.push('<option value="***">%@</option>'.fmt(emptyName)) ;
            context.push('<option disabled="disabled"></option>') ;
          }else{
            el=document.createElement('option');
            el.value="***";
            el.innerHTML=emptyName;
            selectElement.appendChild(el);
            el=document.createElement('option');
            el.disabled="disabled";
            selectElement.appendChild(el);
          }
        }
   
          // generate option elements.
        objects.forEach(function(object) {
        if (object) {
          // either get the name from the object or convert object to string.
          var name = nameKey ? (object.get ? object.get(nameKey) : object[nameKey]) : object.toString() ;
   
          // localize name if specified.
          if(shouldLocalize)
          {
            name = name.loc();
          }
   
          // get the value using the valueKey or the object if no valueKey.
          // then convert to a string or use _guid if one of available.
          var value = (valueKey) ? (object.get ? object.get(valueKey) : object[valueKey]) : object ;
          if (value) value = (SC.guidFor(value)) ? SC.guidFor(value) : value.toString() ;
   
          // render HTML
          var disable = (this.validateMenuItem && this.validateMenuItem(value, name)) ? '' : 'disabled="disabled" ' ;
          if(firstTime){
            context.push('<option %@value="%@">%@</option>'.fmt(disable,value,name)) ;
          } else{
            el=document.createElement('option');
            el.value=value;
            el.innerHTML=name;
            if(disable.length>0) el.disable="disabled";
            selectElement.appendChild(el);
          }
        // null value means separator.
        } else {
          if(firstTime){
            context.push('<option disabled="disabled"></option>') ;
          }else{
            el=document.createElement('option');
            el.disabled="disabled";
            selectElement.appendChild(el);
          }
        }
      }, this );
   
      this.setFieldValue(fieldValue);
   
      } else {
        this.set('value',null);
      }
    }
  },
  
  displayProperties: ['objects','nameKey','valueKey'],

  _objectsObserver: function() {
    this.set('cpDidChange', YES);
  }.observes('objects'),
   
  _nameKeyObserver: function() {
    this.set('cpDidChange', YES);
  }.observes('nameKey'),
   
  _valueKeyObserver: function() {
    this.set('cpDidChange', YES);
  }.observes('valueKey'),
    
  
   
  // .......................................
  // PRIVATE
  //
   
  $input: function() { return this.$(); },
   
  /* @private */
  mouseDown: function(evt) {
    
    if (!this.get('isEnabled')) {
      evt.stop();
      return YES;
    } else return arguments.callee.base.apply(this,arguments);
  },
   
  // when fetching the raw value, convert back to an object if needed...
  /** @private */
  getFieldValue: function() {
    var value = arguments.callee.base.apply(this,arguments); // get raw value... 
    var valueKey = this.get('valueKey') ;
    var objects = this.get('objects') ;
    var found, object;
    
    // Handle empty selection.
    if (value == '***') {
      value = null ;
    
    // If no value key was set and there are objects then match back to an
    // object.
    } else if (value && objects) {
      // objects = Array.from(objects) ;
      
      var loc = (SC.typeOf(objects.length) === SC.T_FUNCTION) ? objects.length() : objects.length;
      
      found = null ; // matching object goes here.
      while(!found && (--loc >= 0)) {
        object = objects.objectAt? objects.objectAt(loc) : objects[loc] ;
      
        // get value using valueKey if there is one or use object
        // map to _guid or toString.
        if (valueKey) object = (object.get) ? object.get(valueKey) : object[valueKey] ;
        var ov = (object) ? (SC.guidFor(object) ? SC.guidFor(object) : object.toString()) : null ;
      
        // use this object value if it matches.
        if (value == ov) found = object ;
      }
    }
    
    return (valueKey || found) ? found : value;
  },
  
  setFieldValue: function(newValue) {
    if (SC.none(newValue)) { newValue = '' ; }
    else {
      newValue = ((newValue) ? (SC.guidFor(newValue) ? SC.guidFor(newValue) : newValue.toString()) : null );
    }
    this.$input().val(newValue);
    return this ;
  },
  
  
  
 
  
  fieldDidFocus: function() {
    var isFocused = this.get('isFocused');
    if (!isFocused) this.set('isFocused', true);
  },

  fieldDidBlur: function() {
    var isFocused = this.get('isFocused');
    if (isFocused) this.set('isFocused', false);
  },


  
  _isFocusedObserver: function() {
    this.$().setClass('focus', this.get('isFocused'));
  }.observes('isFocused'),

  didCreateLayer: function() {
    var input = this.$input();
    SC.Event.add(input, 'blur', this, this.fieldDidBlur);
    SC.Event.add(input, 'focus',this, this.fieldDidFocus);
    return arguments.callee.base.apply(this,arguments);
  },
  
  willDestroyLayer: function() {
    var input = this.$input();
    SC.Event.remove(input, 'focus', this, this.fieldDidFocus);
    SC.Event.remove(input, 'blur', this, this.fieldDidBlur);
    return arguments.callee.base.apply(this,arguments);
  }
 
});

/* >>>>>>>>>> BEGIN source/views/slider.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @class

  A SliderView shows a horizontal slider control that you can use to set 
  variable values.

  You can use a slider view much like you would any other control.  Simply
  set the value or content/contentValueKey to whatever value you want to 
  display.  You can also set the maximumValue and minValue properties to 
  determine the mapping of the control to its children.
  
  @extends SC.View
  @extends SC.Control
  @since SproutCore 1.0
  @test in progress
*/
SC.SliderView = SC.View.extend(SC.Control,
/** @scope SC.SliderView.prototype */ {
  
  classNames: 'sc-slider-view',
  
  /** 
    The DOM element that displays the handle.  This element will have its
    top-left corner updated to reflect the current state of the slider.  Use
    margin-offsets to properly position your handle over this location.
  */
  handleSelector: 'img.sc-handle',
  
  /**
    Bind this to the current value of the progress bar.  Note that by default 
    an empty value will disable the progress bar and a multiple value too make 
    it indeterminate.
  */
  value: 0.50,
  valueBindingDefault: SC.Binding.single().notEmpty(),
  
  /**
    The minimum value of the progress.
  */ 
  minimum: 0,
  minimumBindingDefault: SC.Binding.single().notEmpty(),

  /**
    Optionally specify the key used to extract the minimum progress value 
    from the content object.  If this is set to null then the minimum value
    will not be derived from the content object.
    
    @property {String}
  */
  contentMinimumKey: null,
  
  /**
    The maximum value of the progress bar.
  */
  maximum: 1.0,
  maximumBindingDefault: SC.Binding.single().notEmpty(),

  /**
    Optionally specify the key used to extract the maximum progress value 
    from the content object.  If this is set to null then the maximum value
    will not be derived from the content object.
    
    @property {String}
  */
  contentMaximumKey: null,
  
  /**
    Optionally set to the minimum step size allowed.
    
    All values will be rounded to this step size when displayed.
  */
  step: 0.1,

  // ..........................................................
  // INTERNAL PROPERTIES
  // 
  
  displayProperties: 'value minimum maximum'.w(),
  
  render: function(context, firstTime) {
    arguments.callee.base.apply(this,arguments);
    
    var min = this.get('minimum');
    var max = this.get('maximum');
    var value = this.get('value');

    // determine the constrained value.  Must fit within min & max
    value = Math.min(Math.max(value, min), max);

    // limit to step value
    var step = this.get('step');
    if (!SC.none(step) && step !== 0) {
      value = Math.round(value / step) * step;
    }
    
    // determine the percent across
    value = Math.floor((value - min) / (max - min) * 100);
    
    if(firstTime) {
      var blankImage = SC.BLANK_IMAGE_URL;
      context.push('<span class="sc-inner">');
      context.push('<span class="sc-leftcap"></span>');
      context.push('<span class="sc-rightcap"></span>');
      context.push('<img src="', blankImage, '" class="sc-handle" style="left: ', value, '%" />');
      context.push('</span>');
    }
    else {
      this.$(this.get('handleSelector')).css('left', value + "%");
    }
    
  },
  
  _isMouseDown: NO,
  
  mouseDown: function(evt) {
    if (!this.get('isEnabled')) return YES; // nothing to do...
    this.set('isActive', YES);
    this._isMouseDown = YES ;
    return this._triggerHandle(evt);
  },
  
  // mouseDragged uses same technique as mouseDown.
  mouseDragged: function(evt) { 
    return this._isMouseDown ? this._triggerHandle(evt) : YES; 
  },
  
  // remove active class
  mouseUp: function(evt) {
    if (this._isMouseDown) this.set('isActive', NO);
    var ret = this._isMouseDown ? this._triggerHandle(evt) : YES ;
    this._isMouseDown = NO;
    return ret ;
  },
  
  /** @private
    Updates the handle based on the mouse location of the handle in the
    event.
  */
  _triggerHandle: function(evt) {
    var loc = this.convertFrameFromView({ x: evt.pageX }).x ;
    var width = this.get('frame').width ;

    // constrain loc to 8px on either side (left to allow knob overhang)
    loc = Math.max(Math.min(loc,width-8), 8) - 8;
    width -= 16 ; // reduce by margin
    
    // convert to percentage
    loc = loc / width ;
    
    var min = this.get('minimum'),max=this.get('maximum');  
    var step = this.get('step'), v=this.get('value');

    // convert to value using minimum/maximum then constrain to steps
    loc = min + ((max-min)*loc);
    if (step !== 0) loc = Math.round(loc / step) * step ;

    // if changes by more than a rounding amount, set v.
    if (Math.abs(v-loc)>=0.01) this.set('value', loc); // adjust 
    return YES ;
  },
  
  /** tied to the isEnabled state */
  acceptsFirstResponder: function() {
    return this.get('isEnabled');
  }.property('isEnabled'),
  
  willBecomeKeyResponderFrom: function(keyView) {
    // focus the text field.
    if (!this._isFocused) {
      this._isFocused = YES ;
      this.becomeFirstResponder();
      if (this.get('isVisibleInWindow')) {
        this.$()[0].focus();
      }
    }
  },
  
  willLoseKeyResponderTo: function(responder) {
    if (this._isFocused) this._isFocused = NO ;
  },
  
  keyDown: function(evt) {

     // handle tab key
     if (evt.which === 9) {
       var view = evt.shiftKey ? this.get('previousValidKeyView') : this.get('nextValidKeyView');
       view.becomeFirstResponder();
       return YES ; // handled
     }
     
     if (evt.which === 37 || evt.which === 38 || evt.which === 39 || evt.which === 40){
       var min = this.get('minimum'),max=this.get('maximum');  
       var step = this.get('step');
       var size = max-min, val=0, calculateStep;
     
       if (evt.which === 37 || evt.which === 38 ){
         if(step === 0){
           if(size<100){
             val = this.get('value')-1;
           }else{
             calculateStep = Math.abs(size/100);
             if(calculateStep<2) calculateStep =2;
             val = this.get('value')-Math.abs(size/100);
           }
         }else{
           val = this.get('value')-step;
         }
       }
       if (evt.which === 39 || evt.which === 40 ){
           if(step === 0){
              if(size<100){
                val = this.get('value') + 2;
              }else{
                calculateStep = Math.abs(size/100);
                if(calculateStep<2) calculateStep =2;
                val = this.get('value')+calculateStep;
              }
            }else{
              val = this.get('value')+step;
            }       
       }
       if(val>=min && val<=max) this.set('value', val);
     }
     //handle arrows

     // validate keyDown...
     // if (this.performValidateKeyDown(evt)) {
     //    this._isKeyDown = YES ;
     //    evt.allowDefault(); 
     //  } else {
     //    evt.stop();
     //  }
     SC.RunLoop.begin().end();
     return YES; 
   },

  contentPropertyDidChange: function(target, key) {
    var content = this.get('content');
    this.beginPropertyChanges()
      .updatePropertyFromContent('value', key, 'contentValueKey', content)
      .updatePropertyFromContent('minimum', key, 'contentMinimumKey', content)
      .updatePropertyFromContent('maximum', key, 'contentMaximumKey', content)
      .updatePropertyFromContent('isIndeterminate', key, 'contentIsIndeterminateKey', content)
    .endPropertyChanges();
  }  
  
});
/* >>>>>>>>>> BEGIN source/views/source_list_group.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('mixins/collection_group');
sc_require('views/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.Control
  @author Charles Jolley
  @author Erich Ocean
  @version 1.0
  @since 0.9
*/

SC.SourceListGroupView = SC.View.extend(SC.Control, SC.CollectionGroup,
/** @scope SC.SourceListGroupView.prototoype */ {
  
  classNames: ['sc-source-list-group'],
  
  // ..........................................................
  // KEY PROPERTIES
  // 
  
  /**
    The content object the source list group will display.
    
    @type SC.Object
  */
  content: null,
  
  /**
    The current group visibility.  Used by the source list to determine the 
    layout size of the group.
    
    @type Boolean
  */
  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.
    
    @type Boolean
  */
  hasGroupTitle: YES,
  
  /**
    The content property key to use as the group view's title.
    
    @type String
  */
  groupTitleKey: null,
  
  /**
    The content property key to use to determine if the group's children are 
    visible or not.
    
    @type String
  */
  groupVisibleKey: null,
  
  render: function(context, firstTime) {
    context.push('<a href="javascript:;" class="sc-source-list-label sc-disclosure-view sc-button-view button disclosure no-disclosure">') ;
    context.push('<img src="%@" class="button" />'.fmt(SC.BLANK_IMAGE_URL)) ;
    context.push('<span class="label"></span></a>') ;
  },
  
  /** @private */
  createChildViews: function() {
    
  },
  
  /** @private */
  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('groupTitleKey', this.displayDelegate) ;
    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('groupVisibleKey', this.displayDelegate) ;
    if ((key == '*') || (groupVisibleKey && (key == groupVisibleKey))) {
      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') ;
    }
  },
  
  /** @private
    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('groupVisibleKey', this.displayDelegate) ;
    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.
      
      @type Boolean
    */
    value: YES,
    
    /** @private
      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')
    
  })
  
});

/* >>>>>>>>>> BEGIN source/views/source_list.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/list') ;
sc_require('views/source_list_group');

SC.BENCHMARK_SOURCE_LIST_VIEW = YES ;

/** @class
  
  Displays a source list like the source list in iTunes.  SourceList views
  are very similar to ListView's but come preconfigured with the correct
  appearance and default behaviors of a source list.
  
  @extends SC.ListView
  @since SproutCore 1.0
*/
SC.SourceListView = SC.ListView.extend(
/** @scope SC.SourceListView.prototype */ {

  /**
    Add class name to HTML for styling.
  */
  classNames: ['sc-source-list'],
  
  /**
    Default row height for source list items is larger.
  */
  rowHeight: 32,

  /**
    By default source lists should not select on mouse down since you will
    often want to drag an item instead of selecting it.
  */
  selectOnMouseDown: NO,
  
  /**
    By default, SourceListView's trigger any action you set whenever the user
    clicks on an item.  This gives the SourceList a "menu" like behavior.
  */
  actOnSelect: YES

});

/* >>>>>>>>>> BEGIN source/views/split_divider.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/split');

/**
  @class

  A SplitDividerView displays a divider between two views within a SplitView.
  Clicking and dragging the divider will change the thickness of each view
  either to the top/left or bottom/right of the divider.

  Double-clicking on the SplitDividerView will try to collapse the first
  view within the SplitView that has property canCollapse set to true,
  so it is not visible, unless you have canCollapse disabled on the SplitView.

  This view must be a direct child of the split view it works with. It must
  be surrounded by two other views.

  @extends SC.View

  @author Charles Jolley
  @author Lawrence Pit
  @author Erich Ocean
  @test in split
*/
SC.SplitDividerView = SC.View.extend(
/** @scope SC.SplitDividerView.prototype */ {

  classNames: ['sc-split-divider-view'],
  
  /** @private */
  prepareContext: function(context, firstTime) {
    var splitView = this.get('splitView') ;
    if (splitView) this.set('cursor', splitView.get('thumbViewCursor')) ;
    return arguments.callee.base.apply(this,arguments) ;
  },
  
  mouseDown: function(evt) {
    var splitView = this.get('splitView');
    return (splitView) ? splitView.mouseDownInThumbView(evt, this) : arguments.callee.base.apply(this,arguments);
  },
  
  // FIXME: how does this work with event capture?
  doubleClick: function(evt) {
    console.log('doubleClick in split divider');
    var splitView = this.get('splitView');
    return (splitView) ? splitView.doubleClickInThumbView(evt, this) : arguments.callee.base.apply(this,arguments);
  }
  
});

/* >>>>>>>>>> BEGIN source/views/split.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/split_divider');

SC.RESIZE_BOTH = 'resize-both' ;
SC.RESIZE_TOP_LEFT = 'resize-top-left' ;
SC.RESIZE_BOTTOM_RIGHT = 'resize-bottom-right' ;

/**
  @class
  
  A split view is used to show views that the user can resize or collapse.
  To use a split view you need to set a topLeftView, a bottomRightView and,
  optionally, a splitDividerView.  You can also set various other properties
  to control the minimum and maximum thickness allowed for the flexible views.
  
  h2. Example
  
  {{{
    SC.SplitView.design({
      
      // the left view...
      topLeftView: SC.View.design({
        // view contents
      }),
      
      // the right view
      bottomRightView: SC.View.design({
        // view contents
      })
      
    })
  }}}
  
  When the user clicks and drags on a split divider view, it will
  automatically resize the views immediately before and after the split
  divider view. You can constrain the resizing allowed by the split view
  either by setting a minThickness and maxThickness property on the views
  themselves or by implementing the method splitViewConstrainThickness on
  a delegate object.
  
  In addition to resizing views, users can also collapse views by double
  clicking on a split divider view.  When a view is collapsed, it's isVisible
  property is set to NO and its space it removed from the view.  Double
  clicking on a divider again will restore a collapsed view.  A user can also
  start to drag the divider to show the collapsed view.
  
  You can programmatically control collapsing behavior using various properties
  on either the split view or its child views, and/or by implementing the
  method splitViewCanCollapse on a delegate object.
  
  Finally, SplitViews can layout their child views either horizontally or
  vertically.  To choose the direction of layout set the layoutDirection
  property on the view (or the :direction option with the view helper).
  This property should be set when the view is created. Changing it
  dynamically will have an unknown effect.
  
  @property {Boolean} layoutDirection Either SC.HORIZONTAL or SC.VERTICAL.
  Defaults to SC.HORIZONTAL. Use the :direction option with the split_view
  viewhelper.
  
  @property {Boolean} canCollapseViews Set to NO when you don't want any of
  the child views to collapse. Defaults to YES. 
  
  In addition, the top/left and bottom/right child views can have these
  properties:
  
  @extends SC.View
  @since SproutCore 1.0
  
  @author Charles Jolley
  @author Lawrence Pit
  @author Erich Ocean
*/
SC.SplitView = SC.View.extend(
/** @scope SC.SplitView.prototype */ {
  
  classNames: ['sc-split-view'],
  
  childLayoutProperties: 'layoutDirection dividerThickness autoresizeBehavior'.w(),
  
  displayProperties: ['layoutDirection'],
  
  /**
    delegate for controlling split view behavior.
  */
  delegate: null,
  
  /**
    Direction of layout.  Must be SC.LAYOUT_HORIZONTAL or SC.LAYOUT_VERTICAL.
    
    @property {String}
  */
  layoutDirection: SC.LAYOUT_HORIZONTAL,
  
  /**
    Set to NO to disable collapsing for all views.
  */
  canCollapseViews: YES,
  
  /*
    Configure which view(s) you want to autoresize when this split view's 
    layout changes.  Possible options are:
    
    | SC.RESIZE_BOTTOM_RIGHT | (default) resizes bottomRightView |
    | SC.RESIZE_TOP_LEFT | resized topLeftView |
    
  */
  autoresizeBehavior: SC.RESIZE_BOTTOM_RIGHT,
  
  /**
    Specifies how much space the fixed view should use when the view is setup.
    A number less than one will be treated as a percentage, while a number 
    greater than one will be treated as a pixel width.
    
    The thickness will be applied to the opposite view defined by autoresizeBehavior.
    
    @property {Number}
  */
  defaultThickness: 0.5,
  
  /**
    Yes, we're a split view.
  */
  isSplitView: YES,
  
  // add default views
  topLeftView: SC.View,
  dividerView: SC.SplitDividerView,
  bottomRightView: SC.View,
  
  /**
    The current thickness for the topLeftView
  */
  topLeftThickness: function() {
    var view = this.get('topLeftView');
    return view ? this.thicknessForView(view) : 0;
  }.property('topLeftView').cacheable(),

  /**
    The current thickness for the bottomRightView
  */
  bottomRightThickness: function() {
    var view = this.get('bottomRightView');
    return view ? this.thicknessForView(view) : 0;
  }.property('bottomRightView').cacheable(),
  
  /**
    @property {SC.Cursor} the cursor thumb view should use for themselves
  */
  thumbViewCursor: null,
  
  /**
    Used by split divider to decide if the view can be collapsed.
  */
  canCollapseView: function(view) {
    // console.log('%@.canCollapseView(%@)'.fmt(this, view));
    return this.invokeDelegateMethod(this.delegate, 'splitViewCanCollapse', this, view) ;
  },
  
  /**
    Returns the thickness for a given view.
    
    @param {SC.View} view the view to get.
    @returns the view with the width.
  */
  thicknessForView: function(view) {
    // console.log('%@.thicknessForView(%@)'.fmt(this, view));
    var direction = this.get('layoutDirection') ;
    var ret = view.get('frame') ;
    return (direction === SC.LAYOUT_HORIZONTAL) ? ret.width : ret.height ;
  },
  
  createChildViews: function() {
    // console.log('%@.createChildViews()'.fmt(this));
    var childViews = [] ;
    var viewAry = ['topLeftView', 'dividerView', 'bottomRightView'] ;
    var view, idx, len ;
    
    for (idx=0, len=viewAry.length; idx<len; ++idx) {
      if (view = this.get(viewAry[idx])) {
        view = this[viewAry[idx]] = this.createChildView(view, {
          layoutView: this,
          rootElementPath: [idx]
        }) ;
        childViews.push(view) ;
      }
    }
    
    this.set('childViews', childViews) ;
    return this ; 
  },
  
  /**
    Layout the views.
    
    This method needs to be called anytime you change the view thicknesses
    to make sure they are arranged properly.  This will set up the views so
    that they can resize appropriately.
  */
  updateChildLayout: function() {
    // console.log('%@.updateChildLayout()'.fmt(this));
    var topLeftView = this.get('topLeftView') ;
    var bottomRightView = this.get('bottomRightView') ;
    var dividerView = this.get('dividerView') ;
    var direction = this.get('layoutDirection') ;
    var topLeftThickness = this._desiredTopLeftThickness ;
    var dividerThickness = this.get('dividerThickness') || 7 ;
    var splitViewThickness = (direction == SC.LAYOUT_HORIZONTAL) ? this.get('frame').width : this.get('frame').height ;
    var bottomRightThickness = splitViewThickness - dividerThickness - topLeftThickness ;
    var autoresizeBehavior = this.get('autoresizeBehavior') ;
    var layout ;
    var isCollapsed ;
    
    // console.log('topLeftThickness == %@'.fmt(topLeftThickness));
    // console.log('dividerThickness == %@'.fmt(dividerThickness));
    // console.log('splitViewThickness == %@'.fmt(splitViewThickness));
    // console.log('bottomRightThickness == %@'.fmt(bottomRightThickness));
    
    // top/left view
    isCollapsed = topLeftView.get('isCollapsed') || NO ;
    topLeftView.setIfChanged('isVisible', !isCollapsed) ;
    layout = SC.clone(topLeftView.get('layout'));
    if (direction == SC.LAYOUT_HORIZONTAL) {
      layout.top = 0 ;
      layout.left = 0 ;
      layout.bottom = 0 ;
      switch (autoresizeBehavior) {
        case SC.RESIZE_BOTH:
          throw "SC.RESIZE_BOTH is currently unsupported.";
        case SC.RESIZE_TOP_LEFT:
          layout.right = bottomRightThickness + dividerThickness ;
          delete layout.width ;
          break ;
        case SC.RESIZE_BOTTOM_RIGHT:
          delete layout.right ;
          delete layout.height ;
          layout.width = topLeftThickness ;
          break ;
      }
    } else {
      layout.top = 0;
      layout.left = 0 ;
      layout.right = 0 ;
      switch (autoresizeBehavior) {
        case SC.RESIZE_BOTH:
          throw "SC.RESIZE_BOTH is currently unsupported.";
        case SC.RESIZE_TOP_LEFT:
          layout.bottom = bottomRightThickness + dividerThickness ;
          delete layout.height ;
          break ;
        case SC.RESIZE_BOTTOM_RIGHT:
          delete layout.bottom ;
          delete layout.width ;
          layout.height = topLeftThickness ;
          break ;
      }
    }
    // console.log('topLeftView layout %@'.fmt(SC.inspect(layout)));
    // console.log(topLeftView);
    topLeftView.set('layout', layout);
    
    // split divider view
    if (dividerView) {
      layout = SC.clone(dividerView.get('layout'));
      if (direction == SC.LAYOUT_HORIZONTAL) {
        layout.width = dividerThickness;
        delete layout.height ;
        layout.top = 0 ;
        layout.bottom = 0 ;
        switch (autoresizeBehavior) {
          case SC.RESIZE_BOTH:
            throw "SC.RESIZE_BOTH is currently unsupported.";
            // delete layout.left ;
            // delete layout.right ;
            // layout.centerX = topLeftThickness + (dividerThickness / 2) ;
            // delete layout.centerY ;
            //break ;
          case SC.RESIZE_TOP_LEFT:
            delete layout.left ;
            layout.right = bottomRightThickness ;
            delete layout.centerX ;
            delete layout.centerY ;
            break ;
          case SC.RESIZE_BOTTOM_RIGHT:
            layout.left = topLeftThickness ;
            delete layout.right ;
            delete layout.centerX ;
            delete layout.centerY ;
            break ;
        }
      } else {
        // console.log('setting vertical divider layout');
        // console.log('autoresizeBehavior is %@'.fmt(autoresizeBehavior));
        delete layout.width ;
        layout.height = dividerThickness ;
        layout.left = 0 ;
        layout.right = 0 ;
        switch (autoresizeBehavior) {
          case SC.RESIZE_BOTH:
            throw "SC.RESIZE_BOTH is currently unsupported.";
            // delete layout.top ;
            // delete layout.bottom ;
            // delete layout.centerX ;
            // layout.centerY = topLeftThickness + (dividerThickness / 2) ;
            //break ;
          case SC.RESIZE_TOP_LEFT:
            delete layout.top ;
            layout.bottom = bottomRightThickness ;
            delete layout.centerX ;
            delete layout.centerY ;
            break ;
          case SC.RESIZE_BOTTOM_RIGHT:
            layout.top = topLeftThickness ;
            delete layout.bottom ;
            delete layout.centerX ;
            delete layout.centerY ;
            break ;
        }
      }
      // console.log('dividerView layout %@'.fmt(SC.inspect(layout)));
      // console.log(dividerView);
      dividerView.set('layout', layout);
    }
    
    // bottom/right view
    isCollapsed = bottomRightView.get('isCollapsed') || NO ;
    bottomRightView.setIfChanged('isVisible', !isCollapsed) ;
    layout = SC.clone(bottomRightView.get('layout'));
    if (direction == SC.LAYOUT_HORIZONTAL) {
      layout.top = 0 ;
      layout.bottom = 0 ;
      layout.right = 0 ;
      switch (autoresizeBehavior) {
        case SC.RESIZE_BOTH:
          throw "SC.RESIZE_BOTH is currently unsupported.";
          //break ;
        case SC.RESIZE_BOTTOM_RIGHT:
          layout.left = topLeftThickness + dividerThickness ;
          delete layout.width ;
          break ;
        case SC.RESIZE_TOP_LEFT:
          delete layout.left ;
          layout.width = bottomRightThickness ;
          break ;
      }
    } else {
      layout.left = 0 ;
      layout.right = 0 ;
      layout.bottom = 0 ;
      switch (autoresizeBehavior) {
        case SC.RESIZE_BOTH:
          throw "SC.RESIZE_BOTH is currently unsupported.";
          //break ;
        case SC.RESIZE_BOTTOM_RIGHT:
          layout.top = topLeftThickness + dividerThickness ;
          delete layout.height ;
          break ;
        case SC.RESIZE_TOP_LEFT:
          delete layout.top ;
          layout.height = bottomRightThickness ;
          break ;
      }
    }
    // console.log('bottomRightView layout %@'.fmt(SC.inspect(layout)));
    // console.log(bottomRightView);
    bottomRightView.set('layout', layout);
    
    // topLeftView.updateDisplayLayout();
    // dividerView.updateDisplayLayout();
    // bottomRightView.updateDisplayLayout();
    this.notifyPropertyChange('topLeftThickness')
        .notifyPropertyChange('bottomRightThickness');
  },
  
  /** @private */
  renderLayout: function(context, firstTime) {
    // console.log('%@.renderLayout(%@, %@)'.fmt(this, context, firstTime));
    // console.log('%@.frame = %@'.fmt(this, SC.inspect(this.get('frame'))));
    if (firstTime || this._recalculateDivider) {
      if (!this.get('thumbViewCursor')) {
        this.set('thumbViewCursor', SC.Cursor.create()) ;
      }
      
      var layoutDirection = this.get('layoutDirection') ;
      var fr = this.get('frame');
      var splitViewThickness, elemRendered = this.$();
      var dividerThickness = this.get('dividerThickness') || 7 ;
      var desiredThickness = this.get('defaultThickness') ;
      var autoResizeBehavior = this.get('autoresizeBehavior') ;
      // Turn a flag on to recalculate the spliting if the desired thickness
      // is a percentage
      // debugger;
      if(this._recalculateDivider===undefined && desiredThickness<1) {
        this._recalculateDivider=YES;
      }
      else if(this._recalculateDivider) this._recalculateDivider=NO;
      
      
      if(elemRendered[0]) {
        splitViewThickness = (layoutDirection == SC.LAYOUT_HORIZONTAL) ? 
              elemRendered[0].offsetWidth : elemRendered[0].offsetHeight ;
      }else{
        splitViewThickness = (layoutDirection == SC.LAYOUT_HORIZONTAL) ? 
              fr.width : fr.height ;
      }
      // if default thickness is < 1, convert from percentage to absolute
      if (SC.none(desiredThickness) || (desiredThickness > 0 && desiredThickness < 1)) {
        desiredThickness =  Math.floor((splitViewThickness - (dividerThickness))* (desiredThickness || 0.5)) ;
      }
      if (autoResizeBehavior === SC.RESIZE_BOTTOM_RIGHT) {
        this._desiredTopLeftThickness = desiredThickness ;
      } else { // (autoResizeBehavior === SC.RESIZE_TOP_LEFT)
        this._desiredTopLeftThickness =  splitViewThickness - dividerThickness - desiredThickness ;
      }
      
      // make sure we don't exceed our min and max values, and that collapse 
      // settings are respected
      // cached values are required by _updateTopLeftThickness() below...
      this._topLeftView = this.get('topLeftView') ;
      this._bottomRightView = this.get('bottomRightView') ;
      this._topLeftViewThickness = this.thicknessForView(this.get('topLeftView'));
      this._bottomRightThickness = this.thicknessForView(this.get('bottomRightView'));
      this._dividerThickness = this.get('dividerThickness') ;
      this._layoutDirection = this.get('layoutDirection') ;
      
      // this handles min-max settings and collapse parameters
      this._updateTopLeftThickness(0) ;
      
      // update the cursor used by thumb views
      this._setCursorStyle() ;
      
      // actually set layout for our child views
      this.updateChildLayout() ;
    }
    arguments.callee.base.apply(this,arguments) ;
  },
  
  /** @private */
  render: function(context, firstTime) {
    // console.log('%@.render(%@, %@)'.fmt(this, context, firstTime));
    // console.log('%@.frame = %@'.fmt(this, SC.inspect(this.get('frame'))));
    arguments.callee.base.apply(this,arguments) ;
    
    if (this._inLiveResize) this._setCursorStyle() ;
    
    if (firstTime) {
      var dir = this.get('layoutDirection') ;
      if (dir===SC.LAYOUT_HORIZONTAL) context.addClass('sc-horizontal') ;
      else context.addClass('sc-vertical') ;
    }
  },
  
  // layoutDidChangeFor: function(childView) {
  //   // console.log('%@.layoutChildViews(%@)'.fmt(this, childView));
  //   arguments.callee.base.apply(this,arguments) ;
  // },
  // 
  // layoutChildViews: function() {
  //   // console.log('%@.layoutChildViews()'.fmt(this));
  //   arguments.callee.base.apply(this,arguments) ;
  // },
  
  /**
    Update the split view's layout based on mouse movement.
    
    Call this method in the mouseDown: method of your thumb view. The split view
    will begin tracking the mouse and will update its own layout to reflect the movement 
    of the mouse. As a result, the position of your thumb view will also be updated.
    
    @returns {Boolean}
  */
  mouseDownInThumbView: function(evt, thumbView) {
    // console.log('%@.mouseDownInThumbView(%@, %@)'.fmt(this, evt, thumbView));
    // console.log(evt.originalEvent);
    var responder = this.getPath('pane.rootResponder') ;
    if (!responder) return NO ; // nothing to do
      
    // we're not the source view of the mouseDown:, so we need to capture events manually to receive them
    responder.dragDidStart(this) ;
    
    // cache for later
    this._mouseDownX = evt.pageX ;
    this._mouseDownY = evt.pageY ;
    this._thumbView = thumbView ;
    this._topLeftView = this.get('topLeftView') ;
    this._bottomRightView = this.get('bottomRightView') ;
    this._topLeftViewThickness = this.thicknessForView(this.get('topLeftView'));
    this._bottomRightThickness = this.thicknessForView(this.get('bottomRightView'));
    this._dividerThickness = this.get('dividerThickness') ;
    this._layoutDirection = this.get('layoutDirection') ;
    
    this.beginLiveResize() ;
    this._inLiveResize = YES ;
    
    return YES ;
  },
  
  mouseDragged: function(evt) {
    // console.log('%@.mouseDragged(%@)'.fmt(this, evt));
    // console.log(evt.originalEvent);
    var offset = (this._layoutDirection == SC.LAYOUT_HORIZONTAL) ? evt.pageX - this._mouseDownX : evt.pageY - this._mouseDownY ;
    this._updateTopLeftThickness(offset) ;
    return YES;
  },
  
  mouseUp: function(evt) {
    // console.log('%@.mouseUp(%@, %@)'.fmt(this, evt));
    // console.log(evt.originalEvent);
		if (this._inLiveResize === YES) {
    	this._thumbView = null ; // avoid memory leaks
    	this._inLiveResize = NO ;
    	this.endLiveResize() ;
    	return YES ;
		}
		
		return NO ;
  },
  
  doubleClickInThumbView: function(evt, thumbView) {
    // console.log('%@.mouseDragged(%@, %@)'.fmt(this, evt, thumbView));
    // console.log(evt.originalEvent);
    var view = this._topLeftView ;
    var isCollapsed = view.get('isCollapsed') || NO ;
    if (!isCollapsed && !this.canCollapseView(view)) {
      view = this._bottomRightView ;
      isCollapsed = view.get('isCollapsed') || NO ;
      if (!isCollapsed && !this.canCollapseView(view)) return NO;
    }
    
    if (!isCollapsed) {
      // remember thickness in it's uncollapsed state
      this._uncollapsedThickness = this.getThicknessForView(view)  ;
      // and collapse
      // this.setThicknessForView(view, 0) ;
      if (view === this._topLeftView) {
        this._topLeftViewThickness = 0 ;
      } else {
        this._bottomRightViewThickness = 0 ;
      }
      
      // if however the splitview decided not to collapse, clear:
      if (!view.get("isCollapsed")) {
        this._uncollapsedThickness = null;
      }
    } else {
      // uncollapse to the last thickness in it's uncollapsed state
      if (view === this._topLeftView) {
        this._topLeftViewThickness = this._uncollapsedThickness ;
      } else {
        this._bottomRightViewThickness = this._uncollapsedThickness ;
      }
      view._uncollapsedThickness = null ;
    }
    this._setCursorStyle() ;
    return true ;
  },
  
  /** @private */
  _updateTopLeftThickness: function(offset) {
    // console.log('%@._updateTopLeftThickness(%@)'.fmt(this, offset));
    // console.log('%@.frame = %@'.fmt(this, SC.inspect(this.get('frame'))));
    var topLeftView = this._topLeftView ;
    var bottomRightView = this._bottomRightView ;
    var topLeftViewThickness = this.thicknessForView(topLeftView); // the current thickness, not the original thickness
    var bottomRightViewThickness = this.thicknessForView(bottomRightView);
    
    var minAvailable = this._dividerThickness ;
    var maxAvailable = 0;
    if (!topLeftView.get("isCollapsed")) maxAvailable += topLeftViewThickness ;
    if (!bottomRightView.get("isCollapsed")) maxAvailable += bottomRightViewThickness ;
    
    var proposedThickness = this._topLeftViewThickness + offset;
    var direction = this._layoutDirection ;
    var bottomRightCanCollapse = this.canCollapseView(bottomRightView);
    
    var thickness = proposedThickness;
    
    // constrain to thickness set on top/left
    var max = this.get('topLeftMaxThickness') ;
    var min = this.get('topLeftMinThickness') ;
    
    if (!SC.none(max)) thickness = Math.min(max, thickness) ;
    if (!SC.none(min)) thickness = Math.max(min, thickness) ;
    
    // constrain to thickness set on bottom/right
    max = this.get('bottomRightMaxThickness') ;
    min = this.get('bottomRightMinThickness') ;
    var bottomRightThickness = maxAvailable - thickness ;
    if (!SC.none(max)) bottomRightThickness = Math.min(max, bottomRightThickness) ;
    if (!SC.none(min)) bottomRightThickness = Math.max(min, bottomRightThickness) ;
    thickness = maxAvailable - bottomRightThickness ;
    
    // constrain to thickness determined by delegate.
    thickness = this.invokeDelegateMethod(this.delegate, 'splitViewConstrainThickness', this, topLeftView, thickness) ;
    
    // cannot be more than what's available
    thickness = Math.min(thickness, maxAvailable) ;
    
    // cannot be less than zero
    thickness = Math.max(0, thickness) ;
    
    var tlCollapseAtThickness = topLeftView.get('collapseAtThickness') ;
    if (!tlCollapseAtThickness) tlCollapseAtThickness = 0 ;
    var brCollapseAtThickness = bottomRightView.get('collapseAtThickness') ;
    brCollapseAtThickness = SC.none(brCollapseAtThickness) ? maxAvailable : (maxAvailable - brCollapseAtThickness);
    
    if ((proposedThickness <= tlCollapseAtThickness) && this.canCollapseView(topLeftView)) {
      // want to collapse top/left, check if this doesn't violate the max thickness of bottom/right
      max = bottomRightView.get('maxThickness');
      if (!max || (minAvailable + maxAvailable) <= max) {
        // collapse top/left view, even if it has a minThickness
        thickness = 0 ;
      }
    } else if (proposedThickness >= brCollapseAtThickness && this.canCollapseView(bottomRightView)) {
      // want to collapse bottom/right, check if this doesn't violate the max thickness of top/left
      max = topLeftView.get('maxThickness');
      if (!max || (minAvailable + maxAvailable) <= max) {
        // collapse bottom/right view, even if it has a minThickness
        thickness = maxAvailable;
      }
    }
    
    // now apply constrained value
    if (thickness != this.thicknessForView(topLeftView)) {
      this._desiredTopLeftThickness = thickness ;
      
      // un-collapse if needed.
      topLeftView.set('isCollapsed', thickness === 0) ;
      bottomRightView.set('isCollapsed', thickness >= maxAvailable) ;
      
      // this.set('displayNeedsUpdate', YES);
      // this.adjustLayout();
      this.updateChildLayout(); // updates child layouts
      this.displayDidChange(); // updates cursor
    }
  },
  
  /** 
    This observes 'layoutDirection' to update the cursor style immediately
    after the value of the layoutDirection of Split view is changed

    @private 
  */
  _setCursorStyle: function() {
    // console.log('%@._setCursorStyle()'.fmt(this));
    var topLeftView = this._topLeftView ;
    var bottomRightView = this._bottomRightView ;
    var thumbViewCursor = this.get('thumbViewCursor') ;
    this._layoutDirection = this.get('layoutDirection') ;
    // updates the cursor of the thumb view that called mouseDownInThumbView() to reflect the status of the drag
    var tlThickness = this.thicknessForView(topLeftView) ;
    var brThickness = this.thicknessForView(bottomRightView) ;
    if (topLeftView.get('isCollapsed') || tlThickness == this.get("topLeftMinThickness") || brThickness == this.get("bottomRightMaxThickness")) {
      thumbViewCursor.set('cursorStyle', this._layoutDirection == SC.LAYOUT_HORIZONTAL ? "e-resize" : "s-resize") ;
    } else if (bottomRightView.get('isCollapsed') || tlThickness == this.get("topLeftMaxThickness") || brThickness == this.get("bottomRightMinThickness")) {
      thumbViewCursor.set('cursorStyle', this._layoutDirection == SC.LAYOUT_HORIZONTAL ? "w-resize" : "n-resize") ;
    } else {
      thumbViewCursor.set('cursorStyle', this._layoutDirection == SC.LAYOUT_HORIZONTAL ? "ew-resize" : "ns-resize") ;
    }
  }.observes('layoutDirection'),
  
  /**
    (DELEGATE) Control whether a view can be collapsed.
    
    The default implemention returns NO if the split view property
    canCollapseViews is set to NO or when the given view has
    property canCollapse set to NO, otherwise it returns YES.
    
    @param {SC.SplitView} splitView the split view
    @param {SC.View} view the view we want to collapse.
    @returns {Boolean} YES to allow collapse.
  */
  splitViewCanCollapse: function(splitView, view) {
    // console.log('%@.splitViewCanCollapse(%@, %@)'.fmt(this, splitView, view));
    if (splitView.get('canCollapseViews') === NO) return NO ;
    if (view.get('canCollapse') === NO) return NO ;
    return YES ;
  },
  
  /**
    (DELEGATE) Constrain a views allowed thickness.
    
    The default implementation allows any thickness.  The view will
    automatically constrain the view to not allow views to overflow the
    visible area.
    
    @param {SC.SplitView} splitView the split view
    @param {SC.View} view the view in question
    @param {Number} proposedThickness the proposed thickness.
    @returns the allowed thickness
  */
  splitViewConstrainThickness: function(splitView, view, proposedThickness) {
    // console.log('%@.splitViewConstrainThickness(%@, %@, %@)'.fmt(this, splitView, view, proposedThickness));
    return proposedThickness;
  },
  
  /* Force to rendering once the pane is attached */
  _forceSplitCalculation: function(){
    this.updateLayout(); 
  }.observes('*pane.isPaneAttached')

});

/* >>>>>>>>>> BEGIN source/views/stacked.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/collection') ;

/**
  @class 

  A StackedView is a CollectionView that expects its content to use static 
  layout to stack vertically.  This type of collection view is not designed 
  for use with large size collections, but it can be very useful for 
  collections with complex displays and variable heights such as comments or
  small notification queues.
  
  h2. Static Layout
  
  This view makes no attempt to size or position your child views.  It assumes
  you are using StaticLayout for your child views.  If you don't enable static
  layout your views will probably overlay on top of eachother and will look 
  incorrect.

  Note also that the default layout for this view set's the height to "auto".
  This is usually the behavior you will want. 
  
  @extends SC.CollectionView
  @since SproutCore 0.9
*/
SC.StackedView = SC.CollectionView.extend( 
/** SC.StackedView.prototype */ {
  
  classNames: ['sc-stacked-view'],
  
  /** 
    Default layout for a stacked view will fill the parent view but auto-
    adjust the height of the view.
  */
  layout: { top: 0, left: 0, right: 0, height: 1 },
  
  /**
    Return full range of its indexes for nowShowing
    
    @returns {SC.IndexSet} full range of indexes
  */
  computeNowShowing: function(rect) {
    return this.get('allContentIndexes');
  },  

  /**
    Updates the height of the stacked view to reflect the current content of 
    the view.  This is called automatically whenever an item view is reloaded.
    You can also call this method directly if the height of one of your views
    has changed.
    
    The height will be recomputed based on the actual location and dimensions
    of the last child view.
    
    Note that normally this method will defer actually updating the height
    of the view until the end of the run loop.  You can force an immediate 
    update by passing YES to the "immediately" parameter.
    
    @param {Boolean} immediately YES to update immedately
    @returns {SC.StackedView} receiver
  */
  updateHeight: function(immediately) {
    if (immediately) this._updateHeight();
    else this.invokeLast(this._updateHeight);
    // ^ use invokeLast() here because we need to wait until all rendering has 
    //   completed.
    
    return this;
  },
  
  _updateHeight: function() {
    
    var childViews = this.get('childViews'),
        len        = childViews.get('length'),
        view, layer, height;
        
    if (len === 0) {
      height = 1; 
    } else {
      view = childViews.objectAt(len-1);
      layer = view ? view.get('layer') : null ;
      height = layer ? (layer.offsetTop + layer.offsetHeight) : 1 ;
      layer = null ; // avoid memory leaks
    }
    this.adjust('height', height);
  },
  
  // ..........................................................
  // INTERNAL SUPPORT
  // 

  /** @private
    Whenever the collection view reloads some views, reset the cache on the
    frame as well so that it will recalculate.
  */
  didReload: function(set) { return this.updateHeight(); },

  /** @private
    When layer is first created, make sure we update the height using the 
    newly calculated value.
  */
  didCreateLayer: function() { return this.updateHeight(); }
  
});

/* >>>>>>>>>> BEGIN source/views/tab.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/segmented');

SC.TOP_LOCATION = 'top';
SC.BOTTOM_LOCATION = 'bottom';

/** 
  @class

  Incorporates a segmented view and a container view to display the selected
  tab.  Provide an array of items, which will be passed onto the segmented
  view.
  
  @extends SC.View
  @since SproutCore 1.0
*/
SC.TabView = SC.View.extend(
/** @scope SC.TabView.prototype */ {

  classNames: ['sc-tab-view'],
  
  displayProperties: ['nowShowing'],

  // ..........................................................
  // PROPERTIES
  // 
  
  nowShowing: null,
  
  items: [],
  
  isEnabled: YES,
  
  itemTitleKey: null,
  itemValueKey: null,
  itemIsEnabledKey: null,
  itemIconKey: null,
  itemWidthKey: null,
  itemToolTipKey: null,
  
  tabLocation: SC.TOP_LOCATION,
  
  /** 
    If set, then the tab location will be automatically saved in the user
    defaults.  Browsers that support localStorage will automatically store
    this information locally.
  */
  userDefaultKey: null,
  
  
  // ..........................................................
  // FORWARDING PROPERTIES
  // 
  
  // forward important changes on to child views
  _tab_nowShowingDidChange: function() {
    var v = this.get('nowShowing');
    this.get('containerView').set('nowShowing',v);
    this.get('segmentedView').set('value',v);
    return this ;
  }.observes('nowShowing'),

  _tab_saveUserDefault: function() {
    // if user default is set, save also
    var v = this.get('nowShowing');
    var defaultKey = this.get('userDefaultKey');
    if (defaultKey) {
      SC.userDefaults.set([defaultKey,'nowShowing'].join(':'), v);
    }
  }.observes('nowShowing'),
  
  _tab_itemsDidChange: function() {
    this.get('segmentedView').set('items', this.get('items'));
    return this ;    
  }.observes('items'),

  /** @private
    Restore userDefault key if set.
  */
  init: function() {
    arguments.callee.base.apply(this,arguments);
    this._tab_nowShowingDidChange()._tab_itemsDidChange();
  },

  awake: function() {
    arguments.callee.base.apply(this,arguments);  
    var defaultKey = this.get('userDefaultKey');
    if (defaultKey) {
      defaultKey = [defaultKey,'nowShowing'].join(':');
      var nowShowing = SC.userDefaults.get(defaultKey);
      if (!SC.none(nowShowing)) this.set('nowShowing', nowShowing);
    }

  },
  
  createChildViews: function() {
    var childViews = [], view, ContainerView ;
    
    if (this.get('tabLocation') === SC.TOP_LOCATION) {
      ContainerView = this.containerView.extend({
        layout: { top:12, left:0, right:0, bottom: 0 }
      });
    } else {
      ContainerView = this.containerView.extend({
        layout: { top:0, left:0, right:0, bottom: 12 }
      });
    }
    
    view = this.containerView = this.createChildView(ContainerView) ;
    childViews.push(view);
    
    view = this.segmentedView = this.createChildView(this.segmentedView) ;
    childViews.push(view);
    
    this.set('childViews', childViews);
    return this; 
  },
  
  // ..........................................................
  // COMPONENT VIEWS
  // 

  /**
    The containerView managed by this tab view.  Note that TabView uses a 
    custom container view.  You can access this view but you cannot change 
    it.
  */
  containerView: SC.ContainerView,
  
  /**
    The segmentedView managed by this tab view.  Note that this TabView uses
    a custom segmented view.  You can access this view but you cannot change
    it.
  */
  segmentedView: SC.SegmentedView.extend({
    layout: { left: 0, right: 0, height: 24 },

    /** @private
      When the value changes, update the parentView's value as well.
    */
    _sc_tab_segmented_valueDidChange: function() {
      var pv = this.get('parentView');
      if (pv) pv.set('nowShowing', this.get('value'));
      
      // FIXME: why is this necessary? 'value' is a displayProperty and should
      // automatically cause displayDidChange() to fire, which should cause 
      // the two lines below to execute in the normal course of things...
      this.set('layerNeedsUpdate', YES) ;
      this.invokeOnce(this.updateLayerIfNeeded) ;
    }.observes('value'),
    
    /** @private
      When we need to actually create a container, look for the tab loc from
      the parent view and adjust the internal frame accordingly.  Also copy
      the item key settings from the tab view.
    */
    render: function(context, firstTime) {
      arguments.callee.base.apply(this,arguments);
      // copy some useful properties from the parent view first
      var pv = this.get('parentView');
      var tabLoc = (pv) ? pv.get('tabLocation') : SC.TOP_LOCATION ;
      if (tabLoc === SC.TOP_LOCATION) {
        context.addStyle('top', '0px');
      } else {
        context.addStyle('bottom', '0px');
      }
    },
    
    init: function() {
      // before we setup the rest of the view, copy key config properties 
      // from the owner view...
      var pv = this.get('parentView');
      if (pv) {
        SC._TAB_ITEM_KEYS.forEach(function(k) { this[k] = pv.get(k); }, this);
      }
      return arguments.callee.base.apply(this,arguments);
    }
  })
  
}) ;

SC._TAB_ITEM_KEYS = 'itemTitleKey itemValueKey itemIsEnabledKey itemIconKey itemWidthKey itemToolTipKey'.w();

/* >>>>>>>>>> BEGIN source/views/thumb.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/**
  @class

  A ThumbView works in concert with SC.SplitView to adjust the divider 
  position from an arbitrary subview of the SplitView. Simply make an
  instance of ThumbView a child somewhere in the childViews (or 
  descendants) of the split view and add the path to the ThumbView to the
  SplitView's thumbViews array.
  
  SplitView will automatically set the splitView property of the views in
  its thumbViews array.

  @extends SC.View
  @author Erich Ocean
  @test in split
*/
SC.ThumbView = SC.View.extend(
/** @scope SC.ThumbView.prototype */ {

  classNames: ['sc-thumb-view'],
  
  /**
    Enable this thumb view to control its parent split view.
  */
  isEnabled: YES,
  isEnabledBindingDefault: SC.Binding.bool(),
  
  /** @private */
  prepareContext: function(context, firstTime) {
    var splitView = this.get('splitView') ;
    if (splitView) this.set('cursor', splitView.get('thumbViewCursor')) ;
    return arguments.callee.base.apply(this,arguments) ;
  },
  
  mouseDown: function(evt) {
    if (!this.get('isEnabled')) return NO ;
    
    var splitView = this.get('splitView');
    return (splitView) ? splitView.mouseDownInThumbView(evt, this) : arguments.callee.base.apply(this,arguments);
  }
    
});

/* >>>>>>>>>> BEGIN source/views/toolbar.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @class

  A toolbar view can be anchored at the top or bottom of the window to contain
  your main toolbar buttons.  The default implementation assumes you may have
  a leftView, rightView, and centerView, which will be properly laid out.
  
  You can also override the layout property yourself or simply set the 
  anchorLocation to SC.ANCHOR_TOP or SC.ANCHOR_BOTTOM.  This will configure
  the layout of your toolbar automatically when it is created.

  @extends SC.View
  @since SproutCore 1.0
*/
SC.ToolbarView = SC.View.extend(
  /** @scope SC.ToolbarView.prototype */ {

  classNames: ['sc-toolbar-view'],
  
  /**
    Default anchor location.  This will be applied automatically to the 
    toolbar layout if you set it.
  */
  anchorLocation: null,

  // ..........................................................
  // INTERNAL SUPPORT
  // 
  
  /** @private */
  layout: { left: 0, height: 32, right: 0 },
  
  /** @private */
  init: function() {
    // apply anchor location before setting up the rest of the view.
    if (this.anchorLocation) {
      this.layout = SC.merge(this.layout, this.anchorLocation);
    }
    arguments.callee.base.apply(this,arguments); 
  }

});


/* >>>>>>>>>> BEGIN source/views/web.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================
/** @class

  Used to display an iframe. The source, (specified by the value property) of 
  the iFrame should be from the same domain. (i.e. the src / value should be 
  from the same domain) if you want to access the contents of the iframe.

  @extends SC.View
  @extends SC.Control
  @since SproutCore 1.0
*/
SC.WebView = SC.View.extend(SC.Control, {

  classNames: 'sc-web-view',
  displayProperties: ['value', 'shouldAutoResize'],

  /**
  The content of the iframe can be bigger than the size specifed when creating
  the view. If you want the view to be auto-resized to the dimensions of the 
  iframe, then set the value of this property to YES.
  The web view can be auto resized only if the contents are from the same
  domain as the parent domain.
  @property{Boolean}
  */
  shouldAutoResize: NO,

  render: function(context, firstTime) {
    var src = this.get('value');
    if (firstTime) {
      context.push('<iframe src="' + src + 
      '" style="position: absolute; width: 100%; height: 100%; border: 0px; margin: 0px; padding: 0p;"></iframe>');
    } else {
      var iframe = this.$('iframe');
      // clear out the previous src, to force a reload
      iframe.attr('src', 'javascript:;');
      iframe.attr('src', src);
    }
  },

  /**
  Called when the layer gets created. 
  */
  didCreateLayer: function() {
    var f = this.$('iframe');
    // Attach an onload event to the iframe.
    SC.Event.add(f, 'load', this, this.iframeDidLoad);
  },


  /** 
  Called when iframe onload event is fired.
  1. Resizes the view to fit the contents of the iframe using the 
  scroll width and scroll height of the contents of the iframe
  
  The iframe contents can be accessed only when the src is from the same
  domain as the parent document
  @returns {void}
  */
  iframeDidLoad: function() {
    //fit the iframe to size of the contents.
    if (this.get('shouldAutoResize') === YES){
      var contentWindow;
      var iframeElt = this.$('iframe')[0];
      if(iframeElt && iframeElt.contentWindow){
        contentWindow = iframeElt.contentWindow;
        if(contentWindow && contentWindow.document && contentWindow.document.documentElement){
          var docElement = contentWindow.document.documentElement;
          // setting the width before the height gives more accurate results.. 
          // atleast for the test iframe content i'm using.
          //TODO: try out document flows other than top to bottom.
          if (!SC.browser.isIE){
            this.$().width(docElement.scrollWidth);
            this.$().height(docElement.scrollHeight);          
          } else {
            this.$().width(docElement.scrollWidth + 12);
            this.$().height(docElement.scrollHeight + 5);          
          }
        }
      }
    }
  }
});

/* >>>>>>>>>> BEGIN source/views/well.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

/** @class

  A WellView is a ContainerView with a border. It's useful when you want to
  group a set of views. It allows you to easily switch its contents too.
  
  It has a default contentLayout that will replace the layout of the contentView.
  
  @extends SC.ContainerView
  @since SproutCore 1.0
  @test in progress
*/
SC.WellView = SC.ContainerView.extend(
/** @scope SC.WellView.prototype */ {
  
  classNames: 'sc-well-view',
  
  /**
    Layout for the content of the container view.
    @property {Object}
  */
  contentLayout: {top:10, bottom:10, left:10, right:10},
  
  
  /**
     Overrides createChildViews and replaces the layout of the contentView
     with the one in contentLayout.
   */
  
  createChildViews: function() {
    // if contentView is defined, then create the content
    var view = this.get('contentView') ;
    if (view) {
      view = this.contentView = this.createChildView(view) ;
      view.set('layout', this.contentLayout);
      this.childViews = [view] ;
    } 
  },
  
  /**
     The render method for the WellView simply add the html necessary for
     the border.
     
   */
  
  render: function(context, firstTime) {
    arguments.callee.base.apply(this,arguments);
    if(firstTime){
     context.push("<div class='top-left-edge'></div>",
       "<div class='top-edge'></div>",
       "<div class='top-right-edge'></div>",
       "<div class='right-edge'></div>",
       "<div class='bottom-right-edge'></div>",
       "<div class='bottom-edge'></div>",
       "<div class='bottom-left-edge'></div>",
       "<div class='left-edge'></div>",
       "<div class='content-background'></div>");
     }    
  },
  
  /**
     Invoked whenever the content property changes.  This method will simply
     call replaceContent and set the contentLayout in the new contentView.
     
     Override replaceContent to change how the view is
     swapped out.
   */
  contentViewDidChange: function() {
    var view = this.get('contentView');
    view.set('layout', this.contentLayout);
    this.replaceContent(view);
  }.observes('contentView')
  
}) ;
; tiki.script('sproutcore/desktop:en/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/core_tools', {"packages":{"sproutcore\/desktop":{"stylesheets":[{"url":"\/static\/sproutcore\/desktop\/en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/stylesheet.css","id":"sproutcore\/desktop:en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/stylesheet.css"}],"scripts":[{"url":"\/static\/sproutcore\/desktop\/en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/javascript.js","id":"sproutcore\/desktop:en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/javascript.js"}]}},"depends":["sproutcore\/desktop"],"scripts":[{"url":"\/static\/sproutcore\/core_tools\/en\/21a3b019085a887ba1a5de96aa8bdd91cc4aae36\/javascript.js","id":"sproutcore\/core_tools:en\/21a3b019085a887ba1a5de96aa8bdd91cc4aae36\/javascript.js"}]});
tiki.global('sproutcore/core_tools');

; tiki.script('sproutcore/core_tools:en/21a3b019085a887ba1a5de96aa8bdd91cc4aae36/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/datejs', {"scripts":[{"url":"\/static\/sproutcore\/datejs\/en\/3a43b520525add286092c549c8d5e417de93a4ba\/javascript.js","id":"sproutcore\/datejs:en\/3a43b520525add286092c549c8d5e417de93a4ba\/javascript.js"}]});
tiki.global('sproutcore/datejs');

; tiki.script('sproutcore/datejs:en/3a43b520525add286092c549c8d5e417de93a4ba/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/debug', {"scripts":[{"url":"\/static\/sproutcore\/debug\/en\/13200b9fa685f043535bda49964adb32feb44530\/javascript.js","id":"sproutcore\/debug:en\/13200b9fa685f043535bda49964adb32feb44530\/javascript.js"}]});
tiki.global('sproutcore/debug');

/* >>>>>>>>>> BEGIN source/core.js */
// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

// test
var SC = SC || {} ;

// Note:  We won't use SC.T_* here because those constants might not yet be
//        defined.
SC._mapDisplayNamesUseHashForSeenTypes = ['object', 'number', 'boolean', 'array', 'string', 'function', 'class', 'undefined', 'error'];  // 'hash' causes problems


SC.mapDisplayNames = function(obj, level, path, seenHash, seenArray) {
  if (!SC.browser.safari) return ;

  // Lazily instantiate the hash of types we'll use a hash for the "have we
  // seen this before?" structure.  (Some types are not safe to put in a hash
  // in this manner, so we'll use the hash for its algorithmic advantage when
  // possible, but fall back to an array using indexOf() when necessary.)
  if (!SC._mapDisplayNamesUseHashForSeenTypesHash) {
    var types = SC._mapDisplayNamesUseHashForSeenTypes ;
    var typesHash = {} ;
    var len = types.length ;
    for (var i = 0;  i < len;  ++i) {
      var type = types[i] ;
      typesHash[type] = true ;
    }
    SC._mapDisplayNamesUseHashForSeenTypesHash = typesHash ;
  }


  if (obj === undefined) obj = window ;
  if (level === undefined) level = 0 ;
  if (path === undefined) path = [] ;
  if (seenHash === undefined) seenHash = {} ;
  if (seenArray === undefined) seenArray = [] ;

  if (level > 5) return ;

  var useHash = !!SC._mapDisplayNamesUseHashForSeenTypesHash[SC.typeOf(obj)] ;

  var hash;
  var arrayToCheck;
  if (useHash) {
    hash = SC.hashFor(obj) ;
    arrayToCheck = seenHash[hash];
  }
  else {
    arrayToCheck = seenArray;
  }
  
  if (arrayToCheck  &&  arrayToCheck.indexOf(obj) !== -1) return ;
  
  if (arrayToCheck) {
    arrayToCheck.push(obj) ;
  }
  else if (useHash) {
    seenHash[hash] = [obj] ;
  }

  var loc = path.length, str, val, t;
  path[loc] = '';

  for(var key in obj) {
    if (obj.hasOwnProperty && !obj.hasOwnProperty(key)) continue ;
    if (!isNaN(Number(key))) continue ; // skip array indexes
    if (key === "constructor") continue ;
    if (key === "superclass") continue ;
    if (key === "document") continue ;

    val = obj[key];
    if (key === "SproutCore") key = "SC";
    t   = SC.typeOf(val);
    if (t === SC.T_FUNCTION) {
      if (!val.displayName) { // only name the first time it is encountered
        path[loc] = key ;
        str = path.join('.').replace('.prototype.', '#');
        val.displayName = str;
      }

      // handle constructor-style
      if (val.prototype) {
        path.push("prototype");
        SC.mapDisplayNames(val.prototype, level+1, path, seenHash, seenArray);
        path.pop();
      }

    } else if (t === SC.T_CLASS) {
      path[loc] = key ;
      SC.mapDisplayNames(val, level+1, path, seenHash, seenArray);

    } else if ((key.indexOf('_')!==0) && (t===SC.T_OBJECT || t===SC.T_HASH)) {
      path[loc] = key ;
      SC.mapDisplayNames(val, level+1, path, seenHash, seenArray);
    }
  }

  path.pop();
};


; tiki.script('sproutcore/debug:en/13200b9fa685f043535bda49964adb32feb44530/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/designer', {"packages":{"sproutcore\/runtime":{"scripts":[{"url":"\/static\/sproutcore\/runtime\/en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js","id":"sproutcore\/runtime:en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js"}]},"sproutcore\/foundation":{"stylesheets":[{"url":"\/static\/sproutcore\/foundation\/en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/stylesheet.css","id":"sproutcore\/foundation:en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/stylesheet.css"}],"scripts":[{"url":"\/static\/sproutcore\/foundation\/en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/javascript.js","id":"sproutcore\/foundation:en\/d5356c4dc5eed765f3239fcaaa4b599b3de5c4b0\/javascript.js"}]}},"stylesheets":[{"url":"\/static\/sproutcore\/designer\/en\/3080eb96d6f0ac8d0e9236af215ac0647848d08b\/stylesheet.css","id":"sproutcore\/designer:en\/3080eb96d6f0ac8d0e9236af215ac0647848d08b\/stylesheet.css"}],"depends":["sproutcore\/runtime","sproutcore\/foundation"],"scripts":[{"url":"\/static\/sproutcore\/designer\/en\/3080eb96d6f0ac8d0e9236af215ac0647848d08b\/javascript.js","id":"sproutcore\/designer:en\/3080eb96d6f0ac8d0e9236af215ac0647848d08b\/javascript.js"}]});
tiki.global('sproutcore/designer');

; tiki.script('sproutcore/designer:en/3080eb96d6f0ac8d0e9236af215ac0647848d08b/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/empty_theme', {"scripts":[{"url":"\/static\/sproutcore\/empty_theme\/en\/21c3b7b16d7ef39d60d2651975590828812f3ad9\/javascript.js","id":"sproutcore\/empty_theme:en\/21c3b7b16d7ef39d60d2651975590828812f3ad9\/javascript.js"}]});
tiki.global('sproutcore/empty_theme');

; tiki.script('sproutcore/empty_theme:en/21c3b7b16d7ef39d60d2651975590828812f3ad9/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore', {"packages":{"sproutcore\/desktop":{"stylesheets":[{"url":"\/static\/sproutcore\/desktop\/en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/stylesheet.css","id":"sproutcore\/desktop:en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/stylesheet.css"}],"scripts":[{"url":"\/static\/sproutcore\/desktop\/en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/javascript.js","id":"sproutcore\/desktop:en\/57a37cf8f2abf4dfdcfdbc5bcf79f41605b5ca83\/javascript.js"}]}},"depends":["sproutcore\/desktop"],"scripts":[{"url":"\/static\/sproutcore\/en\/d1f17166d729847603796d2e6df2d6d9cb197021\/javascript.js","id":"sproutcore:en\/d1f17166d729847603796d2e6df2d6d9cb197021\/javascript.js"}]});
tiki.global('sproutcore');

/* >>>>>>>>>> BEGIN source/license.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 */

; tiki.script('sproutcore:en/d1f17166d729847603796d2e6df2d6d9cb197021/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/mini', {"packages":{"sproutcore\/runtime":{"scripts":[{"url":"\/static\/sproutcore\/runtime\/en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js","id":"sproutcore\/runtime:en\/cfe6f8a7557f916515999a065d70bfa71bd13986\/javascript.js"}]},"sproutcore\/datastore":{"scripts":[{"url":"\/static\/sproutcore\/datastore\/en\/334c3630f54926a13d0810d33b3686af21530ee7\/javascript.js","id":"sproutcore\/datastore:en\/334c3630f54926a13d0810d33b3686af21530ee7\/javascript.js"}]}},"depends":["sproutcore\/runtime","sproutcore\/datastore"],"scripts":[{"url":"\/static\/sproutcore\/mini\/en\/33133db781fd6d264936fd2c81d828875be42b87\/javascript.js","id":"sproutcore\/mini:en\/33133db781fd6d264936fd2c81d828875be42b87\/javascript.js"}]});
tiki.global('sproutcore/mini');

; tiki.script('sproutcore/mini:en/33133db781fd6d264936fd2c81d828875be42b87/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/standard_theme', {"packages":{"sproutcore\/empty_theme":{"scripts":[{"url":"\/static\/sproutcore\/empty_theme\/en\/21c3b7b16d7ef39d60d2651975590828812f3ad9\/javascript.js","id":"sproutcore\/empty_theme:en\/21c3b7b16d7ef39d60d2651975590828812f3ad9\/javascript.js"}]}},"stylesheets":[{"url":"\/static\/sproutcore\/standard_theme\/en\/4448cf3c8117a1e50376695dc8ea12138b0f9099\/stylesheet.css","id":"sproutcore\/standard_theme:en\/4448cf3c8117a1e50376695dc8ea12138b0f9099\/stylesheet.css"}],"depends":["sproutcore\/empty_theme"],"scripts":[{"url":"\/static\/sproutcore\/standard_theme\/en\/4448cf3c8117a1e50376695dc8ea12138b0f9099\/javascript.js","id":"sproutcore\/standard_theme:en\/4448cf3c8117a1e50376695dc8ea12138b0f9099\/javascript.js"}]});
tiki.global('sproutcore/standard_theme');

; tiki.script('sproutcore/standard_theme:en/4448cf3c8117a1e50376695dc8ea12138b0f9099/javascript.js');/* >>>>>>>>>> BEGIN package_info.js */
;tiki.register('sproutcore/testing', {"stylesheets":[{"url":"\/static\/sproutcore\/testing\/en\/1e0d6dc675d3d9adb05a01b524dd7fc8fb2ad475\/stylesheet.css","id":"sproutcore\/testing:en\/1e0d6dc675d3d9adb05a01b524dd7fc8fb2ad475\/stylesheet.css"}],"scripts":[{"url":"\/static\/sproutcore\/testing\/en\/1e0d6dc675d3d9adb05a01b524dd7fc8fb2ad475\/javascript.js","id":"sproutcore\/testing:en\/1e0d6dc675d3d9adb05a01b524dd7fc8fb2ad475\/javascript.js"}]});
tiki.global('sproutcore/testing');

; tiki.script('sproutcore/testing:en/1e0d6dc675d3d9adb05a01b524dd7fc8fb2ad475/javascript.js');;tiki.register("bespin",{});
tiki.module("bespin:builtins",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

exports.metadata =
{
    "bespin":
    {
        "provides":
        [
            {
                "ep": "extensionpoint",
                "name": "extensionpoint",
                "description": "Use the 'extensionpoint' extension point to \
define new extension points. You can provide an 'indexOn' property to name a \
property of extensions through which you'd like to be able to easily look up the \
extension. You can also provide a 'register' and 'unregister' properties which are \
pointers to functions that are called whenever a new extension is discovered or \
removed, respectively. 'register' and 'unregister' \
should be used sparingly, because your plugin will be loaded whenever a \
matching plugin is available."
            },
            {
                "ep": "extensionpoint",
                "name": "extensionhandler",
                "description": "extensionhandlers are able to have 'register' \
and 'unregister' pointers to functions that are called with each extension as \
it is discovered or unregistered. Use 'register' sparingly as your plugin will \
be loaded automatically if there is a matching extension (and not just when \
your extension's functionality is required)."
            },
            {
                "ep": "extensionpoint",
                "name": "startup",
                "description": "A function that should be called at startup. This should be used \
sparingly, as these plugins will be eagerly loaded at the beginning. All that's needed for this \
extension point is a pointer to a function that takes no arguments.",
                "register": "plugins#startupHandler"
            },
            {
                "ep": "extensionpoint",
                "name": "factory",
                "description": "Provides a factory for singleton components. Each extension needs to \
provide a name, a pointer and an action. The action can be 'call' (if the pointer refers to \
a function), 'create' (if the pointer refers to an SC.Object), 'new' (if the pointer refers to \
a traditional JS object) or 'value' (if the pointer refers to the object itself that is the \
component).",
                "indexOn": "name"
            },
            {
                "ep": "factory",
                "name": "hub",
                "action": "create",
                "pointer": "util/hub#Hub"
            },
            {
                "ep": "extensionpoint",
                "name": "command",
                "description": "Editor commands/actions. TODO: list parameters here."
            }
        ]
    }
};

});

tiki.module("bespin:editor/mixins/textinput",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;

/**
 * @namespace
 *
 * This mixin delivers text input events similar to those defined in the DOM
 * Level 3 specification. It allows views to support internationalized text
 * input via non-US keyboards, dead keys, and/or IMEs. It also provides support
 * for copy and paste. Currently, an invisible contentEditable div is used, but
 * in the future this module should use DOM 3 TextInput events directly where
 * available.
 *
 * To use this mixin, derive from it and implement the functions (don't have to)
 *   - copy: function() { return "text for clipboard" }
 *   - cut: function() { "Cut some text"; return "text for clipboard"}
 *   - textInserted: function(newInsertedText) { "handle new inserted text"; }
 * Note: Pasted text is provied through the textInserted(pastedText) function.
 *
 * Make sure to call the superclass implementation if you override any of the
 * following functions:
 *   - render(context, firstTime)
 *   - didCreateLayer()
 *   - mouseDown(event)
 *   - willBecomeKeyResponderFrom(responder)
 *   - willLoseKeyResponderTo(responder)
 */
exports.TextInput = {
    _TextInput_composing: false,
    _TextInput_ignore: false,
    _TextInput_textFieldId: undefined,
    _TextInput_textFieldDom: undefined,

    // Keyevents and copy/cut/paste are not the same on Safari and Chrome.
    _isChrome: !!parseFloat(navigator.userAgent.split("Chrome/")[1]),

    // This function doesn't work on WebKit! The textContent comes out empty...
    _TextInput_textFieldChanged: function() {
        if (this._TextInput_composing || this._TextInput_ignore) {
            return;
        }

        var textField = this._TextInput_textFieldDom;
        var text = textField.value;
        // On FF textFieldChanged is called sometimes although nothing changed.
        // -> don't call textInserted() in such a case.
        if (text == '') {
            return;
        }
        textField.value = "";

        this._TextInput_textInserted(text);
    },

    _TextInput_copy: function() {
        var copyData = false;
        if (this.respondsTo('copy')) {
            SC.RunLoop.begin();
            copyData = this.copy();
            SC.RunLoop.end();
        }
        return copyData;
    },

    _TextInput_cut: function() {
        var cutData = false;
        if (this.respondsTo('cut')) {
            SC.RunLoop.begin();
            cutData = this.cut();
            SC.RunLoop.end();
        }
        return cutData;
    },

    _TextInput_textInserted: function(text) {
        if (this.respondsTo('textInserted')) {
            SC.RunLoop.begin();
            this.textInserted(text);
            SC.RunLoop.end();
        }
    },

    _TextInput_setValueAndSelect: function(text) {
        var textField = this._TextInput_textFieldDom;
        textField.value = text;
        textField.select();
    },

    /**
     * Gives focus to the field editor so that input events will be
     * delivered to the view. If you override willBecomeKeyResponderFrom(),
     * you should call this function in your implementation.
     */
    focusTextInput: function() {
        this._TextInput_textFieldDom.focus();
    },

    /**
     * Removes focus from the invisible text input so that input events are no
     * longer delivered to this view. If you override willLoseKeyResponderTo(),
     * you should call this function in your implementation.
     */
    unfocusTextInput: function() {
        this._TextInput_textFieldDom.blur();
    },

    /**
     * If you override this method, you should call that function as well.
     */
    render: function(context, firstTime) {
        arguments.callee.base.apply(this, arguments);

        if (firstTime) {
            // Add a textarea to handle focus, copy & paste and key input
            // within the current view and hide it under the view.
            var frame = this.get('frame');
            var textFieldContext = context.begin("textarea");
            this._TextInput_textFieldId = SC.guidFor(textFieldContext);
            textFieldContext.id(this._TextInput_textFieldId);
            textFieldContext.attr("style", ("position: absolute; " +
                "z-index: -99999; top: 0px; left: 0px; width: %@px; " +
                "height: %@px").fmt(frame.width, frame.height));
            textFieldContext.end();
        }
    },

    /**
     * Attaches notification listeners to the text field so that your view will
     * be notified of events. If you override this method, you should call
     * that function as well.
     */
    didCreateLayer: function() {
        arguments.callee.base.apply(this, arguments);

        var textField = this.$("#" + this._TextInput_textFieldId)[0];
        this._TextInput_textFieldDom = textField;
        var thisTextInput = this;

        // No way that I can see around this ugly browser sniffing, without
        // more complicated hacks. No browsers have a complete enough
        // implementation of DOM 3 events at the current time (12/2009). --pcw
        if (SC.browser.safari) {    // Chrome too
            // On Chrome the compositionend event is fired as well as the
            // textInput event, but only one of them has to be handled.
            if (!this._isChrome) {
                textField.addEventListener('compositionend', function(evt) {
                    thisTextInput._TextInput_textInserted(evt.data);
                }, false);
            }
            textField.addEventListener('textInput', function(evt) {
                thisTextInput._TextInput_textInserted(evt.data);
            }, false);
            textField.addEventListener('paste', function(evt) {
                thisTextInput._TextInput_textInserted(evt.clipboardData.
                    getData('text/plain'));
            }, false);
        } else {
            var textFieldChangedFn = function(evt) {
                thisTextInput._TextInput_textFieldChanged();
            };
            textField.addEventListener('keypress', textFieldChangedFn, false);
            textField.addEventListener('keyup', textFieldChangedFn, false);

            textField.addEventListener('compositionstart', function(evt) {
                thisTextInput._TextInput_composing = true;
            }, false);
            textField.addEventListener('compositionend', function(evt) {
                thisTextInput._TextInput_composing = false;
                thisTextInput._TextInput_textFieldChanged();
            }, false);

            textField.addEventListener('paste', function(evt) {
                // FIXME: This is ugly and could result in extraneous text
                // being included as part of the text if extra DOMNodeInserted
                // or DOMCharacterDataModified events happen to be in the queue
                // when this function runs. But until Fx supports TextInput
                // events, there's nothing better we can do.

                // Waits till the paste content is pasted to the textarea.
                // Sometimes a delay of 0 is too short for Fx. In such a case
                // the keyUp events occur a little bit later and the pasted
                // content is detected there.
                thisTextInput._TextInput_setValueAndSelect('');
                window.setTimeout(function() {
                    thisTextInput._TextInput_textFieldChanged();
                }, 0);
            }, false);
        }

        // Here comes the code for copy and cut...

        // This is the basic copy and cut function. Depending on the
        // OS and browser this function needs to be extended.
        var copyCutBaseFn = function(evt) {
            // Get the data that should be copied/cutted.
            var copyCutData = evt.type.indexOf('copy') != -1 ?
                            thisTextInput._TextInput_copy() :
                            thisTextInput._TextInput_cut();
            // Set the textField's value equal to the copyCutData.
            // After this function is called, the real copy or cut
            // event takes place and the selected text in the
            // textField is pushed to the OS's clipboard.
            thisTextInput._TextInput_setValueAndSelect(copyCutData);
        };

        // For all browsers that are not Safari running on Mac.
        if (!(SC.browser.isSafari && !this._isChrome && SC.browser.isMac)) {
            var copyCutMozillaFn = false;
            if (SC.browser.isMozilla) {
                // If the browser is Mozilla like, the copyCut function has to
                // be extended.
                copyCutMozillaFn = function(evt) {
                    // Call the basic copyCut function.
                    copyCutBaseFn(evt);

                    // On Firefox you have to ignore the textarea's content
                    // until it's copied / cutted. Otherwise the value of the
                    // textarea is inserted again.
                    if (SC.browser.isMozilla) {
                        thisTextInput._TextInput_ignore = true;
                        window.setTimeout(function() {
                            thisTextInput._TextInput_setValueAndSelect('');
                            thisTextInput._TextInput_ignore = false;
                        }, 0);
                    }
                };
            }
            textField.addEventListener('copy', copyCutMozillaFn ||
                copyCutBaseFn, false);
            textField.addEventListener('cut',  copyCutMozillaFn ||
                copyCutBaseFn, false);
         } else {
            // For Safari on Mac (only!) the copy and cut event only occurs if
            // you have some text selected. Fortunately, the beforecopy and
            // beforecut event occurs before the copy or cut event does so we
            // can put the to be copied or cutted text in the textarea.

            // Also, the cut event is fired twice. If it's fired twice within a
            // certain time period, the second call will be skipped.
            var lastCutCall = new Date().getTime();
            var copyCutSafariMacFn = function(evt) {
                var doCut = evt.type.indexOf('cut') != -1;
                if (doCut && new Date().getTime() - lastCutCall < 10) {
                    return;
                }

                // Call the basic copyCut function.
                copyCutBaseFn(evt);

                if (doCut) {
                    lastCutCall = new Date().getTime();
                }
            };

            textField.addEventListener('beforecopy', copyCutSafariMacFn,
                false);
            textField.addEventListener('beforecut',  copyCutSafariMacFn,
                false);
        }

        // Clicking the address bar causes a blur, but SproutCore won't notice
        // unless we tell it explicitly.
        textField.addEventListener('blur', function(evt) {
            thisTextInput.resignFirstResponder();
        }, false);
    },

    /**
     * The default implementation of this event sets the focus. If you
     * override this method, you should call that function as well.
     */
    mouseDown: function(evt) {
        arguments.callee.base.apply(this, arguments);
        this.get('pane').makeFirstResponder(this);
    },

    /**
     * The default implementation of this event calls focusTextarea(). If you
     * override this method, you should call that function as well.
     */
    willBecomeKeyResponderFrom: function(responder) {
        arguments.callee.base.apply(this, arguments);
        this.focusTextInput();
    },

    /**
     * The default implementation of this event calls unfocusTextarea(). If you
     * override this method, you should call that function as well.
     */
    willLoseKeyResponderTo: function(responder) {
        arguments.callee.base.apply(this, arguments);
        this.unfocusTextInput();
    }
};


});

tiki.module("bespin:globals",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require("sproutcore/runtime").SC;

/*
* Installs ES5 and SproutCore monkeypatches as needed.
*/
var installGlobals = function() {
    /**
     * Array detector.
     * Firefox 3.5 and Safari 4 have this already. Chrome 4 however ...
     * Note to Dojo - your isArray is still broken: instanceof doesn't work with
     * Arrays taken from a different frame/window.
     */
    if (!Array.isArray) {
        Array.isArray = function(data) {
            return (data && Object.prototype.toString.call(data) == "[object Array]");
        };
    }

    if (!Function.prototype.bind) {
        // From Narwhal
        Function.prototype.bind = function () {
            var args = Array.prototype.slice.call(arguments);
            var self = this;
            var bound = function () {
                return self.call.apply(
                    self,
                    args.concat(
                        Array.prototype.slice.call(arguments)
                    )
                );
            };
            bound.name = this.name;
            bound.displayName = this.displayName;
            bound.length = this.length;
            bound.unbound = self;
            return bound;
        };
    }


    /**
     * Hack to allow us to call this.sc_super() in place of the global sc_super();
     */
    SC.Object.prototype.sc_super = function super_name() {
        super_name.caller.base.apply(this, super_name.caller.arguments);
    };

    SC.objectForPropertyPathOriginal = SC.objectForPropertyPath;

    SC.objectForPropertyPath = function(path, root, stopAt) {
        stopAt = (stopAt == undefined) ? path.length : stopAt;
        var hashed = path.split("#");
        if (hashed.length == 1) {
            return SC.objectForPropertyPathOriginal(path, root, stopAt);
        }
        var module = require(hashed[0]);
        if (module === undefined) {
            return undefined;
        }
        stopAt = stopAt - hashed[0].length;
        return SC.objectForPropertyPathOriginal(hashed[1], module, stopAt);
    };
};

installGlobals();

});

tiki.module("bespin:plugins",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require("sproutcore/runtime").SC;
require("globals");

var builtins = require("builtins");
var r = require;

var object_keys = Object.keys;
if (!object_keys) {
    object_keys = function(obj) {
        var k, ret = [];
        for (k in obj) {
            if (obj.hasOwnProperty(k)) {
                ret.push(k);
            }
        }
        return ret;
    };
}

var _splitPointer = function(pluginName, pointer) {
    if (!pointer) {
        return undefined;
    }
    
    var parts = pointer.split("#");
    var modName;
    
    // this allows syntax like #foo
    // which is equivalent to PluginName:index#foo
    if (parts[0]) {
        modName = pluginName + ":" + parts[0];
    } else {
        modName = pluginName;
    }
    
    return {
        modName: modName,
        objName: parts[1]
    };
};

var _retrieveObject = function(pointerObj) {
    var module = r(pointerObj.modName);
    if (pointerObj.objName) {
        return module[pointerObj.objName];
    }
    return module;
};

exports.Extension = SC.Object.extend({
    _getPointer: function(property) {
        property = property || "pointer";
        return _splitPointer(this._pluginName, this.get(property));
    },
    
    init: function() {
        this._observers = [];
    },
    
    load: function(callback, property) {
        var pointer = this._getPointer(property);
        
        if (!pointer) {
            console.error("Extension cannot be loaded because it has no poitner");
            console.log(this);
            return;
        }
        
        var self = this;

        tiki.async(this._pluginName).then(function() {
            SC.run(function() {
                var foo = self;
                var module = r(pointer.modName);
                if (callback) {
                    if (pointer.objName) {
                        callback(module[pointer.objName]);
                    } else {
                        callback(module);
                    }
                }
            });
        });
    },
    
    /*
    * Loads this extension and passes the result to the callback.
    * Any time this extension changes, the callback is called with
    * the new value. Note that if this extension goes away, the
    * callback will be called with undefined.
    * 
    * observingPlugin is required, because if that plugin is
    * torn down, all of its observing callbacks need to be torn down
    * as well.
    */ 
    observe: function(observingPlugin, callback, property) {
        this._observers.push({plugin: observingPlugin, 
            callback: callback, property: property});
        this.load(callback, property);
    },
    
    _getLoaded: function(property) {
        var pointer = this._getPointer(property);
        return _retrieveObject(pointer);
    }
});

exports.ExtensionPoint = SC.Object.extend({
    init: function() {
        this.extensions = [];
        this.handlers = [];
    },

    addExtension: function(extension) {
        this.extensions.push(extension);
        this.register(extension);
    },

    /**
     * If we are keeping an index (an indexOn property is set on the
     * extension point), you can look up an extension by key.
     */
    getByKey: function(key) {
        var indexOn = this.get("indexOn");

        if (!indexOn) {
            return undefined;
        }

        for (var i = 0; i < this.extensions.length; i++) {
            if (this.extensions[i][indexOn] == key) {
                return this.extensions[i];
            }
        }
        return undefined;
    },

    register: function(extension) {
        this.handlers.forEach(function(handler) {
            if (handler.register) {
                handler.load(function(register) {
                    register(extension);
                }, "register");
            }
        });
    },

    unregister: function(extension) {
        this.handlers.forEach(function(handler) {
            if (handler.unregister) {
                handler.load(function(unregister) {
                    unregister(extension);
                }, "unregister");
            }
        });
    }
});

exports.Plugin = SC.Object.extend({
    register: function() {
        var provides = this.provides;
        self = this;
        this.provides.forEach(function(extension) {
            var ep = self.get("catalog").getExtensionPoint(extension.ep);
            ep.register(extension);
        });
    },

    unregister: function() {
        var provides = this.provides;
        self = this;
        this.provides.forEach(function(extension) {
            var ep = self.get("catalog").getExtensionPoint(extension.ep);
            ep.unregister(extension);
        });
    },
    
    _getObservers: function() {
        var result = {};
        this.provides.forEach(function(extension) {
            console.log("ep: ", extension.ep);
            console.log(extension._observers);
            result[extension.ep] = extension._observers;
        });
        return result;
    },
    
    /*
    * Figure out which plugins depend on a given plugin. This
    * will allow the reload behavior to unregister/reregister
    * all of the plugins that depend on the one being reloaded.
    */
    _findDependents: function(pluginList, dependents) {
        var pluginName = this.name;
        var self = this;
        pluginList.forEach(function(testPluginName) {
            if (testPluginName == pluginName) {
                return;
            }
            var plugin = self.catalog.plugins[testPluginName];
            if (plugin && plugin.depends) {
                plugin.depends.forEach(function(dependName) {
                    if (dependName == pluginName && !dependents[testPluginName]) {
                        dependents[testPluginName] = true;
                        plugin._findDependents(pluginList, dependents);
                    }
                });
            }
        });
    },
    
    /*
    * reloads the plugin and reinitializes all
    * dependent plugins
    */
    reload: function(callback) {
        // All reloadable plugins will have a reloadURL
        if (!this.get("reloadURL")) {
            return;
        }
        
        var pluginName = this.name;
        
        var reloadPointer = this.get("reloadPointer");
        if (reloadPointer) {
            var pointer = _splitPointer(pluginName, reloadPointer);
            var func = _retrieveObject(pointer);
            if (func) {
                func();
            } else {
                console.error("Reload function could not be loaded. Aborting reload.");
                return;
            }
        }
        
        // find all of the dependents recursively so that
        // they can all be unregisterd
        var dependents = {};
        
        var self = this;
        
        var pluginList = object_keys(this.catalog.plugins);
        
        this._findDependents(pluginList, dependents);
        
        // notify everyone that this plugin is going away
        this.unregister();
        
        for (var dependName in dependents) {
            this.catalog.plugins[dependName].unregister();
        }
        
        // remove all traces of the plugin
        
        var nameMatch = new RegExp("^" + pluginName + ":");
        
        _removeFromList(nameMatch, tiki.scripts);
        _removeFromList(nameMatch, tiki.modules,
            function(module) {
                delete tiki._factories[module];
            });
        _removeFromList(nameMatch, tiki.stylesheets);
        _removeFromList(new RegExp("^" + pluginName + "$"), tiki.packages);
        
        var promises = tiki._promises;
        
        delete promises.catalog[pluginName];
        delete promises.loads[pluginName];
        _removeFromObject(nameMatch, promises.modules);
        _removeFromObject(nameMatch, promises.scripts);
        _removeFromObject(nameMatch, promises.stylesheets);
        
        delete tiki._catalog[pluginName];
        
        // clear the sandbox of modules from all of the dependent plugins
        var fullModList = [];
        var sandbox = tiki.sandbox;
        
        var i = sandbox.modules.length;
        var dependRegexes = [];
        for (dependName in dependents) {
            dependRegexes.push(new RegExp("^" + dependName + ":"));
        }
        
        while (--i >= 0) {
            var item = sandbox.modules[i];
            if (nameMatch.exec(item)) {
                fullModList.push(item);
            } else {
                var j = dependRegexes.length;
                while (--j >= 0) {
                    if (dependRegexes[j].exec(item)) {
                        fullModList.push(item);
                        break;
                    }
                }
            }
        }
        
        // make a private Tiki call that clears these
        // modules from the module cache in the sandbox.
        sandbox.clear.apply(sandbox, fullModList);
        
        // reload the plugin metadata
        this.catalog.loadMetadata(this.reloadURL,
            function() {
                // actually load the plugin, so that it's ready
                // for any dependent plugins
                tiki.async(pluginName).then(function() {
                    // reregister all of the dependent plugins
                    for (dependName in dependents) {
                        self.catalog.plugins[dependName].register();
                    }
                    
                    if (callback) {
                        // at long last, reloading is done.
                        callback();
                    }
                });
            }
        );
    }
});

exports.Catalog = SC.Object.extend({
    init: function() {
        this.points = {};
        this.plugins = {};

        // set up the "extensionpoint" extension point.
        // it indexes on name.
        var ep = this.getExtensionPoint("extensionpoint");
        ep.set("indexOn", "name");
        this.load(builtins.metadata);
    },

    /**
     * Retrieve a registered singleton. Returns undefined
     * if that factory is not registered.
     */
    getObject: function(name) {
        var ext = this.getExtensionByKey("factory", name);
        if (ext === undefined) {
            return undefined;
        }

        var obj = ext.get("instance");
        if (obj) {
            return obj;
        }

        var exported = ext._getLoaded();
        var action = ext.action;

        if (action == "call") {
            obj = exported();
        } else if (action == "create") {
            obj = exported.create();
        } else if (action == "new") {
            obj = new exported();
        } else if (action == "value") {
            obj = exported;
        } else {
            throw "Create action must be call|create|new|value. " +
                    "Found" + action;
        }

        ext.set("instance", obj);
        return obj;
    },

    /** Retrieve an extension point object by name. */
    getExtensionPoint: function(name) {
        if (this.points[name] === undefined) {
            this.points[name] = exports.ExtensionPoint.create({
                name: name,
                catalog: this
            });
        }
        return this.points[name];
    },

    /**
     * Retrieve the list of extensions for the named extension point.
     * If none are defined, this will return an empty array.
     */
    getExtensions: function(name) {
        var ep = this.getExtensionPoint(name);
        if (ep === undefined) {
            return [];
        }
        return ep.extensions;
    },

    /**
     * Look up an extension in an indexed extension point by the given key. If
     * the extension point or the key are unknown, undefined will be returned.
     */
    getExtensionByKey: function(name, key) {
        var ep = this.getExtensionPoint(name);
        if (ep === undefined) {
            return undefined;
        }

        return ep.getByKey(key);
    },

    _registerExtensionPoint: function(extension) {
        var ep = this.getExtensionPoint(extension.name);
        ep.handlers.push(extension);
        if (extension.indexOn) {
            ep.set("indexOn", extension.indexOn);
        }
    },

    _registerExtensionHandler: function(extension) {
        var ep = this.getExtensionPoint(extension.name);
        ep.handlers.push(extension);
        if (extension.register) {
            extension.load(function(register) {
                if (!register) {
                    throw extension.name + " is not ready";
                }
                ep.extensions.forEach(function(ext) {
                    register(ext);
                });
            }, "register");
        }

    },

    load: function(metadata) {
        for (var name in metadata) {
            var md = metadata[name];
            md.catalog = this;
            if (md.provides) {
                var provides = md.provides;
                for (var i = 0; i < provides.length; i++) {
                    var extension = exports.Extension.create(provides[i]);
                    extension._pluginName = name;
                    provides[i] = extension;
                    var epname = extension.ep;
                    if (epname == "extensionpoint") {
                        this._registerExtensionPoint(extension);
                    } else if (epname == "extensionhandler") {
                        this._registerExtensionHandler(extension);
                    }
                    var ep = this.getExtensionPoint(extension.ep);
                    ep.addExtension(extension);
                }
            } else {
                md.provides = [];
            }
            md.name = name;
            var plugin = exports.Plugin.create(md);
            this.plugins[name] = plugin;
        }
    },
    
    /*
    * Loads the named plugin, calling the provided callback
    * when the plugin is loaded. This function is a convenience
    * for unusual situations and debugging only. Generally,
    * you should load plugins by calling load() on an Extesnion
    * object.
    */
    loadPlugin: function(pluginName, callback) {
        var p = tiki.async(pluginName);
        if (callback) {
            p.then(callback, function(reason) {
                console.error("Plugin loading canceled: ", reason);
            });
        }
    },

    loadMetadata: function(url, callback) {
        SC.Request.create({ address: url }).notify(0, this,
            this._metadataFinishedLoading, { callback: callback }).send("");
    },
    
    /*
    * for the given plugin, get the first part of the URL required to
    * get at that plugin's resources (images, etc.).
    */
    getResourceURL: function(pluginName) {
        var plugin = this.plugins[pluginName];
        if (plugin == undefined) {
            return undefined;
        }
        return plugin.resourceURL;
    },

    _metadataFinishedLoading: function(response, params) {
        if (!response.isError) {
            var body = response.body();
            var data = JSON.parse(body);
            for (var pluginName in data) {
                if (data[pluginName].errors) {
                    console.error("Plugin ", pluginName, " has errors:");
                    data[pluginName].errors.forEach(function(error) {
                        console.error(error);
                    });
                    delete data[pluginName];
                    continue;
                }
                tiki.register(pluginName, data[pluginName]);
            }
            this.load(data);
        }
        if (params.callback) {
            params.callback(this, response);
        }
    }
});

var _removeFromList = function(regex, array, matchFunc) {
    var i = 0;
    while (i < array.length) {
        if (regex.exec(array[i])) {
            var item = array.splice(i, 1);
            if (matchFunc) {
                matchFunc(item);
            }
            continue;
        }
        i++;
    }
};

var _removeFromObject = function(regex, obj) {
    var keys = object_keys(obj);
    var i = keys.length;
    while (--i > 0) {
        if (regex.exec(keys[i])) {
            delete obj[keys[i]];
        }
    }
};
// reregister all of the dependent plugins

exports.catalog = exports.Catalog.create();

exports.startupHandler = function(ep) {
    ep.load(function(func) {
        func();
    });
};

});

tiki.module("bespin:theme",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require("sproutcore/runtime").SC;

/**
 * Coffee Theme
 * The editor can be styled with Themes. This will become CSS soon, but for now
 * is JSON
 */
exports.coffee = {
    backgroundStyle: "#2A211C",
    gutterStyle: "#4c4a41",
    lineNumberColor: "#e5c138",
    lineNumberFont: "10pt Monaco, Lucida Console, monospace",
    lineMarkerErrorColor: "#CC4444",
    lineMarkerWarningColor: "#B8860B",
    lineMarkerMessageColor: "green",
    zebraStripeColor: "#2A211C",
    highlightCurrentLineColor: "#3a312b",
    editorTextFont: "10pt Monaco, Lucida Console, monospace",
    editorTextColor: "rgb(230, 230, 230)",
    editorSelectedTextColor: "rgb(240, 240, 240)",
    editorSelectedTextBackground: "#526DA5",
    cursorStyle: "#879aff",
    cursorType: "ibeam",       // one of "underline" or "ibeam"
    unfocusedCursorStrokeStyle: "#FF0033",
    unfocusedCursorFillStyle: "#73171E",
    partialNibStyle: "rgba(100, 100, 100, 0.3)",
    partialNibArrowStyle: "rgba(255, 255, 255, 0.3)",
    partialNibStrokeStyle: "rgba(150, 150, 150, 0.3)",
    fullNibStyle: "rgb(100, 100, 100)",
    fullNibArrowStyle: "rgb(255, 255, 255)",
    fullNibStrokeStyle: "rgb(150, 150, 150)",
    scrollTrackFillStyle: "rgba(50, 50, 50, 0.8)",
    scrollTrackStrokeStyle: "rgb(150, 150, 150)",
    scrollBarFillStyle: "rgba(0, 0, 0, %a)",
    scrollBarFillGradientTopStart: "rgba(90, 90, 90, %a)",
    scrollBarFillGradientTopStop: "rgba(40, 40, 40, %a)",
    scrollBarFillGradientBottomStart: "rgba(22, 22, 22, %a)",
    scrollBarFillGradientBottomStop: "rgba(44, 44, 44, %a)",
    tabSpace: "#392A25",
    searchHighlight: "#B55C00",
    searchHighlightSelected: "#FF9A00",

    // syntax definitions
    plain: "#bdae9d",
    keyword: "#42a8ed",
    string: "#039a0a",
    comment: "#666666",
    'c-comment': "#666666",
    punctuation: "#888888",
    attribute: "#BF9464",
    test: "rgb(255,0,0)",
    cdata: "#bdae9d",
    "attribute-value": "#039a0a",
    tag: "#46a8ed",
    color: "#c4646b",
    "tag-name": "#46a8ed",
    value: "#039a0a",
    important: "#990000",
    sizes: "#990000",
    cssclass: "#BF9464",
    cssid: "#46a8ed",

    // Codemirror additions (TODO: better color choice)

    atom: "#aa4444",
    variable: "#00cccc",
    variabledef: "#4422cc",
    localvariable: "#cc2277",
    property: "#66bb33",
    operator: "#88bbff",
    error: "#FF0000",

    // XML and HTML
    processing: "#999999",
    entity: "#AA2222",
    text: "#00BB00",

    // PHP
    "compile-time-constant": "#776088",
    "predefined-constant": "#33CC33",
    "reserved-language-construct": "#00FF00",
    "predefined-function": "#22FF22",
    "predefined-class": "#22FF22",

    // Python
    literal: "#DD4411",
    identifier: "#22FF22",
    func: "#2200FF",
    type: "#8822FF",
    decorator: "#2222FF"
};

/** Coffee Zebra Theme */
exports.coffeezebra = {
};

SC.mixin(exports.coffeezebra, exports.coffee);
exports.coffeezebra.zebraStripeColor = '#FFFFFF';

/** Setup the default */
// themes['default'] = exports.coffee;
exports['default'] = exports.coffee;

});

tiki.module("bespin:util/cookie",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * Adds escape sequences for special characters in regular expressions
 * @param {String} str a String with special characters to be left unescaped
 */
var escapeString = function(str, except){
    return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(ch){
        if(except && except.indexOf(ch) != -1){
            return ch;
        }
        return "\\" + ch;
    });
};

/**
 * Get a cookie value by name
 * @param {String} name The cookie value to retrieve
 * @return The value, or undefined if the cookie was not found
 */
exports.get = function(name) {
    var matcher = new RegExp("(?:^|; )" + escapeString(name) + "=([^;]*)");
    var matches = document.cookie.match(matcher);
    return matches ? decodeURIComponent(matches[1]) : undefined;
};

/**
 * Set a cookie value
 * @param {String} name The cookie value to alter
 * @param {String} value The new value for the cookie
 * @param {Object} props (Optional) cookie properties. One of:<ul>
 * <li>expires: Date|String|Number|null If a number, the number of days from
 * today at which the cookie will expire. If a date, the date past which the
 * cookie will expire. If expires is in the past, the cookie will be deleted.
 * If expires is omitted or is 0, the cookie will expire either directly (ff3)
 * or when the browser closes
 * <li>path: String|null The path to use for the cookie.
 * <li>domain: String|null The domain to use for the cookie.
 * <li>secure: Boolean|null Whether to only send the cookie on secure connections
 * </ul>
 */
exports.set = function(name, value, props) {
    props = props || {};

    if (typeof props.expires == "number") {
        var date = new Date();
        date.setTime(date.getTime() + props.expires * 24 * 60 * 60 * 1000);
        props.expires = date;
    }
    if (props.expires && props.expires.toUTCString) {
        props.expires = props.expires.toUTCString();
    }

    value = encodeURIComponent(value);
    var updatedCookie = name + "=" + value, propName;
    for (propName in props) {
        updatedCookie += "; " + propName;
        var propValue = props[propName];
        if (propValue !== true) {
            updatedCookie += "=" + propValue;
        }
    }

    document.cookie = updatedCookie;
};

/**
 * Remove a cookie by name. Depending on the browser, the cookie will either
 * be deleted directly or at browser close.
 * @param {String} name The cookie value to retrieve
 */
exports.remove = function(name) {
    exports.set(name, "", { expires: -1 });
};

/**
 * Use to determine if the current browser supports cookies or not.
 * @return Returns true if user allows cookies, false otherwise
 */
exports.isSupported = function() {
    if (!("cookieEnabled" in navigator)) {
        exports.set("__djCookieTest__", "CookiesAllowed");
        navigator.cookieEnabled = exports.get("__djCookieTest__") == "CookiesAllowed";
        if (navigator.cookieEnabled) {
            exports.remove("__djCookieTest__");
        }
    }
    return navigator.cookieEnabled;
};

});

tiki.module("bespin:util/path",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * Take the given arguments and combine them with one path separator:
 * <pre>
 * combine("foo", "bar") -&gt; foo/bar
 * combine(" foo/", "/bar  ") -&gt; foo/bar
 * </pre>
 */
exports.combine = function() {
    // clone to a true array
    var args = Array.prototype.slice.call(arguments);

    var path = args.join('/');
    path = path.replace(/\/\/+/g, '/');
    path = path.replace(/^\s+|\s+$/g, '');
    return path;
};

/**
 * Given a <code>path</code> return the directory
 * <li>directory("/path/to/directory/file.txt") -&gt; /path/to/directory/
 * <li>directory("/path/to/directory/") -&gt; /path/to/directory/
 * <li>directory("foo.txt") -&gt; ""
 */
exports.directory = function(path) {
    var dirs = path.split('/');
    if (dirs.length == 1) {
        // no directory so return blank
        return "";
    } else if ((dirs.length == 2) && dirs[dirs.length -1] == "") {
        // a complete directory so return it
        return path;
    } else {
        return dirs.slice(0, dirs.length - 1).join('/');
    }
};

/**
 * Given a <code>path</code> make sure that it returns as a directory
 * (As in, ends with a '/')
 * <pre>
 * makeDirectory("/path/to/directory") -&gt; /path/to/directory/
 * makeDirectory("/path/to/directory/") -&gt; /path/to/directory/
 * </pre>
 */
exports.makeDirectory = function(path) {
    if (!/\/$/.test(path)){path += '/';}
    return path;
};

/**
 * Take the given arguments and combine them with one path separator and
 * then make sure that you end up with a directory
 * <pre>
 * combine("foo", "bar") -&gt; foo/bar/
 * combine(" foo/", "/bar  ") -&gt; foo/bar/
 * </pre>
 */
exports.combineAsDirectory = function() {
    return this.makeDirectory(this.combine.apply(this, arguments));
};

/**
 * This function doubles down and calls <code>combine</code> and then
 * escapes the output
 */
exports.escape = function() {
    return escape(this.combine.apply(this, arguments));
};

/**
 *
 */
exports.trimLeadingSlash = function(path) {
    if (path.indexOf('/') == 0) {
        path = path.substring(1, path.length);
    }
    return path;
},

/**
 * This function returns a file type based on the extension
 * (foo.html -&gt; html)
 */
exports.fileType = function(path) {
    if (path.indexOf('.') >= 0) {
        var split = path.split('.');
        if (split.length > 1) {
            return split[split.length - 1];
        }
    }
};

});

tiki.module("bespin:util/scratchcanvas",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;

/**
 * @class
 *
 * A invisible singleton canvas on the page, useful whenever a canvas context
 * is needed (e.g. for computing text sizes), but an actual canvas isn't handy
 * at the moment.
 */
var ScratchCanvas = SC.Object.extend({
    getContext: function() {
        return document.getElementById('bespin-scratch-canvas').
            getContext('2d');
    },

    init: function() {
        // It's possible that another ScratchCanvas instance in another sandbox
        // exists on the page. If so, we assume they're compatible, and use
        // that one.
        if (!SC.none(document.getElementById('bespin-scratch-canvas'))) {
            return;
        }

        var element = document.createElement('canvas');
        element.id = 'bespin-scratch-canvas';
        element.width = 400;
        element.height = 300;
        element.style.position = 'absolute';
        element.style.top = "-10000px";
        element.style.left = "-10000px";
        document.body.appendChild(element);
    },

    /**
     * Returns the width in pixels of the given string ("M", by default) in the
     * given font.
     */
    measureStringWidth: function(font, str) {
        if (SC.none(str)) {
            str = "M";
        }

        var context = this.getContext();
        context.save();
        context.font = font;
        var width = context.measureText(str).width;
        context.restore();
        return width;
    }
});

var singleton = null;

/**
 * Returns the instance of the scratch canvas on the page, creating it if
 * necessary.
 */
exports.get = function() {
    if (singleton === null) {
        singleton = ScratchCanvas.create();
    }
    return singleton;
}


});

tiki.module("bespin:util/util",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * Create an object representing a de-serialized query section of a URL.
 * Query keys with multiple values are returned in an array.
 * <p>Example: The input "foo=bar&foo=baz&thinger=%20spaces%20=blah&zonk=blarg&"
 * Produces the output object:
 * <pre>{
 *   foo: [ "bar", "baz" ],
 *   thinger: " spaces =blah",
 *   zonk: "blarg"
 * }
 * </pre>
 * <p>Note that spaces and other urlencoded entities are correctly handled
 * @see dojo.queryToObject()
 * While dojo.queryToObject() is mainly for URL query strings, this version
 * allows to specify a separator character
 */
exports.queryToObject = function(str, seperator) {
    var ret = {};
    var qp = str.split(seperator || "&");
    var dec = decodeURIComponent;
    qp.forEach(function(item) {
        if (item.length) {
            var parts = item.split("=");
            var name = dec(parts.shift());
            var val = dec(parts.join("="));
            if (exports.isString(ret[name])){
                ret[name] = [ret[name]];
            }
            if (Array.isArray(ret[name])){
                ret[name].push(val);
            } else {
                ret[name] = val;
            }
        }
    });
    return ret;
};

/**
 * Takes a name/value mapping object and returns a string representing a
 * URL-encoded version of that object for use in a GET request
 * <p>For example, given the input:
 * <code>{ blah: "blah", multi: [ "thud", "thonk" ] }</code>
 * The following string would be returned:
 * <code>"blah=blah&multi=thud&multi=thonk"</code>
 * @param map {Object} The object to convert
 * @return {string} A URL-encoded version of the input
 */
exports.objectToQuery = function(map) {
    // FIXME: need to implement encodeAscii!!
    var enc = encodeURIComponent;
    var pairs = [];
    var backstop = {};
    for (var name in map) {
        var value = map[name];
        if (value != backstop[name]) {
            var assign = enc(name) + "=";
            if (_d.isArray(value)) {
                for (var i = 0; i < value.length; i++) {
                    pairs.push(assign + enc(value[i]));
                }
            } else {
                pairs.push(assign + enc(value));
            }
        }
    }
    return pairs.join("&");
};

/**
 * Holds the count to keep a unique value for setTimeout
 * @private See rateLimit()
 */
var nextRateLimitId = 0;

/**
 * Holds the timeouts so they can be cleared later
 * @private See rateLimit()
 */
var rateLimitTimeouts = {};

/**
 * Delay calling some function to check that it's not called again inside a
 * maxRate. The real function is called after maxRate ms unless the return
 * value of this function is called before, in which case the clock is restarted
 */
exports.rateLimit = function(maxRate, scope, func) {
    if (maxRate) {
        var rateLimitId = nextRateLimitId++;

        return function() {
            if (rateLimitTimeouts[rateLimitId]) {
                clearTimeout(rateLimitTimeouts[rateLimitId]);
            }

            rateLimitTimeouts[rateLimitId] = setTimeout(function() {
                func.apply(scope, arguments);
                delete rateLimitTimeouts[rateLimitId];
            }, maxRate);
        };
    }
};

/**
 * Return true if it is a String
 */
exports.isString = function(it) {
    return (typeof it == "string" || it instanceof String);
};

/**
 * Hack copied from dojo.
 */
exports.isObject = function(it) {
    return it !== undefined &&
        (it === null || typeof it == "object" ||
        Array.isArray(it) || exports.isFunction(it));
};

/**
 * Is the passed object a function?
 * From dojo.isFunction()
 */
exports.isFunction = (function() {
    var _isFunction = function(it) {
        var t = typeof it; // must evaluate separately due to bizarre Opera bug. See #8937
        //Firefox thinks object HTML element is a function, so test for nodeType.
        return it && (t == "function" || it instanceof Function) && !it.nodeType; // Boolean
    };

    return exports.isSafari ?
        // only slow this down w/ gratuitious casting in Safari (not WebKit)
        function(/*anything*/ it) {
            if (typeof it == "function" && it == "[object NodeList]") {
                return false;
            }
            return _isFunction(it); // Boolean
        } : _isFunction;
})();

/**
 * A la Prototype endsWith(). Takes a regex excluding the '$' end marker
 */
exports.endsWith = function(str, end) {
    return str.match(new RegExp(end + "$"));
};

/**
 * A la Prototype include().
 */
exports.include = function(array, item) {
    return array.indexOf(item) > -1;
};

/**
 * Like include, but useful when you're checking for a specific
 * property on each object in the list...
 *
 * Returns null if the item is not in the list, otherwise
 * returns the index of the item.
 */
exports.indexOfProperty = function(array, propertyName, item) {
    for (var i = 0; i < array.length; i++) {
        if (array[i][propertyName] == item) {
            return i;
        }
    }
    return null;
};

/**
 * A la Prototype last().
 */
exports.last = function(array) {
    if (Array.isArray(array)) {
        return array[array.length - 1];
    }
};

/**
 * Knock off any undefined items from the end of an array
 */
exports.shrinkArray = function(array) {
    var newArray = [];

    var stillAtBeginning = true;
    array.reverse().forEach(function(item) {
        if (stillAtBeginning && item === undefined) {
            return;
        }

        stillAtBeginning = false;

        newArray.push(item);
    });

    return newArray.reverse();
};

/**
 * Create an array
 * @param number The size of the new array to create
 * @param character The item to put in the array, defaults to ' '
 */
exports.makeArray = function(number, character) {
    if (number < 1) {
        return []; // give us a normal number please!
    }
    if (!character){character = ' ';}

    var newArray = [];
    for (var i = 0; i < number; i++) {
        newArray.push(character);
    }
    return newArray;
};

/**
 * Repeat a string a given number of times.
 * @param string String to repeat
 * @param repeat Number of times to repeat
 */
exports.repeatString = function(string, repeat) {
    var newstring = '';

    for (var i = 0; i < repeat; i++) {
        newstring += string;
    }

    return newstring;
};

/**
 * Given a row, find the number of leading spaces.
 * E.g. an array with the string "  aposjd" would return 2
 * @param row The row to hunt through
 */
exports.leadingSpaces = function(row) {
    var numspaces = 0;
    for (var i = 0; i < row.length; i++) {
        if (row[i] == ' ' || row[i] == '' || row[i] === undefined) {
            numspaces++;
        } else {
            return numspaces;
        }
    }
    return numspaces;
};

/**
 * Given a row, find the number of leading tabs.
 * E.g. an array with the string "\t\taposjd" would return 2
 * @param row The row to hunt through
 */
exports.leadingTabs = function(row) {
    var numtabs = 0;
    for (var i = 0; i < row.length; i++) {
        if (row[i] == '\t' || row[i] == '' || row[i] === undefined) {
            numtabs++;
        } else {
            return numtabs;
        }
    }
    return numtabs;
};

/**
 * Given a row, extract a copy of the leading spaces or tabs.
 * E.g. an array with the string "\t    \taposjd" would return an array with the
 * string "\t    \t".
 * @param row The row to hunt through
 */
exports.leadingWhitespace = function(row) {
    var leading = [];
    for (var i = 0; i < row.length; i++) {
        if (row[i] == ' ' || row[i] == '\t' || row[i] == '' || row[i] === undefined) {
            leading.push(row[i]);
        } else {
            return leading;
        }
    }
    return leading;
};

/**
 * Given a camelCaseWord convert to "Camel Case Word"
 */
exports.englishFromCamel = function(camel) {
    camel.replace(/([A-Z])/g, function(str) {
        return " " + str.toLowerCase();
    }).trim();
};

/**
 * I hate doing this, but we need some way to determine if the user is on a Mac
 * The reason is that users have different expectations of their key combinations.
 *
 * Take copy as an example, Mac people expect to use CMD or APPLE + C
 * Windows folks expect to use CTRL + C
 */
exports.OS = {
    LINUX: 'LINUX',
    MAC: 'MAC',
    WINDOWS: 'WINDOWS'
};

var ua = navigator.userAgent;
var av = navigator.appVersion;

/** Is the user using a browser that identifies itself as Linux */
exports.isLinux = av.indexOf("Linux") >= 0;

/** Is the user using a browser that identifies itself as Windows */
exports.isWindows = av.indexOf("Win") >= 0;

/** Is the user using a browser that identifies itself as WebKit */
exports.isWebKit = parseFloat(ua.split("WebKit/")[1]) || undefined;

/** Is the user using a browser that identifies itself as Chrome */
exports.isChrome = parseFloat(ua.split("Chrome/")[1]) || undefined;

/** Is the user using a browser that identifies itself as Mac OS */
exports.isMac = av.indexOf("Macintosh") >= 0;

if (ua.indexOf("AdobeAIR") >= 0) {
    exports.isAIR = 1;
}

/**
 * Is the user using a browser that identifies itself as Safari
 * See also:
 * - http://developer.apple.com/internet/safari/faq.html#anchor2
 * - http://developer.apple.com/internet/safari/uamatrix.html
 */
var index = Math.max(av.indexOf("WebKit"), av.indexOf("Safari"), 0);
if (index && !exports.isChrome) {
    // try to grab the explicit Safari version first. If we don't get
    // one, look for less than 419.3 as the indication that we're on something
    // "Safari 2-ish".
    exports.isSafari = parseFloat(av.split("Version/")[1]);
    if (!exports.isSafari || parseFloat(av.substr(index + 7)) <= 419.3) {
        exports.isSafari = 2;
    }
}

if (ua.indexOf("Gecko") >= 0 && !exports.isWebKit) {
    exports.isMozilla = parseFloat(av);
}

/**
 * Return a exports.OS constant
 */
exports.getOS = function() {
    if (exports.isMac) {
        return exports.OS['MAC'];
    } else if (exports.isLinux) {
        return exports.OS['LINUX'];
    } else {
        return exports.OS['WINDOWS'];
    }
};

/**
 * Return true if with contains(a, b) the element b exists within the element a
 */
exports.contains = document.compareDocumentPosition ? function(a, b) {
    return a.compareDocumentPosition(b) & 16;
} : function(a, b) {
    return a !== b && (a.contains ? a.contains(b) : true);
};

/**
 * Prevents propagation and clobbers the default action of the passed event
 */
exports.stopEvent = function(ev) {
    ev.preventDefault();
    ev.stopPropagation();
};

/**
 * Create a random password of the given length (default 16 chars)
 */
exports.randomPassword = function(length) {
    length = length || 16;
    var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    var pass = "";
    for (var x = 0; x < length; x++) {
        var charIndex = Math.floor(Math.random() * chars.length);
        pass += chars.charAt(charIndex);
    }
    return pass;
};

/**
 * Is the passed object free of members, i.e. are there any enumerable
 * properties which the objects claims as it's own using hasOwnProperty()
 */
exports.isEmpty = function(object) {
    for (var x in object) {
        if (object.hasOwnProperty(x)) {
            return false;
        }
    }
    return true;
};

/**
 * Does the name of a project indicate that it is owned by someone else
 * TODO: This is a major hack. We really should have a File object that include
 * separate owner information.
 */
exports.isMyProject = function(project) {
    return project.indexOf("+") == -1;
};

/**
 * Format a date as dd MMM yyyy
 */
exports.formatDate = function (date) {
    if (!date) {
        return "Unknown";
    }
    return date.getDate() + " " +
        exports.formatDate.shortMonths[date.getMonth()] + " " +
        date.getFullYear();
};

/**
 * Month data for exports.formatDate
 */
exports.formatDate.shortMonths = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];

});

tiki.module("bespin:views/dock",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;

exports.DOCK_BOTTOM = 'bottom';

/**
 * @class
 *
 * A dock view contains and maintains layout for a center view that fills its
 * boundaries and, optionally, a docked view along the bottom.
 *
 * TODO: Allow views on the left, right, and top.
 * TODO: Allow more than one view on each side.
 */
exports.DockView = SC.View.extend({
    classNames: [ 'bespin-dock-view' ],

    centerView: SC.View,
    dockedViews: [],

    /**
     * Instantiates a view, adds it to the set of docked views, and returns it.
     * You should use this method over adding views to the dockedViews array
     * directly, because this method installs observers on the docked view that
     * are necessary to keep the subviews properly sized.
     *
     * This method does not call appendChild(), so you will probably want to
     * call that after calling this.
     */
    addDockedView: function(view, index) {
        var dockedViews = this.get('dockedViews');

        if (SC.none(index)) {
            index = dockedViews.length;
        }

        var thisDockView = this;
        var dockedView = this.createChildView(view, {
            layoutView: this,
            rootElementPath: [ index ],
            _bespin_dockView_frameDidChange: function() {
                thisDockView._updateChildLayout();
            }.observes('frame')
        });

        dockedViews[index] = dockedView;
        return dockedView;
    },

    createChildViews: function() {
        var centerView = this.createChildView(this.get('centerView'), {
            layoutView: this
        });
        this.set('centerView', centerView);
        var childViews = [ centerView ];

        var dockedViews = this.get('dockedViews');
        for (var i = 0; i < dockedViews.length; i++) {
            childViews.push(this.addDockedView(dockedViews[i], i));
        }

        this.set('childViews', childViews);
        return this;
    },

    _updateChildLayout: function() {
        var layout = { left: 0, bottom: 0, top: 0, right: 0 };
        this.get('dockedViews').forEach(function(item) {
            var frame = item.get('frame');
            var dock = item.get('dock');
            switch (dock) {
            case exports.DOCK_BOTTOM:
                layout.bottom += frame.height;
                break;
            default:
                throw "invalid 'dock' property: " + dock;
                break;  // silence jslint
            }
        });
        this.get('centerView').adjust(layout);
    },

    renderLayout: function(context, firstTime) {
        this._updateChildLayout();
        arguments.callee.base.apply(this, arguments);
    }
});


});

tiki.module("bespin:index",function(require,exports,module){});
;tiki.register("AppSupport",{});
tiki.module("AppSupport:controllers/undomanager",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;

/**
 * @class
 *
 * This simple undo manager coordinates undo for the app that embeds Bespin.
 * It's similar to SproutCore's UndoManager class, but it separates undo and
 * redo and correctly flushes the redo stack when an action is performed.
 */
exports.undoManager = SC.Object.create({
    _redoStack: null,
    _undoStack: null,

    _undoOrRedo: function(method, stack, otherStack) {
        if (stack.length === 0) {
            return false;
        }

        var record = stack.pop();
        if (!record.target[method](record.context)) {
            this._redoStack = [];
            this._undoStack = [];
            return false;
        }

        otherStack.push(record);
        return true;
    },

    init: function() {
        this._redoStack = [];
        this._undoStack = [];
    },

    /**
     * Redoes the last undone action.
     *
     * @return{boolean} True if the action was successfully redone, false
     *     otherwise.
     */
    redo: function() {
        return this._undoOrRedo('redo', this._redoStack, this._undoStack);
    },

    /**
     * Notifies the undo manager that an action was performed. When the action
     * is to be undone, the 'undo' message will be sent to the target with the
     * given context. When the action is to be redone, the 'redo' message is
     * sent in the same way.
     */
    registerUndo: function(target, context) {
        this._redoStack = [];
        this._undoStack.push({ target: target, context: context });
    },

    /**
     * Undoes the last action.
     *
     * @return{boolean} True if the action was successfully undone, false
     *     otherwise.
     */
    undo: function() {
        return this._undoOrRedo('undo', this._undoStack, this._redoStack);
    }
});

exports.undoManagerCommand = function(instruction, args, command) {
    exports.undoManager[command.name]();
};


});

tiki.module("AppSupport:views/keylistener",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var keyboardManager = require('Canon:keyboard').keyboardManager;

/**
 * @class
 *
 * This simple responder listens for keys that should be handled by the
 * application and routes them to the appropriate objects.
 */
exports.KeyListener = SC.Responder.extend({
    performKeyEquivalent: function(key, evt) {
        keyboardManager.processKeyEvent(evt, this, { "isApplication": true });
    }
});

});

tiki.module("AppSupport:index",function(require,exports,module){});
;tiki.register("Canon",{});
tiki.module("Canon:arguments",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var catalog = require("bespin:plugins").catalog;

/**
* string argument conversion. Very simple, since arguments
* start as strings. This is the default if no type is given.
*/
exports.string = {
    convert: function(value) {
        return value.toString();
    }
};

/**
* integer type converts to a number. No default is generated here.
*/
exports.integer = {
    convert: function(value) {
        return parseInt(value, 10);
    }
};

/**
* The editor model
*/
exports.model = {
    acceptsInput: false,
    getDefault: function() {
        return catalog.getObject("model");
    }
};

/**
* The editor view
*/
exports.view = {
    acceptsInput: false,
    getDefault: function() {
        return catalog.getObject("view");
    }
};

});

tiki.module("Canon:commands",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// TODO: How to we mark sub commands?

var rootCanon = require("Canon:directory").rootCanon;
var util = require("bespin:util/util");
var catalog = require("plugins").catalog;

var files = catalog.getObject("files");
var editor = catalog.getObject("editor");
var editSession = catalog.getObject("editSession");
var server = catalog.getObject("server");

/**
 * 'cmd load'
 */
exports.loadCommand = function(instruction, commandname) {
    if (!commandname) {
        instruction.addUsageOutput(this);
        return;
    }

    var project = files.userSettingsProject;
    var path = "commands/" + commandname + ".js";

    var onSuccess = function(file) {
        try {
            var command = eval(file.content);
            // Note: This used to allow multiple commands to be stored in
            // a single file, however that meant that the file was a (more)
            // butchered version of JSON - the contents of an array.
            rootCanon.addCommand(command);
        } catch (e) {
            instruction.addErrorOutput("Something is wrong about the command:<br><br>" + e);
        }
    };

    files.loadContents(project, path, onSuccess, true);
};

/**
 * 'cmd edit'
 */
exports.editCommand = function(instruction, commandname) {
    if (!commandname) {
        instruction.addUsageOutput(this);
        return;
    }

    var filename = "commands/" + commandname + ".js";
    var content = "" +
        "{\n" +
        "    name: '" + commandname + "',\n" +
        "    takes: [YOUR_ARGUMENTS_HERE],\n" +
        "    preview: 'execute any editor action',\n" +
        "    execute: function(self, args) {\n" +
        "\n" +
        "    }\n" +
        "}";

    editor.openFile(files.userSettingsProject, filename, {
        content: content,
        force: true
    });
};

/**
 * 'cmd list'
 */
exports.listCommand = function(instruction) {
    var project = files.userSettingsProject;
    server.list(project, 'commands/', function(commands) {
        if (!commands || commands.length < 1) {
            instruction.addOutput("You haven't installed any custom commands." +
                    "<br>Want to " +
                    "<a href='https://wiki.mozilla.org/Labs/Bespin/Roadmap/Commands'>" +
                    "learn how?</a>");
        } else {
            var output = "<u>Your Custom Commands</u><br/><br/>";

            var jsCommands = commands.filter(function(file) {
                return util.endsWith(file.name, '\\.js');
            });

            output += jsCommands.map(function(jsCommand) {
                return jsCommand.name.replace(/\.js$/, '');
            }).join("<br>");
            instruction.addOutput(output);
        }
    });
};

/**
 * 'cmd rm' command
 */
exports.deleteCommand = function(instruction, commandname) {
    if (!commandname) {
        instruction.addUsageOutput(this);
        return;
    }

    var project = files.userSettingsProject;
    var commandpath = "commands/" + commandname + ".js";

    var onSuccess = instruction.link(function() {
        if (editSession.checkSameFile(project, commandpath)) {
            // only clear if deleting the same file
            editor.model.clear();
        }
        instruction.addOutput('Removed command: ' + commandname);
    });

    var onFailure = instruction.link(function(xhr) {
        instruction.addOutput("Wasn't able to remove the command <b>" + commandname + "</b><br/><em>Error</em> (probably doesn't exist): " + xhr.responseText);
    });

    files.removeFile(project, commandpath, onSuccess, onFailure);
};

});

tiki.module("Canon:directory",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require("sproutcore/runtime").SC;
var catalog = require("bespin:plugins").catalog;

/**
 * Register new commands as they are discovered in plugins.
 * A command is a JSON structure that looks something like this:
 * <pre>
 * {
 *     "ep": "command",
 *     "parent": "git", // Optional - Parent command, e.g. "git checkout"
 *     "name": "checkout",
 *     "takes": [ "revision" ],
 *     "aliases": [ "co" ],
 *     "hidden": true,
 *     "preview": "",
 *     "completeText": "",
 *     "usage": "",
 *     "pointer": "git#checkoutCommand"
 * }
 * </pre>
 */
exports.newCommandHandler = function(command) {
    exports.rootCanon.addCommand(command);
};

exports.Command = SC.Object.extend({
    extension: null,
    
    init: function() {
        this.set("takesArgs", this.get("takes") != undefined);
        this._normalizeTakes();
    },
    
    load: function(callback) {
        this.get("extension").load(callback);
    },
    
    execute: function() {
        var args = arguments;
        var self = this;
        this.load(function(execute) {
            execute.apply(self, args);
        });
    },
    
    _normalizeTakes: function() {
        var paramList = [];
        var self = this;
        this._noInputList = [];
        
        var takes = this.get("takes");
        
        if (takes) {
            takes.forEach(function(item) {
                if (typeof(item) == "string") {
                    paramList.push(item);
                } else {
                    var itemType = item.type || "string";
                    var argType = catalog.getExtensionByKey("argumentType",
                        itemType);
                    if (!argType) {
                        console.error("Command ", item.name, 
                            " requires an argument of type ", itemType, 
                            " which is undefined");
                        return;
                    }
                    if (argType.noInput) {
                        self._noInputList.push(argType);
                    } else {
                        if (item.name) {
                            paramList.push(item.name);
                        } else {
                            paramList.push(itemType);
                        }
                    }
                }
            });
        }
        
        this._paramList = paramList.join(" ");
    },
    
    /**
     * Calculate the args object to be passed into the command.
     * Split the arguments up for the command and send in an object.
     */
    getArgs: function(fromUser, callback) {
        var takes = this.get("takes");
        if (!takes) {
            callback(undefined);
            return;
        }

        var args;
        var userString = fromUser.join(' ');
        
        // Commenting out the old-style varargs for now.
        // Likely want to do something different with the new
        // 'takes' options.
        // if (command.takes['*']) {
        //     args = TokenObject.create({ input:userString });
        //     args.rawinput = userString;
        // 
        //     args.varargs = args.pieces; // directly grab the token pieces as an array
        // } else {
        args = TokenObject.create({
            input: userString,
            options: { params: this._paramList }
        });
        args.rawinput = userString;
        
        this.convertArguments(args, callback);
        // }
    },
    
    convertArguments: function(args, callback) {
        // counter to keep track of how many conversions
        // are done, since they're done asynchronously.
        // should probably be using promises here
        var accounted = 0;
        var fired = false;
        
        var total = this.get("takes").length;
        
        this.get("takes").forEach(function(item) {
            if (typeof(item) == "string") {
                accounted++;
                return;
            }
            var itemType = item.type || "string";
            var itemName = item.name || item.type;
            var argTypeExt = catalog.getExtensionByKey("argumentType",
                itemType);
            if (!argTypeExt) {
                accounted++;
                return;
            }
            
            argTypeExt.load(function(argType) {
                accounted++;
                if (args[itemName] != undefined) {
                    if (argType.convert) {
                        args[itemName] = argType.convert(args[itemName]);
                    }
                } else {
                    if (item["default"]) {
                        args[itemName] = item["default"];
                    } else if (argType.getDefault) {
                        args[itemName] = argType.getDefault();
                    }
                }
                if (accounted == total) {
                    callback(args);
                    fired = true;
                }
            });
        });
        
        if (accounted == total && !fired) {
            callback(args);
        }
    },
    
    /**
     * This is like store.getFullCommandName() but for commands
     */
    getFullCommandName: function() {
        var name = this.get("name");
        if (this.get("parent")) {
            name = this.parent.getFullCommandName() + " " + name;
        }
        return name.trim();
    },
    
    /**
     * Like canon.findCompletions() but a default that just uses
     * command.completeText to provide a hint
     */
    findCompletions: function(query, callback) {
        query.hint = this.completeText;
        callback(query);
    }
});

/**
 * A Canon is a set of commands
 */
exports.Canon = SC.Object.extend({

    commands: {},
    aliases: {},

    /**
     * TODO: this is perhaps misnamed - it's really here just for the benefit
     * of the SproutCore init function. Perhaps init should remove it from this
     * once it has been added to the parent store
     */
    command: null,
    parent: null,

    /**
     * To create a root command store, call with no parameters.
     * To create a sub-command store, pass the parent store in first, and a
     * single command into the second parameter
     */
    init: function() {
        // If there is a parent, then this is a store for a command with subcommands
        if (this.parent) {
            // save the fact that we are a subcommand for this chap
            this.containerCommand = this.command;

            // implicit that it takes something
            this.command.takes = ['*'];

            // link back to this store
            this.command.subcommands = this;

            // add the sub command to the parent store
            this.parent.addCommand(this.command);
        }
    },

    /**
     * Add a new command to this command store
     */
    addCommand: function(extension) {
        if (!extension) {
            return;
        }
        
        var command = exports.Command.create(extension);

        command.set("parent", this);
        command.set("extension", extension);

        // Remember the command
        this.commands[command.get("name")] = command;
        
        // Cache all the aliases in a store wide list
        if (command.aliases) {
            command.aliases.forEach(function(alias) {
                this.aliases[alias] = command.name;
            }, this);
        }
    },
    

    /**
     * Add a new command to this canon
     */
    removeCommand: function(command) {
        if (!command) {
            return;
        }

        delete this.commands[command.name];
    },

    /**
     * Commands can contain sub commands, this gets us the full name of this
     * command. e.g. This may be a 'commit' command that is part of the 'vcs'
     * command, so this will return "vcs commit"
     */
    getFullCommandName: function() {
        var name = this.containerCommand ? this.containerCommand.name : "";
        if (this.parent) {
            name = this.parent.getFullCommandName() + " " + name;
        }
        return name.trim();
    },

    /**
     * Returns the subset of the options input array where the string values
     * begin with the given prefix
     */
    filterOptionsByPrefix: function(options, prefix) {
        return options.filter(function(option) {
            return option.substr(0, prefix.length) === prefix;
        });
    },

    /**
     * Find a command from this canon from something the user typed at a command
     * line.
     * <p>A null return from this method implies a value that can't be matched
     * to a command, or completed in any way to a command.
     * <p>Examples:<ul>
     * <li>findCommand("") = root canon
     * <li>findCommand("s") = root canon
     * <li>findCommand("set") = set command
     * <li>findCommand("set ") = set command
     * <li>findCommand("set invalid params") = set command
     * <li>findCommand("sett") = null (TODO: Returns root canon now)
     * <li>findCommand("vcs") = vcs canon
     * <li>findCommand("vcs clon") = vcs canon
     * <li>findCommand("vcs clone") = vcs clone command
     * <li>findCommand("vcs clone repo") = vcs clone command
     * </ul>
     * <p>This works by delegating to child command canons when necessary
     * having first removed any command prefixes from the matched canon.
     * e.g. rootCanon.findCommand("vcs clone repo") will delegate to the vcs
     * canon, and ask vcsStore.findCommand("clone repo"). The answer to
     * this last request is the clone command, because it is not a canon.
     */
    findCommand: function(value) {
        var parts = value.trim().split(/\s+/);
        var first = parts.shift();
        var command = this.commands[first] || this.commands[this.aliases[first]];
        if (!command) {
            if (parts.length > 0) {
                return null;
            } else {
                // TODO: We really should be doing some matching on this
                return this;
            }
        }
        if (command.subcommands) {
            return command.subcommands.findCommand(parts.join(" "));
        } else {
            return command;
        }
    },

    /**
     * Find the commands that could work, given the value typed
     * @see commandLine._findCompletions(e) for how this is called
     * @param query Values to be used in completion
     * @param callback Function to be called with the results
     */
    findCompletions: function(query, callback) {
        // Multiple args implies that we've failed to route to a child command
        if (query.action.length > 1) {
            query.error = "No matches";
            callback(query);
            return;
        }

        // Find the text that we're working on.
        var typed = query.action[0];

        // No hints for a blank command line
        if (typed.length === 0 && this.parent) {
            callback(query);
            return;
        }

        // Get a list of all commands and aliases. TODO: cache?
        var matches = [];
        for (var command in this.commands) {
            if (command.indexOf(typed) === 0) {
                matches.push(command);
            }
        }
        for (var alias in this.aliases) {
            if (alias.indexOf(typed) === 0) {
                matches.push(alias);
            }
        }

        if (matches.length == 1) {
            // Single match: go for autofill and hint
            var newValue = matches[0];
            command = this.commands[newValue] || this.commands[this.aliases[newValue]];
            if (command.get("takesArgs")) {
                newValue = newValue + " ";
            }
            query.autofill = query.prefix + newValue;
            query.hint = command.preview;
        } else if (matches.length === 0) {
            // No matches, cause an error
            query.error = "No matches";
        } else {
            // Multiple matches, present a list
            matches.sort(function(a, b) {
                return a.localeCompare(b);
            });
            query.options = matches;
        }

        callback(query);
        return;
    },

    /**
     * Generate some help text for all commands in this canon, optionally
     * filtered by a <code>prefix</code>, and with a <code>helpSuffix</code>
     * appended.
     */
    getHelp: function(prefix, options) {
        var commands = [];
        var command, name;

        if (this.commands[prefix]) { // caught a real command
            command = this.commands[prefix];
            commands.push(command.description ? command.description : command.preview);
        } else {
            var showHidden = false;

            var subcmdprefix = "";
            if (this.containerCommand) {
                subcmdprefix = " for " + this.containerCommand.name;
            }

            if (prefix) {
                if (prefix == "hidden") { // sneaky, sneaky.
                    prefix = "";
                    showHidden = true;
                }
                commands.push("<h2>Commands starting with '" + prefix + "':</h2>");
            } else {
                commands.push("<h2>Available Commands:</h2>");
            }

            var toBeSorted = [];
            for (name in this.commands) {
                toBeSorted.push(name);
            }

            var sorted = toBeSorted.sort();

            commands.push("<table>");
            for (var i = 0; i < sorted.length; i++) {
                name = sorted[i];
                command = this.commands[name];

                if (!showHidden && command.hidden) {
                    continue;
                }
                if (command.preview === undefined) {
                    // Ignore editor actions
                    continue;
                }
                if (prefix && name.indexOf(prefix) !== 0) {
                    continue;
                }

                var args = (command.takes) ? ' [' + command._paramList.split(' ').join('] [') + ']' : '';

                commands.push("<tr>");
                commands.push('<th>' + name + '</th>');
                commands.push('<td>' + command.preview + "</td>");
                commands.push('<td>' + args + '</td>');
                commands.push("</tr>");
            }
            commands.push("</table>");
        }

        var output = commands.join("");
        if (options && options.prefix) {
            output = options.prefix + output;
        }
        if (options && options.suffix) {
            output = output + options.suffix;
        }
        return output;
    }
});

/**
 * Create the root that all commands will be added to
 */
exports.rootCanon = new exports.Canon();

/**
 * Given a string, make a token object that holds positions and has name access.
 * <p>Examples:
 * <pre>
 * var args = TokenObject.create({ input:userString, options: {
 *     params: command.takes.order.join(' ')
 * }});
 *
 * var test = TokenObject.create({
 *     input: document.getElementById("input").value,
 *     options: {
 *         splitBy: document.getElementById("regex").value,
 *         params: document.getElementById("params").value
 *     }
 * });
 *
 * var test = TokenObject.create({ input:"male 'Dion Almaer'", options: {
 *     params: 'gender name'
 * }});
 * </pre>
 */
var TokenObject = SC.Object.extend({
    input: null,
    options: { },

    init: function() {
        this._splitterRegex = new RegExp(this.options.splitBy || '\\s+');
        this.pieces = this.tokenize(this.input.split(this._splitterRegex));

        if (this.options.params) {
            this._nametoindex = {};
            var namedparams = this.options.params.split(' ');
            for (var x = 0; x < namedparams.length; x++) {
                this._nametoindex[namedparams[x]] = x;

                // side step if you really don't want this
                if (!this.options.noshortcutvalues) {
                    this[namedparams[x]] = this.pieces[x];
                }
            }
        }
        this.sc_super();
    },

    /**
     * Split up the input taking into account ' and "
     */
    tokenize: function(incoming) {
        var tokens = [];

        var nextToken;
        while (nextToken = incoming.shift()) {
            if (nextToken[0] == '"' || nextToken[0] == "'") { // it's quoting time
                var eaten = [ nextToken.substring(1, nextToken.length) ];
                var eataway;
                while (eataway = incoming.shift()) {
                    if (eataway[eataway.length - 1] == '"' || eataway[eataway.length - 1] == "'") { // end quoting time
                        eaten.push(eataway.substring(0, eataway.length - 1));
                        break;
                    } else {
                        eaten.push(eataway);
                    }
                }
                tokens.push(eaten.join(' '));
            } else {
                tokens.push(nextToken);
            }
        }

        return tokens;
    },

    param: function(index) {
        return (typeof index == "number")
                ? this.pieces[index]
                : this.pieces[this._nametoindex[index]];
    },

    length: function() {
        return this.pieces.length;
    }
});

});

tiki.module("Canon:instruction",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require("sproutcore/runtime").SC;

/**
 * Wrapper for a command execution that the user typed
 */
exports.Instruction = SC.Object.extend({
    output: "",
    _outstanding: 0,
    _callbacks: [],
    completed: false,
    historical: false,

    /**
     *
     */
    init: function() {
        // It is valid to not know the commandLine when we are filling the
        // history from disk, but in that case we don't need to parse it
        if (!this.historical) {
            this.start = new Date();
        } else {
            this.completed = true;
        }
    },

    /**
     * Execute the command
     */
    exec: function() {
        try {
            if (this._parseError) {
                this.addErrorOutput(this._parseError);
            } else {
                this.command.execute(this, this.args, this.command);
            }
        }
        catch (ex) {
            if (ex instanceof TypeError) {
                console.error(ex);
            }
            this.addErrorOutput(ex);
        }
        finally {
            if (this._outstanding == 0) {
                this.completed = true;
                this._callbacks.forEach(function(callback) {
                    callback();
                });
            }
        }
    },

    /**
     * Link Function to Instruction
     * Make a function be part of the thread of execution of an instruction
     */
    link: function(action, context) {
        this._outstanding++;

        return function() {
            try {
                action.apply(context || window, arguments);
            } finally {
                this._outstanding--;

                if (this._outstanding === 0) {
                    this.completed = true;
                    this._callbacks.forEach(function(callback) {
                        callback();
                    });
                }
            }
        }.bind(this);
    },

    /**
     * A hack to allow an instruction that has called link to forget all the
     * linked functions.
     */
    unlink: function() {
        this._outstanding = 0;
        this.completed = true;
        this._callbacks.forEach(function(callback) {
            callback();
        });
    },

    /**
     * A string version of this Instruction suitable for serialization
     */
    toString: function() {
        return JSON.stringify({
            output: this.output,
            start: this.start ? this.start.getTime() : -1,
            end: this.end ? this.end.getTime() : -1
        });
    },

    /**
     * Complete the currently executing command with successful output
     */
    addOutput: function(html) {
        if (html && html !== "") {
            if (this.output !== "") {
                this.output += "<br/>";
            }
            this.output += html;
        }

        this.element = null;
        this.hideOutput = false;
        this.end = new Date();

        this._callbacks.forEach(function(callback) {
            callback(html);
        });
    },

    /**
     * Complete the currently executing command with error output
     */
    addErrorOutput: function(html) {
        this.error = true;
        this.addOutput(html);
    },

    /**
     * Complete the currently executing command with usage output
     * TODO: Why do we need to pass the command in?
     */
    addUsageOutput: function(command) {
        this.error = true;
        var usage = command.usage || "no usage information found for " + command.name;
        this.addOutput("Usage: " + command.name + " " + usage);
    },

    /**
     * Monitor output that goes to an instruction
     */
    onOutput: function(callback) {
        // Catch-up on the output so far
        callback.call(null, this.output);

        this._callbacks.push(callback);

        // TODO: return an element to allow us to unregister the listener
    },

    /**
     * Instead of doing output by appending strings, commands can pass in a
     * DOM node that they update. It is assumed that commands doing this will
     * provide their own progress indicators.
     */
    setElement: function(element) {
        this.element = element;
        this.end = new Date();
        this.hideOutput = false;
        this.error = false;

        this._callbacks.forEach(function(callback) {
            callback();
        });
    }
});

});

tiki.module("Canon:keyboard",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var Instruction = require("instruction").Instruction;

var canon = require("directory").rootCanon;

/**
 * The canon, or the repository of commands, contains functions to process
 * events and dispatch command messages to targets.
 * @class
 */
var KeyboardManager = SC.Object.extend({
    _commandMatches: function(command, symbolicName, flags) {
        var mappedKeys = command.key;
        if (!mappedKeys) {
            return false;
        }
        if (typeof(mappedKeys) == "string") {
            if (mappedKeys != symbolicName) {
                return false;
            }
            return true;
        }
        
        if (!mappedKeys.isArray) {
            mappedKeys = [mappedKeys];
            command.key = mappedKeys;
        }
        
        for (var i = 0; i < mappedKeys.length; i++) {
            var keymap = mappedKeys[i];
            if (typeof(keymap) == "string") {
                if (keymap == symbolicName) {
                    return true;
                }
                continue;
            }
            
            if (keymap.key != symbolicName) {
                continue;
            }
            
            var predicates = keymap.predicates;
            
            if (!predicates) {
                return true;
            }
            
            for (var flagName in predicates) {
                if (!flags || flags[flagName] != predicates[flagName]) {
                    return false;
                }
            }
            return true;
        }
        return false;
    },

    /**
     * Searches through the command canon for an event matching the given flags
     * with a key equivalent matching the given SproutCore event, and, if the
     * command is found, sends a message to the appropriate target.
     * 
     * This will get a couple of upgrades in the not-too-distant future:
     * 1. caching in the Canon for fast lookup based on key
     * 2. there will be an extra layer in between to allow remapping via
     *    user preferences and keyboard mapping plugins
     *
     * @return True if a matching command was found, false otherwise.
     */
    processKeyEvent: function(evt, sender, flags) {
        var symbolicName = evt.commandCodes()[0];
        var commands = canon.get("commands");
        for (var commandName in commands) {
            var command = commands[commandName];
            if (this._commandMatches(command, symbolicName, flags)) {
                command.getArgs([], function(args) {
                    var instruction = Instruction.create({
                        command: command,
                        args: args
                    });
                    instruction.exec();
                });
                return true;
            }
        }
        return false;
    }
});

/**
 * The global exported KeyboardManager
 */
exports.keyboardManager = KeyboardManager.create();

});

tiki.module("Canon:index",function(require,exports,module){});
;tiki.register("Editor",{});
tiki.module("Editor:commands/editor",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var util = require("bespin:util/util");
var settings = require("bespin:plugins").catalog.getObject("settings");

/**
 * 'goto' command
 */
exports.gotoCommand = function(instruction, value) {
    if (value) {
        var linenum = parseInt(value, 10);
        editor.moveAndCenter(linenum);
        editor.focus();
    }
};

/**
 * 'replace' command
 */
exports.replaceCommand = function(instruction, args) {
    editor.replace(args);
};

/**
 * 'sort' command
 */
exports.sortCommand = function(instruction, direction) {
    var buffer = editor.getDocument().split(/\n/);
    buffer.sort();
    if (direction && /^desc/.test(direction.toLowerCase())) {
        buffer.reverse();
    }
    editor.insertDocument(buffer.join("\n"));
};

/**
 * 'entab' command
 */
exports.entabCommand = function(instruction, tabsize) {
    if (!tabsize) {
        tabsize = settings.values.tabsize;
    }

    editor.replace({
        search: ' {' + tabsize + '}',
        replace: '\t'
    });
};

/**
 * 'detab' command
 */
exports.detabCommand = function(instruction, tabsize) {
    if (!tabsize) {
        tabsize = settings.values.tabsize;
    }

    editor.replace({
        search: '\t',
        replace: util.repeatString(' ', tabsize)
    });
};

/**
 * 'trim' command
 */
exports.trimCommand = function(instruction, side) {
    if (!side) {
        side = "right";
    }
    var replaceArgs = {
        replace: ''
    };

    if (util.include(["left", "both"], side)) {
        replaceArgs.search = "^\\s+";
        editor.replace(replaceArgs);
    }

    if (util.include(["right", "both"], side)) {
        replaceArgs.search = "\\s+$";
        editor.replace(replaceArgs);
    }
};

/**
 * 'uc' command
 */
exports.ucCommand = function(instruction) {
    editor.selectionChangeCase({ stringCase: 'u' });
};

/**
 * 'lc' command
 */
exports.lcCommand = function(instruction) {
    editor.selectionChangeCase({ stringCase: 'l' });
};

});

tiki.module("Editor:controllers/layoutmanager",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var MultiDelegateSupport =
    require('mixins/multidelegate').MultiDelegateSupport;
var Range = require('utils/range');
var TextStorage = require('models/textstorage').TextStorage;
var catalog = require('bespin:plugins').catalog;

exports.LayoutManager = SC.Object.extend(MultiDelegateSupport, {
    _characterWidth: 8,
    _layoutAnnotations: null,
    _lineHeight: 20,
    _maximumWidth: 0,

    /**
     * @property
     *
     * The margins on each edge in pixels, expressed as an object with "left",
     * "bottom", "top", and "right" properties.
     *
     * Do not modify the properties of this object directly; clone, adjust, and
     * reset the margin property of the layout manager instead.
     */
    margin: { left: 5, bottom: 6, top: 0, right: 12 },

    /**
     * @property
     *
     * The plugin catalog to use. Typically this will be plugins.catalog, but
     * for testing this may be replaced with a mock object.
     */
    pluginCatalog: catalog,

    /**
     * @property{Array}
     *
     * The marked-up lines of text, consisting of an array of objects with
     * 'character' and 'lineHeight' properties, along with any other properties
     * that annotations may have added. Read-only.
     */
    textLines: null,

    /**
     * @property
     *
     * The model instance that this object is responsible for laying out.
     */
    textStorage: TextStorage,

    /**
     * @property
     *
     * The theme to use.
     *
     * TODO: Convert to a SproutCore theme.
     */
    theme: {
        editorTextColor: "rgb(230, 230, 230)"
    },

    _computeInvalidRects: function(oldRange, newRange) {
        var startRect = this.characterRectForPosition(oldRange.start);

        var lineRect = {
            x:      startRect.x,
            y:      startRect.y,
            width:  Number.MAX_VALUE,
            height: startRect.height
        };

        return oldRange.end.row === newRange.end.row ?
            [ lineRect ] :
            [
                lineRect,
                {
                    x:      0,
                    y:      startRect.y + this._lineHeight,
                    width:  Number.MAX_VALUE,
                    height: Number.MAX_VALUE
                }
            ];
    },

    // Returns the last valid position in the buffer.
    _lastCharacterPosition: function() {
        return {
            row:    this.get('textLines').length - 1,
            column: this._maximumWidth
        };
    },

    _recalculateMaximumWidth: function() {
        // Lots of room for optimization here if this turns out to be slow. But
        // for now...
        var textLines = this.get('textLines');
        var max = 0;
        textLines.forEach(function(line) {
            var width = line.characters.length;
            if (max < width) {
                max = width;
            }
        });
        this._maximumWidth = max;
    },

    _recomputeEntireLayout: function() {
        var entireRange = this.get('textStorage').range();
        this._recomputeLayoutForRanges(entireRange, entireRange);
    },

    _recomputeLayoutForRanges: function(oldRange, newRange) {
        var theme = this.get('theme');
        var oldStartRow = oldRange.start.row;
        this.textLines.replace(oldStartRow, oldRange.end.row - oldStartRow + 1,
            this.getPath('textStorage.lines').slice(oldStartRow,
            newRange.end.row + 1).map(function(line) {
                return {
                    characters: line,
                    colors:     [
                        {
                            start:  0,
                            end:    line.length,
                            color:  theme.editorTextColor
                        }
                    ]
                };
            }));

        this._recalculateMaximumWidth();

        var changedRange = this._runAnnotations(Range.unionRanges(oldRange,
            newRange));

        var invalidRects = this._computeInvalidRects(oldRange, newRange);
        this.notifyDelegates('layoutManagerInvalidatedRects', invalidRects);
    },

    _runAnnotations: function(range) {
        var textLines = this.get('textLines');
        this._layoutAnnotations.forEach(function(annotation) {
            range = annotation.annotateLayout(textLines, range);
        });
        return range;
    },

    /**
     * Determines the boundaries of the entire text area.
     *
     * TODO: Unit test.
     */
    boundingRect: function() {
        return this.rectsForRange({
            start:  { row: 0, column: 0 },
            end:    {
                row:    this.get('textLines').length - 1,
                column: this._maximumWidth
            }
        })[0];
    },

    /**
     * Determines the location of the character underneath the given point.
     *
     * @return Returns an object with three properties:
     *   * row: The row of the character nearest the point.
     *   * column: The column of the character nearest the point.
     *   * partialFraction: The fraction of the horizontal distance between
     *       this character and the next character. The extreme left of the
     *       character is 0.0, while the extreme right of the character is 1.0.
     *       If you are calling this function to determine where to place the
     *       cursor, then you should place the cursor after the returned
     *       character if this value is greater than 0.5.
     *
     * If there is no character under the point, then the character nearest the
     * given point is returned, according to the selection rules.
     */
    characterAtPoint: function(point) {
        var margin = this.get('margin');
        var clientX = point.x - margin.left, clientY = point.y - margin.top;

        var characterWidth = this._characterWidth;
        var textStorage = this.get('textStorage');
        var clampedPosition = textStorage.clampPosition({
            row:    Math.floor(clientY / this._lineHeight),
            column: Math.floor(clientX / characterWidth)
        });

        var lineLength = textStorage.get('lines')[clampedPosition.row].length;
        return SC.mixin(clampedPosition, {
            partialFraction:
                clientX < 0 || clampedPosition.column === lineLength ? 0.0 :
                clientX % characterWidth / characterWidth
        });
    },

    /**
     * Given a rectangle expressed in pixels, returns the range of characters
     * that lie at least partially within the rectangle as an object.
     *
     * TODO: Write unit tests for this method.
     */
    characterRangeForBoundingRect: function(rect) {
        // TODO: variable line heights, needed for word wrap and perhaps
        // extensions as well
        var lineHeight = this._lineHeight;
        var characterWidth = this._characterWidth;
        var margin = this.get('margin');
        var x = rect.x - margin.left, y = rect.y - margin.top;
        return {
            start:  {
                row:    Math.max(Math.floor(y / lineHeight), 0),
                column: Math.max(Math.floor(x / characterWidth), 0)
            },
            end:    {
                row:    Math.floor((y + rect.height - 1) / lineHeight),
                column: Math.floor((x + rect.width - 1) / characterWidth) + 1
            }
        };
    },

    /**
     * Returns the boundaries of the character at the given position.
     */
    characterRectForPosition: function(position) {
        return this.rectsForRange({
            start:  position,
            end:    { row: position.row, column: position.column + 1 }
        })[0];
    },

    /**
     * @protected
     *
     * Instantiates the internal text storage object. The default
     * implementation of this method simply calls create() on the internal
     * textStorage property.
     */
    createTextStorage: function() {
        this.set('textStorage', this.get('textStorage').create());
    },

    init: function() {
        this._layoutAnnotations = [];
        this.set('textLines', [
            {
                characters: "",
                colors:     [
                    {
                        start:  0,
                        end:    0,
                        color:  this.get('theme').editorTextColor
                    }
                ]
            }
        ]);

        this.createTextStorage();
        this.get('textStorage').addDelegate(this);

        var thisLayoutManager = this;
        this.get('pluginCatalog').getExtensions('layoutannotations').
            forEach(function(extension) {
                extension.load(function(annotation) {
                    thisLayoutManager._layoutAnnotations.push(annotation);
                    thisLayoutManager._recomputeEntireLayout();
                }, 'layoutannotation');
            });

        this._recomputeEntireLayout();
    },

    /**
     * Returns the pixel boundaries of the given line.
     *
     * TODO: Unit test.
     */
    lineRectForRow: function(row) {
        return this.rectsForRange({
            start:  { row: row, column: 0                   },
            end:    { row: row, column: this._maximumWidth  }
        })[0];
    },

    rectForPosition: function(position) {
        var margin = this.get('margin');
        var characterWidth = this._characterWidth;
        var lineHeight = this._lineHeight;
        return {
            x:      margin.left + characterWidth * position.column,
            y:      margin.top + lineHeight * position.row,
            width:  characterWidth,
            height: lineHeight
        };
    },

    /**
     * Returns the 1, 2, or 3 rectangles that make up the given range.
     */
    rectsForRange: function(range) {
        var characterWidth = this._characterWidth;
        var lineHeight = this._lineHeight;
        var maximumWidth = this._maximumWidth;
        var margin = this.get('margin');

        var start = range.start, end = range.end;
        var startRow = start.row, startColumn = start.column;
        var endRow = end.row, endColumn = end.column;

        if (startRow === endRow) {
            // The simple rectangle case.
            return [
                {
                    x:      margin.left + characterWidth * startColumn,
                    y:      margin.top + lineHeight * startRow,
                    width:  characterWidth * (endColumn - startColumn),
                    height: lineHeight
                }
            ];
        }

        var rects = [];

        // Top line
        var middleStartRow;
        if (startColumn === 0) {
            middleStartRow = startRow;
        } else {
            middleStartRow = startRow + 1;
            rects.push({
                x:      margin.left + characterWidth * startColumn,
                y:      margin.top + lineHeight * startRow,
                width:  characterWidth * (maximumWidth - startColumn),
                height: lineHeight
            });
        }

        // Bottom line
        var middleEndRow;
        if (endColumn === 0) {
            middleEndRow = endRow - 1;
        } else if (endColumn === maximumWidth) {
            middleEndRow = endRow;
        } else {
            middleEndRow = endRow - 1;
            rects.push({
                x:      margin.left,
                y:      margin.top + lineHeight * endRow,
                width:  characterWidth * endColumn,
                height: lineHeight
            });
        }

        // Middle area
        rects.push({
            x:      margin.left,
            y:      margin.top + lineHeight * middleStartRow,
            width:  characterWidth * maximumWidth,
            height: lineHeight * (middleEndRow - middleStartRow + 1)
        });

        return rects;
    },

    textStorageEdited: function(sender, oldRange, newRange) {
        this._recomputeLayoutForRanges(oldRange, newRange);
    }
});


});

tiki.module("Editor:controllers/undo",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var undoManager = require('AppSupport:controllers/undomanager').undoManager;

/**
 * @class
 *
 * The editor undo controller is a delegate of the text view that groups
 * changes into patches and saves them with the undo manager.
 *
 * This object does not assume that it has exclusive write access to the text
 * storage object, and as such it tries to maintain sensible behavior in the
 * presence of direct modification to the text storage by other objects. This
 * is important for collaboration.
 */
exports.EditorUndoController = SC.Object.extend({
    _inTransaction: false,
    _record: null,

    /**
     * @property{TextView}
     *
     * The view object to forward changes to. This property must be set upon
     * instantiating the undo controller.
     */
    textView: null,

    _beginTransaction: function() {
        if (this._inTransaction) {
            console.trace();
            throw "UndoController._beginTransaction() called with a " +
                "transaction already in place";
        }

        this._inTransaction = true;
        this._record = { patches: [] };
    },

    _endTransaction: function() {
        if (!this._inTransaction) {
            throw "UndoController._endTransaction() called without a " +
                "transaction in place";
        }

        undoManager.registerUndo(this, this._record);
        this._record = null;

        this._inTransaction = false;
    },

    _tryApplyingPatches: function(patches) {
        var textStorage = this.getPath('textView.layoutManager.textStorage');
        patches.forEach(function(patch) {
            textStorage.replaceCharacters(patch.oldRange, patch.newCharacters);
        });
        return true;
    },

    _undoOrRedo: function(patches, selection) {
        if (this._inTransaction) {
            // Can't think of any reason why this should be supported, and it's
            // often an indication that someone forgot an endTransaction()
            // call somewhere...
            throw "UndoController._undoOrRedo() called while in a transaction";
        }

        if (!this._tryApplyingPatches(patches)) {
            return false;
        }

        this.get('textView').setSelection(selection);
        return true;
    },

    init: function() {
        this._redoStack = [];
        this._undoStack = [];

        this.get('textView').addDelegate(this);
    },

    redo: function(record) {
        return this._undoOrRedo(record.patches, record.selectionAfter);
    },

    textViewBeganChangeGroup: function(sender, selection) {
        this._beginTransaction();
        this._record.selectionBefore = selection;
    },

    textViewEndedChangeGroup: function(sender, selection) {
        this._record.selectionAfter = selection;
        this._endTransaction();
    },

    textViewReplacedCharacters: function(sender, oldRange, characters) {
        if (!this._inTransaction) {
            throw "UndoController.textViewReplacedCharacters() called " +
                "outside a transaction";
        }

        this._record.patches.push({
            oldCharacters:  this._deletedCharacters,
            oldRange:       oldRange,
            newCharacters:  characters,
            newRange:       this.getPath('textView.layoutManager.textStorage').
                            resultingRangeForReplacement(oldRange,
                            characters.split("\n"))
        });

        this._deletedCharacters = null;
    },

    textViewWillReplaceRange: function(sender, oldRange) {
        if (!this._inTransaction) {
            throw "UndoController.textViewWillReplaceRange() called outside " +
                "a transaction";
        }

        this._deletedCharacters = this.getPath('textView.layoutManager.' +
            'textStorage').getCharacters(oldRange);
    },

    undo: function(record) {
        return this._undoOrRedo(record.patches.map(function(patch) {
                return {
                    oldCharacters:  patch.newCharacters,
                    oldRange:       patch.newRange,
                    newCharacters:  patch.oldCharacters,
                    newRange:       patch.oldRange
                };
            }), record.selectionBefore);
    }
});


});

tiki.module("Editor:mixins/multidelegate",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;

/**
 * @namespace
 *
 * This mixin provides support for delegate objects. It's similar to
 * SC.DelegateSupport but is simpler and allows multiple delegates.
 */
exports.MultiDelegateSupport = {
    /**
     * @property{Array}
     *
     * The set of delegates.
     */
    delegates: [],

    /**
     * Adds a delegate to the list of delegates.
     */
    addDelegate: function(delegate) {
        this.set('delegates', this.get('delegates').concat(delegate));
    },

    /**
     * @protected
     *
     * For each delegate that implements the given method, calls it, passing
     * this object as the first parameter along with any other parameters
     * specified.
     */
    notifyDelegates: function(method) {
        var args = [ this ];
        for (var i = 1; i < arguments.length; i++) {
            args.push(arguments[i]);
        }

        this.get('delegates').forEach(function(delegate) {
            if (delegate.respondsTo(method)) {
                delegate[method].apply(delegate, args);
            }
        });
    }
};


});

tiki.module("Editor:mixins/textbuffer",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;

/**
 * @namespace
 *
 * This mixin provides support for mutable text buffer objects (currently, the
 * text storage and undo controller). To use the mixin, incorporate it into a
 * class and implement replaceCharacters().
 */
exports.TextBuffer = {
    /**
     * Deletes all characters in the range.
     */
    deleteCharacters: function(range) {
        this.replaceCharacters(range, "");
    },

    /**
     * Inserts characters at the given position.
     */
    insertCharacters: function(position, characters) {
        this.replaceCharacters({ start: position, end: position }, characters);
    },

    /**
     * @protected
     *
     * Returns the character range that would be modified if the range were
     * replaced with an array of lines.
     */
    resultingRangeForReplacement: function(range, lines) {
        var lineCount = lines.length;
        var lastLineLength = lines[lineCount - 1].length;
        var start = range.start;
        return {
            start:  start,
            end:    {
                row:    start.row + lineCount - 1,
                column: (lineCount === 1 ? start.column : 0) + lastLineLength
            }
        };
    }
};


});

tiki.module("Editor:models/textstorage",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var MultiDelegateSupport =
    require('mixins/multidelegate').MultiDelegateSupport;
var TextBuffer = require('mixins/textbuffer').TextBuffer;

exports.TextStorage = SC.Object.extend(MultiDelegateSupport, TextBuffer, {
    /**
     * @property{Array<String>}
     *
     * The list of lines, stored as an array of strings. Read-only.
     */
    lines: null,

    value: function(key, value) {
        if (value !== undefined) {
            this.replaceCharacters(this.range(), value);
        }
        return this.get('lines').join("\n");
    }.property('lines.[]'),

    /**
     * Returns the position of the nearest character to the given position,
     * according to the selection rules.
     */
    clampPosition: function(position) {
        var lines = this.get('lines');
        var row = position.row;
        if (row < 0) {
            return { row: 0, column: 0 };
        } else if (row >= lines.length) {
            return this.range().end;
        }

        return {
            row:    row,
            column: Math.max(0, Math.min(position.column, lines[row].length))
        };
    },

    /**
     * Returns the result of displacing the given position by one character
     * forward (if @count is 1) or backward (if @count is -1).
     */
    displacePosition: function(position, count) {
        var row = position.row, column = position.column;
        switch (count) {
        case -1:
            if (row === 0 && column == 0) {
                return position;
            }
            return column === 0 ?
                { row: row - 1, column: this.get('lines')[row - 1].length   } :
                { row: row,     column: column - 1                          };
        case 1:
            var lines = this.get('lines');
            var lineCount = lines.length, rowLength = lines[row].length;
            if (row === lineCount - 1 && column === rowLength) {
                return position;
            }
            return column === rowLength ?
                { row: row + 1, column: 0           } :
                { row: row,     column: column + 1  };
        default:
            throw "TextStorage.displacePosition(): count must be 1 or -1";
        }
    },

    /**
     * Returns the characters in the given range as a string.
     */
    getCharacters: function(range) {
        var lines = this.get('lines');
        var start = range.start, end = range.end;
        var startRow = start.row, endRow = end.row;
        var startColumn = start.column, endColumn = end.column;
        if (startRow === endRow) {
            return lines[startRow].substring(startColumn, endColumn);
        }
        return [ lines[startRow].substring(startColumn) ].
            concat(lines.slice(startRow + 1, endRow),
            lines[endRow].substring(0, endColumn)).join("\n");
    },

    init: function() {
        this.superclass();

        this.set('delegates', []);
        this.set('lines', [ "" ]);
    },

    /**
     * Returns the span of the entire text content.
     */
    range: function() {
        var lines = this.get('lines');
        return {
            start:  { row: 0, column: 0 },
            end:    {
                row:    lines.length - 1,
                column: lines[lines.length - 1].length
            }
        };
    },

    /**
     * Replaces the characters within the supplied range with the given string.
     */
    replaceCharacters: function(oldRange, characters) {
        var addedLines = characters.split("\n");
        var addedLineCount = addedLines.length;

        var newRange = this.resultingRangeForReplacement(oldRange, addedLines);

        var oldStart = oldRange.start, oldEnd = oldRange.end;
        var oldStartRow = oldStart.row, oldEndRow = oldEnd.row;
        var oldStartColumn = oldStart.column;

        var lines = this.get('lines');
        addedLines[0] = lines[oldStartRow].substring(0, oldStartColumn) +
            addedLines[0];
        addedLines[addedLineCount - 1] +=
            lines[oldEndRow].substring(oldEnd.column);

        lines.replace(oldStartRow, oldEndRow - oldStartRow + 1, addedLines);

        this.notifyDelegates('textStorageEdited', oldRange, newRange);
    }
});


});

tiki.module("Editor:utils/range",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;

/**
 * Returns the result of adding the two positions.
 */
exports.addPositions = function(a, b) {
    return { row: a.row + b.row, column: a.column + b.column };
};

/**
 * Given two positions a and b, returns a negative number if a < b, 0 if a = b,
 * or a positive number if a > b.
 */
exports.comparePositions = function(positionA, positionB) {
    var rowDiff = positionA.row - positionB.row;
    return rowDiff === 0 ? positionA.column - positionB.column : rowDiff;
};

exports.extendRange = function(range, delta) {
    var end = range.end;
    return {
        start:  range.start,
        end:    {
            row:    end.row + delta.row,
            column: end.column + delta.column
        }
    };
};

/**
 * Given two sets of ranges, returns the ranges of characters that exist in one
 * of the sets but not both.
 */
exports.intersectRangeSets = function(setA, setB) {
    var stackA = SC.clone(setA), stackB = SC.clone(setB);
    var result = [];
    while (stackA.length > 0 && stackB.length > 0) {
        var rangeA = stackA.shift(), rangeB = stackB.shift();
        var startDiff = exports.comparePositions(rangeA.start, rangeB.start);
        var endDiff = exports.comparePositions(rangeA.end, rangeB.end);

        if (exports.comparePositions(rangeA.end, rangeB.start) < 0) {
            // A is completely before B
            result.push(rangeA);
            stackB.unshift(rangeB);
        } else if (exports.comparePositions(rangeB.end, rangeA.start) < 0) {
            // B is completely before A
            result.push(rangeB);
            stackA.unshift(rangeA);
        } else if (startDiff < 0) {     // A starts before B
            result.push({ start: rangeA.start, end: rangeB.start });
            stackA.unshift({ start: rangeB.start, end: rangeA.end });
            stackB.unshift(rangeB);
        } else if (startDiff === 0) {   // A and B start at the same place
            if (endDiff < 0) {          // A ends before B
                stackB.unshift({ start: rangeA.end, end: rangeB.end });
            } else if (endDiff > 0) {   // A ends after B
                stackA.unshift({ start: rangeB.end, end: rangeA.end });
            }
        } else if (startDiff > 0) {     // A starts after B
            result.push({ start: rangeB.start, end: rangeA.start });
            stackA.unshift(rangeA);
            stackB.unshift({ start: rangeA.start, end: rangeB.end });
        }
    }
    return result.concat(stackA, stackB);
};

exports.isZeroLength = function(range) {
    return range.start.row === range.end.row &&
        range.start.column === range.end.column;
};

/**
 * Returns the greater of the two positions.
 */
exports.maxPosition = function(a, b) {
    return exports.comparePositions(a, b) > 0 ? a : b;
};

/**
 * Converts a range with swapped 'end' and 'start' values into one with the
 * values in the correct order.
 *
 * TODO: Unit test.
 */
exports.normalizeRange = function(range) {
    return this.comparePositions(range.start, range.end) < 0 ? range :
        { start: range.end, end: range.start };
};

/**
 * Returns a single range that spans the entire given set of ranges.
 */
exports.rangeSetBoundaries = function(rangeSet) {
    return {
        start:  rangeSet[0].start,
        end:    rangeSet[rangeSet.length - 1].end
    };
};

exports.toString = function(range) {
    var start = range.start, end = range.end;
    return "[ %@,%@ %@,%@ ]".fmt(start.row, start.column, end.row, end.column);
};

/**
 * Returns the union of the two ranges.
 */
exports.unionRanges = function(a, b) {
    return {
        start:  a.start.row < b.start.row ||
            (a.start.row === b.start.row && a.start.column < b.start.column) ?
            a.start : b.start,
        end:    a.end.row > b.end.row ||
            (a.end.row === b.end.row && a.end.column > b.end.column) ?
            a.end : b.end
    };
};


});

tiki.module("Editor:utils/rect",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;

/**
 * @private
 *
 * Returns the distance between the given value and the given inclusive upper
 * and lower bounds, or 0 if the value lies between them.
 *
 * Exported so that the function can be unit tested.
 */
exports._distanceFromBounds = function(value, low, high) {
    if (value < low) {
        return value - low;
    }
    if (value >= high) {
        return value - high;
    }
    return 0;
};

/**
 * Merges the given rectangle with the given set and returns the resulting set
 * of non-overlapping rectangles.
 */
exports.addRectToSet = function(set, rect) {
    set = set.concat(rect);

    var modified;
    do {
        modified = false;
        var newSet = [];

        for (var i = 0; i < set.length; i++) {
            var rectA = set[i];
            newSet.push(rectA);
            for (var j = i+1; j < set.length; j++) {
                var rectB = set[j];
                if (!exports.rectsIntersect(rectA, rectB)) {
                    continue;
                }

                set.removeAt(j, 1);

                // There's room for optimization here...
                newSet[newSet.length - 1] = SC.unionRects(rectA, rectB);

                modified = true;
                break;
            }
        }

        set = newSet;
    } while (modified);

    return set;
};

/**
 * Returns the vector representing the shortest offset between the given
 * rectangle and the given point.
 */
exports.offsetFromRect = function(rect, point) {
    return {
        x: exports._distanceFromBounds(point.x, rect.x, SC.maxX(rect)),
        y: exports._distanceFromBounds(point.y, rect.y, SC.maxY(rect))
    };
};

/**
 * Returns true if the rectanges intersect or false otherwise. Adjacent
 * rectangles don't count; they must actually overlap some region.
 */
exports.rectsIntersect = function(a, b) {
    var intersection = SC.intersectRects(a, b);
    return intersection.width !== 0 && intersection.height !== 0;
};


});

tiki.module("Editor:views/canvas",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var Rect = require('utils/rect');

/**
 * @class
 *
 * This class provides support for manual scrolling and positioning for canvas-
 * based elements. Getting these elements to play nicely with SproutCore is
 * tricky and error-prone, so all canvas-based views should consider deriving
 * from this class. Derived views should implement drawRect() in order to
 * perform the appropriate canvas drawing logic.
 *
 * The actual size of the canvas is always the size of the container the canvas
 * view is placed in.
 */
exports.CanvasView = SC.View.extend({
    _canvasDom: null,
    _canvasId: null,
    _invalidRects: null,
    _lastRedrawTime: null,
    _previousClippingFrame: null,
    _redrawTimer: null,

    _clippingFrameChanged: function() {
        // False positives here are very common, so check to make sure before
        // we take the slow path.
        var previousClippingFrame = this._previousClippingFrame;
        var clippingFrame = this.get('clippingFrame');
        if (previousClippingFrame === null ||
                !SC.rectsEqual(clippingFrame, previousClippingFrame)) {
            this._previousClippingFrame = clippingFrame;
            this.setNeedsDisplay();
        }
    }.observes('clippingFrame'),

    _isVisibleInWindowChanged: function() {
        if (this.get('isVisibleInWindow')) {
            this.redraw();
        }
    }.observes('isVisibleInWindow'),

    _layoutChanged: function() {
        this._resizeToFit();
    }.observes('layout'),

    _parentViewFrameChanged: function() {
        this._resizeToFit();
    },

    _resizeToFit: function() {
        var parentFrame = this.getPath('parentView.frame');
        var frame = this.get('frame');
        var frameWidth = frame.width, frameHeight = frame.height;
        var parentWidth = parentFrame.width, parentHeight = parentFrame.height;
        if (frameWidth >= parentWidth && frameHeight >= parentHeight) {
            return;
        }

        this.set('layout', {
            left:   frame.x,
            top:    frame.y,
            width:  Math.max(frameWidth, parentWidth),
            height: Math.max(frameHeight, parentHeight)
        });
    },

    /**
     * @property{2DContext}
     *
     * Cache for the canvas' 2d context.
     */
    canvasContext2D: function() {
        return this.get('_canvasDom').getContext('2d')
    }.property('_canvasDom').cacheable(),

    layoutStyle: { left: "0px", top: "0px" },

    /**
     * @property{Number}
     *
     * The minimum delay between canvas redraws in milliseconds, equal to 1000
     * divided by the desired number of frames per second.
     */
    minimumRedrawDelay: 1000.0 / 30.0,

    init: function() {
        arguments.callee.base.apply(this, arguments);
        this._invalidRects = [];
        this.get('parentView').addObserver('frame', this,
            this._parentViewFrameChanged);
    },

    /**
     * Calls drawRect() on all the invalid rects to redraw the canvas contents.
     * Generally, you should not need to call this function unless you override
     * the default implementations of didCreateLayer() or render().
     *
     * @return True on a successful render, or false if the rendering wasn't
     *   done because the view wasn't visible.
     */
    redraw: function() {
        if (!this.get('isVisibleInWindow')) {
            return false;
        }

        var frame = this.get('frame');
        if (frame.x < 0) {
            this.computeFrameWithParentFrame(null);
        }

        var context = this.get('canvasContext2D');
        context.save();
        context.translate(frame.x, frame.y);

        var clippingFrame = this.get('clippingFrame');
        this._invalidRects.forEach(function(rect) {
            context.save();

            rect = SC.intersectRects(rect, clippingFrame);
            if (rect.width !== 0 && rect.height !== 0) {
                var x = rect.x, y = rect.y;
                var width = rect.width, height = rect.height;
                context.beginPath();
                context.moveTo(x, y);
                context.lineTo(x + width, y);
                context.lineTo(x + width, y + height);
                context.lineTo(x, y + height);
                context.closePath();
                context.clip();

                this.drawRect(rect, context);
            }

            context.restore();
        }, this);

        context.restore();

        this._invalidRects = [];
        this._lastRedrawTime = new Date().getTime();
        return true;
    },

    renderLayout: function(context, firstTime) {
        arguments.callee.base.apply(this, arguments);

        var parentFrame = this.getPath('parentView.frame');
        if (firstTime) {
            context.attr('width', parentFrame.width);
            context.attr('height', parentFrame.height);
            return;
        }

        var canvas = this._canvasDom;
        var widthChanged = canvas.width !== parentFrame.width;
        var heightChanged = canvas.height !== parentFrame.height;
        if (widthChanged) {
            canvas.width = parentFrame.width;
        }
        if (heightChanged) {
            canvas.height = parentFrame.height;
        }
        if (widthChanged || heightChanged) {
            this.setNeedsDisplay();
        }
    },

    /**
     * Subclasses should override this method to perform any drawing that they
     * need to.
     *
     * @param rect{Rect} The rectangle to draw in.
     * @param context{CanvasRenderingContext2D} The 2D graphics context, taken
     *   from the canvas.
     */
    drawRect: function(rect, context) {
        // empty
    },

    didCreateLayer: function() {
        arguments.callee.base.apply(this, arguments);
        this.set('_canvasDom', this.$("#" + this._canvasId)[0]);
    },

    render: function(context, firstTime) {
        arguments.callee.base.apply(this, arguments);

        if (firstTime) {
            var parentFrame = this.getPath('parentView.frame');
            var canvasContext = context.begin("canvas");
            this._canvasId = SC.guidFor(canvasContext);
            canvasContext.id(this._canvasId);
            canvasContext.attr("width", "" + parentFrame.width);
            canvasContext.attr("height", "" + parentFrame.height);
            canvasContext.push("canvas tag not supported by your browser");
            canvasContext.end();
            return;
        }

        // The render() method can actually get called multiple times during a
        // run loop, because other calls to render() can be queued after the
        // first render() runs. This workaround forces tryRedraw() to be called
        // only once per run loop.
        SC.RunLoop.currentRunLoop.invokeLast(this, this.tryRedraw);
    },

    /**
     * Invalidates the entire visible region of the canvas.
     */
    setNeedsDisplay: function(rect) {
        var frame = this.get('frame');
        this._invalidRects = [
            {
                x:      0,
                y:      0,
                width:  frame.width,
                height: frame.height
            }
        ];
        this.set('layerNeedsUpdate', true);
    },

    /**
     * Invalidates the given rect of the canvas, and schedules that portion of
     * the canvas to be redrawn at the end of the run loop.
     */
    setNeedsDisplayInRect: function(rect) {
        this._invalidRects = Rect.addRectToSet(this._invalidRects, rect);
        this.set('layerNeedsUpdate', true);
    },

    tryRedraw: function(context, firstTime) {
        var now = new Date().getTime();
        var lastRedrawTime = this._lastRedrawTime;
        var minimumRedrawDelay = this.get('minimumRedrawDelay');

        if (lastRedrawTime === null ||
                now - lastRedrawTime >= minimumRedrawDelay) {
            this.redraw();
            return;
        }

        var redrawTimer = this._redrawTimer;
        if (redrawTimer !== null && redrawTimer.get('isValid')) {
            return; // already scheduled
        }

        this._redrawTimer = SC.Timer.schedule({
            target:     this,
            action:     this.redraw,
            interval:   minimumRedrawDelay,
            repeats:    false
        });
    }
});


});

tiki.module("Editor:views/editor",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var GutterView = require('views/gutter').GutterView;
var LayoutManager = require('controllers/layoutmanager').LayoutManager;
var ScrollView = require('views/scroll').ScrollView;
var TextView = require('views/text').TextView;
var EditorUndoController = require('controllers/undo').EditorUndoController;

/**
 * @class
 *
 * A view responsible for laying out a scrollable text view and its associated
 * gutter view, as well as maintaining a layout manager.
 */
exports.EditorView = SC.View.extend(SC.Border, {
    borderStyle: SC.BORDER_GRAY,

    /**
     * @property
     *
     * The gutter view class to use. This field will be instantiated when the
     * child views are created.
     */
    gutterView: GutterView,

    /**
     * @property
     *
     * The layout manager class to use. This field will be instantiated when
     * this object is.
     */
    layoutManager: LayoutManager,

    /**
     * @property
     *
     * The scroll view class to use. This field will be instantiated when the
     * child views are created.
     */
    scrollView: ScrollView,

    /**
     * @property
     *
     * The text view class to use. This field will be instantiated when the
     * child views are created.
     */
    textView: TextView,

    /**
     * @property{EditorUndoController}
     *
     * The undo controller class to use. This field will be instantiated when
     * the child views are created.
     */
    undoController: EditorUndoController,

    _gutterViewFrameChanged: function() {
        this.get('scrollView').adjust({
            left: this.getPath('gutterView.frame').width
        });
    },

    _scrollViewVerticalScrollOffsetChanged: function() {
        // FIXME: property binding?
        this.setPath('gutterView.verticalScrollOffset',
            this.getPath('scrollView.verticalScrollOffset'));
    },

    createChildViews: function() {
        var layoutManager = this.get('layoutManager');

        var scrollViewClass = this.get('scrollView');

        var gutterView = this.createChildView(this.get('gutterView'), {
            layoutManager: layoutManager
        });
        this.set('gutterView', gutterView);
        gutterView.addObserver('frame', this, this._gutterViewFrameChanged);

        var textViewClass = this.get('textView');
        var scrollView = this.createChildView(scrollViewClass, {
            contentView: textViewClass.extend({
                layoutManager: layoutManager,
                undoController: this.get('undoController')
            }),
            layout: {
                left:   gutterView.get('frame').width,
                bottom: 0,
                top:    0,
                right:  0
            }
        });
        this.set('scrollView', scrollView);
        scrollView.addObserver('verticalScrollOffset', this,
            this._scrollViewVerticalScrollOffsetChanged);

        var textView = scrollView.get('contentView');
        this.set('textView', textView);

        this.set('undoController', this.get('undoController').create({
            textView: textView
        }));

        this.set('childViews', [ gutterView, scrollView ]);
    },

    init: function() {
        this.set('layoutManager', this.get('layoutManager').create());
        return arguments.callee.base.apply(this, arguments);
    }
});


});

tiki.module("Editor:views/gutter",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var CanvasView = require('views/canvas').CanvasView;
var m_scratchcanvas = require('bespin:util/scratchcanvas');

var InteriorGutterView = CanvasView.extend({
    // TODO: calculate from the size or let the user override via themes if
    // desired
    _lineAscent: 16,

    /**
     * @property
     * Theme information for the gutter. Currently exposed properties are
     * gutterStyle, lineNumberColor, and lineNumberFont.
     *
     * TODO: Convert to SproutCore's theme system.
     */
    theme: {
        gutterStyle: "#4c4a41",
        lineNumberColor: "#e5c138",
        lineNumberFont: "10pt Monaco, Lucida Console, monospace",
        editorTextFont: "10pt Monaco, Lucida Console, monospace"
    },

    _frameChanged: function() {
        // We have to be more aggressive than the canvas view alone would be,
        // because of the possibility that we will have to draw additional line
        // numbers in the gutter when the height of the text changes.
        this.setNeedsDisplay();
    }.observes('frame'),

    drawRect: function(rect, context) {
        var theme = this.get('theme');
        context.fillStyle = theme.gutterStyle;
        context.fillRect(rect.x, rect.y, rect.width, rect.height);

        context.save();

        var parentView = this.get('parentView');
        var padding = parentView.get('padding');
        context.translate(padding.left, 0);

        context.fillStyle = theme.lineNumberColor;
        context.font = theme.lineNumberFont;

        var layoutManager = parentView.get('layoutManager');
        var range = layoutManager.characterRangeForBoundingRect(rect);
        var endRow = Math.min(range.end.row,
            layoutManager.get('textLines').length - 1);
        var lineAscent = this._lineAscent;
        for (var row = range.start.row; row <= endRow; row++) {
            // TODO: breakpoints
            context.fillText("" + (row + 1), -0.5,
                layoutManager.lineRectForRow(row).y + lineAscent - 0.5);
        }

        context.restore();
    }
});

exports.GutterView = SC.View.extend({
    _interiorView: null,

    layout: { left: 0, top: 0, bottom: 0, width: 32 },

    /**
     * @property{LayoutManager}
     *
     * The layout manager to monitor. This property must be filled in upon
     * instantiating the gutter view.
     */
    layoutManager: null,

    /**
     * @property
     *
     * The amount of padding to leave on the sides of the gutter, given as an
     * object with "bottom", "left", and "right" properties.
     */
    padding: { bottom: 30, left: 5, right: 10 },

    /**
     * @property{number}
     *
     * The amount by which the user has scrolled the neighboring editor in
     * pixels.
     */
    verticalScrollOffset: 0,

    _recomputeLayout: function() {
        var layoutManager = this.get('layoutManager');
        var padding = this.get('padding');

        var width = 32;
        // padding.left + padding.right + m_scratchcanvas.get().getContext().
        // measureStringWidth(this.get('theme').lineNumberFont,
        // "" + (layoutManager.get('textLines').length + 1))

        this.set('layout', SC.mixin(SC.clone(this.get('layout')), {
            width: width
        }));
        this._interiorView.set('layout', {
            left:   0,
            top:    -this.get('verticalScrollOffset'),
            width:  this.get('frame').width,
            height: layoutManager.boundingRect().height + padding.bottom
        });
    },

    _verticalScrollOffsetChanged: function() {
        this._recomputeLayout();
    }.observes('verticalScrollOffset'),

    createChildViews: function() {
        var interiorView = this.createChildView(InteriorGutterView);
        this._interiorView = interiorView;

        var frame = this.get('frame');
        this._recomputeLayout();

        this.set('childViews', [ interiorView ]);
    },

    init: function() {
        arguments.callee.base.apply(this, arguments);

        this.get('layoutManager').addDelegate(this);
    },

    layoutManagerInvalidatedRects: function(sender, rects) {
        this._recomputeLayout();
    }


});


});

tiki.module("Editor:views/scroll",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var BespinScrollerView = require('views/scroller').BespinScrollerView;

exports.ScrollView = SC.ScrollView.extend({
    _containerViewLaidOut: false,

    autohidesHorizontalScroller: false,
    autohidesVerticalScroller: false,
    borderStyle: SC.BORDER_NONE,
    hasHorizontalScroller: true,
    hasVerticalScroller: true,
    horizontalScrollerThickness: 24,
    horizontalScrollerView: BespinScrollerView,
    verticalScrollerThickness: 24,
    verticalScrollerView: BespinScrollerView,

    tile: function() {
        if (!this._containerViewLaidOut) {
            var containerView = this.get('containerView');
            containerView.adjust({ left: 0, bottom: 0, top: 0, right: 0 });
            containerView.updateLayout();
            this._containerViewLaidOut = true;
        }

        var hScroller = this.get('horizontalScrollerView');
        var vScroller = this.get('verticalScrollerView');
        var hScrollerVisible = this.get('isHorizontalScrollerVisible');
        var vScrollerVisible = this.get('isVerticalScrollerVisible');

        var hScrollerThickness = this.get('horizontalScrollerThickness');
        var vScrollerThickness = this.get('verticalScrollerThickness');
        if (hScrollerVisible) {
            hScroller = this.get('horizontalScrollerView');
            hScroller.set('scrollerThickness', hScrollerThickness);
            hScroller.set('padding', {
                top:    0,
                bottom: 6,
                left:   6,
                right:  6 + vScrollerThickness
            });
            hScroller.set('layout', {
                left:   0,
                bottom: 0,
                right:  0,
                height: hScrollerThickness
            });
        }
        if (vScrollerVisible) {
            vScroller = this.get('verticalScrollerView');
            vScroller.set('scrollerThickness', vScrollerThickness);
            vScroller.set('padding', {
                left:   0,
                right:  6,
                top:    6,
                bottom: 6 + hScrollerThickness
            });
            vScroller.set('layout', {
                top:    0,
                right:  0,
                bottom: 0,
                width:  vScrollerThickness
            });
        }

        var contentView = this.get('contentView');
        if (contentView.get('hasPadding') === true) {
            this.get('contentView').set('padding', {
                bottom: hScrollerThickness + 6,
                right:  vScrollerThickness + 6
            });
        }
    }
});


});

tiki.module("Editor:views/scroller",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var CanvasView = require('views/canvas').CanvasView;

var LINE_HEIGHT                 = 15;
var MINIMUM_HANDLE_SIZE         = 20;
var NIB_ARROW_PADDING_BEFORE    = 3;
var NIB_ARROW_PADDING_AFTER     = 5;
var NIB_LENGTH                  = 15;
var NIB_PADDING                 = 8;    // 15/2

// The fancy custom Bespin scroll bars.
var ScrollerCanvasView = CanvasView.extend({
    classNames: ['bespin-scroller-view'],

    _mouseDownScreenPoint: null,
    _mouseDownValue: null,
    _isMouseOver: false,

    // TODO: Make this a real SproutCore theme (i.e. an identifier that gets
    // prepended to CSS properties), perhaps?
    theme: {
        backgroundStyle: "#2A211C",
        partialNibStyle: "rgba(100, 100, 100, 0.3)",
        partialNibArrowStyle: "rgba(255, 255, 255, 0.3)",
        partialNibStrokeStyle: "rgba(150, 150, 150, 0.3)",
        fullNibStyle: "rgb(100, 100, 100)",
        fullNibArrowStyle: "rgb(255, 255, 255)",
        fullNibStrokeStyle: "rgb(150, 150, 150)",
        scrollTrackFillStyle: "rgba(50, 50, 50, 0.8)",
        scrollTrackStrokeStyle: "rgb(150, 150, 150)",
        scrollBarFillStyle: "rgba(0, 0, 0, %a)",
        scrollBarFillGradientTopStart: "rgba(90, 90, 90, %a)",
        scrollBarFillGradientTopStop: "rgba(40, 40, 40, %a)",
        scrollBarFillGradientBottomStart: "rgba(22, 22, 22, %a)",
        scrollBarFillGradientBottomStop: "rgba(44, 44, 44, %a)"
    },

    _drawNib: function(ctx) {
        var theme = this.get('theme');
        var fillStyle, arrowStyle, strokeStyle;
        if (this._isMouseOver) {
            fillStyle   = theme.fullNibStyle;
            arrowStyle  = theme.fullNibArrowStyle;
            strokeStyle = theme.fullNibStrokeStyle;
        } else {
            fillStyle   = theme.partialNibStyle;
            arrowStyle  = theme.partialNibArrowStyle;
            strokeStyle = theme.partialNibStrokeStyle;
        }

        var midpoint = Math.floor(NIB_LENGTH / 2);

        ctx.fillStyle = fillStyle;
        ctx.beginPath();
        ctx.arc(0, 0, Math.floor(NIB_LENGTH / 2), 0, Math.PI * 2, true);
        ctx.closePath();
        ctx.fill();
        ctx.strokeStyle = strokeStyle;
        ctx.stroke();

        ctx.fillStyle = arrowStyle;
        ctx.beginPath();
        ctx.moveTo(0, -midpoint + NIB_ARROW_PADDING_BEFORE);
        ctx.lineTo(-midpoint + NIB_ARROW_PADDING_BEFORE,
            midpoint - NIB_ARROW_PADDING_AFTER);
        ctx.lineTo(midpoint - NIB_ARROW_PADDING_BEFORE,
            midpoint - NIB_ARROW_PADDING_AFTER);
        ctx.closePath();
        ctx.fill();
    },

    _drawNibs: function(ctx) {
        var isMouseOver = this._isMouseOver;
        var thickness = this._getClientThickness();
        var value = this.getPath('parentView.value');

        // Starting nib
        if (isMouseOver || value !== 0) {
            ctx.save();
            ctx.translate(NIB_PADDING, thickness / 2);
            ctx.rotate(Math.PI * 1.5);
            ctx.moveTo(0, 0);
            this._drawNib(ctx);
            ctx.restore();
        }

        // Ending nib
        if (isMouseOver || value !== this.getMaximumValue()) {
            ctx.save();
            ctx.translate(this._getClientLength() - NIB_PADDING,
                thickness / 2);
            ctx.rotate(Math.PI * 0.5);
            ctx.moveTo(0, 0);
            this._drawNib(ctx);
            ctx.restore();
        }
    },

    // Returns the frame of the scroll bar, not counting any padding.
    _getClientFrame: function() {
        var frame = this.get('frame');
        var padding = this.getPath('parentView.padding');
        return {
            x:      padding.left,
            y:      padding.top,
            width:  frame.width - (padding.left + padding.right),
            height: frame.height - (padding.top + padding.bottom)
        };
    },

    // Returns the length of the scroll bar, not counting any padding. Equal to
    // the width or height of the client frame, depending on the layout
    // direction.
    _getClientLength: function() {
        var clientFrame = this._getClientFrame();
        switch (this.getPath('parentView.layoutDirection')) {
        case SC.LAYOUT_HORIZONTAL:
            return clientFrame.width;
        case SC.LAYOUT_VERTICAL:
            return clientFrame.height;
        default:
            console.assert(false, "unknown layout direction");
            return null;
        }
    },

    // Returns the thickness of the scroll bar, not counting any padding.
    _getClientThickness: function() {
        var parentView = this.get('parentView');
        var padding = parentView.get('padding');
        var scrollerThickness = parentView.get('scrollerThickness');

        switch (parentView.get('layoutDirection')) {
        case SC.LAYOUT_VERTICAL:
            return scrollerThickness - (padding.left + padding.right);
        case SC.LAYOUT_HORIZONTAL:
            return scrollerThickness - (padding.top + padding.bottom);
        default:
            console.assert(false, "unknown layout direction");
            return null;
        }
    }.property(),

    // The length of the scroll bar, counting the padding. Equal to frame.width
    // or frame.height, depending on the layout direction of the bar.
    // Read-only.
    _getFrameLength: function() {
        var frame = this.get('frame');
        switch (this.getPath('parentView.layoutDirection')) {
        case SC.LAYOUT_HORIZONTAL:
            return frame.width;
        case SC.LAYOUT_VERTICAL:
            return frame.height;
        default:
            console.assert(false, "unknown layout direction");
            return null;
        }
    },

    // The dimensions of the gutter (the middle area between the buttons, which
    // contains the handle or knob).
    _getGutterFrame: function() {
        var clientFrame = this._getClientFrame();
        var thickness = this._getClientThickness();
        switch (this.getPath('parentView.layoutDirection')) {
        case SC.LAYOUT_VERTICAL:
            return {
                x:      clientFrame.x,
                y:      clientFrame.y + NIB_LENGTH,
                width:  thickness,
                height: Math.max(0, clientFrame.height - 2*NIB_LENGTH)
            };
        case SC.LAYOUT_HORIZONTAL:
            return {
                x:      clientFrame.x + NIB_LENGTH,
                y:      clientFrame.y,
                width:  Math.max(0, clientFrame.width - 2*NIB_LENGTH),
                height: thickness
            };
        default:
            console.assert(false, "unknown layout direction");
            return null;
        }
    },

    // The length of the gutter, equal to gutterFrame.width or
    // gutterFrame.height depending on the scroll bar's layout direction.
    _getGutterLength: function() {
        var gutterFrame = this._getGutterFrame();
        var gutterLength;
        switch (this.getPath('parentView.layoutDirection')) {
        case SC.LAYOUT_HORIZONTAL:
            gutterLength = gutterFrame.width;
            break;
        case SC.LAYOUT_VERTICAL:
            gutterLength = gutterFrame.height;
            break;
        default:
            console.assert(false, "unknown layout direction");
            break;
        }
        return gutterLength;
    },

    // Returns the dimensions of the handle or knob.
    _getHandleFrame: function() {
        var parentView = this.get('parentView');
        var value = parentView.get('value');
        var maximum = parentView.get('maximum');
        var frame = this.get('frame');
        var clientFrame = this._getClientFrame();
        var gutterFrame = this._getGutterFrame();
        var clientThickness = this._getClientThickness();

        switch (parentView.get('layoutDirection')) {
        case SC.LAYOUT_VERTICAL:
            return {
                x:      clientFrame.x,
                y:      clientFrame.y + NIB_LENGTH +
                        value * gutterFrame.height / maximum,
                width:  clientThickness,
                height: Math.min(frame.height, maximum) * gutterFrame.height /
                        maximum
            };
        case SC.LAYOUT_HORIZONTAL:
            return {
                x:      clientFrame.x + NIB_LENGTH +
                        value * gutterFrame.width / maximum,
                y:      clientFrame.y,
                width:  Math.min(frame.width, maximum) * gutterFrame.width /
                        maximum,
                height: clientThickness
            };
        default:
            console.assert(false, "unknown layout direction");
            return null;
        }
    },

    _segmentForMouseEvent: function(evt) {
        var point = this.convertFrameFromView({ x: evt.pageX, y: evt.pageY });
        var clientFrame = this._getClientFrame();

        if (!SC.pointInRect(point, clientFrame)) {
            return null;
        }

        var layoutDirection = this.getPath('parentView.layoutDirection');
        switch (layoutDirection) {
        case SC.LAYOUT_HORIZONTAL:
            if (point.x < NIB_LENGTH) {
                return 'nib-start';
            } else if (point.x >= clientFrame.width - NIB_LENGTH) {
                return 'nib-end';
            }
            break;
        case SC.LAYOUT_VERTICAL:
            if (point.y < NIB_LENGTH) {
                return 'nib-start';
            } else if (point.y >= clientFrame.height - NIB_LENGTH) {
                return 'nib-end';
            }
            break;
        default:
            console.assert(false, "unknown layout direction");
            break;
        }

        var handleFrame = this._getHandleFrame();
        if (SC.pointInRect(point, handleFrame)) {
            return 'handle';
        }

        switch (layoutDirection) {
        case SC.LAYOUT_HORIZONTAL:
            if (point.x < handleFrame.x) {
                return 'gutter-before';
            } else if (point.x >= handleFrame.x + handleFrame.width) {
                return 'gutter-after';
            }
            break;
        case SC.LAYOUT_VERTICAL:
            if (point.y < handleFrame.y) {
                return 'gutter-before';
            } else if (point.y >= handleFrame.y + handleFrame.height) {
                return 'gutter-after';
            }
            break;
        default:
            console.assert(false, "unknown layout direction");
            break;
        }

        console.assert(false, "_segmentForMouseEvent: point ", point,
            " outside view with handle frame ", handleFrame,
            " and client frame ", clientFrame);
        return null;
    },

    /**
     * Adjusts the canvas view's frame to match the parent container's frame.
     */
    adjustFrame: function() {
        var parentFrame = this.getPath('parentView.frame');
        this.set('layout', {
            left:   0,
            top:    0,
            width:  parentFrame.width,
            height: parentFrame.height
        });
    },

    drawRect: function(rect, ctx) {
        var alpha = (ctx.globalAlpha) ? ctx.globalAlpha : 1;
        var theme = this.get('theme');

        var frame = this.get('frame');
        ctx.clearRect(0, 0, frame.width, frame.height);

        // Begin master drawing context
        ctx.save();

        // Translate so that we're only drawing in the padding.
        var parentView = this.get('parentView');
        var padding = parentView.get('padding');
        ctx.translate(padding.left, padding.top);

        var handleFrame = this._getHandleFrame();
        var gutterLength = this._getGutterLength();
        var thickness = this._getClientThickness();
        var halfThickness = thickness / 2;

        var layoutDirection = parentView.get('layoutDirection');
        var handleDistance, handleLength;
        switch (layoutDirection) {
        case SC.LAYOUT_VERTICAL:
            handleDistance = handleFrame.y - padding.top;
            handleLength = handleFrame.height;

            // The rest of the drawing code assumes the scroll bar is
            // horizontal. Create that fiction by installing a 90 degree
            // rotation.
            ctx.translate(thickness + 1, 0);
            ctx.rotate(Math.PI * 0.5);
            break;

        case SC.LAYOUT_HORIZONTAL:
            handleDistance = handleFrame.x - padding.left;
            handleLength = handleFrame.width;
            break;

        default:
            console.assert(false, "unknown layout direction");
            break;
        }

        if (gutterLength <= handleLength) {
            return; // Don't display the scroll bar.
        }

        if (this._isMouseOver === false) {
            ctx.globalAlpha = 0.3;
        } else {
            // Draw the scroll track rectangle.
            var clientLength = this._getClientLength();
            ctx.fillStyle = theme.scrollTrackFillStyle;
            ctx.fillRect(NIB_PADDING + 0.5, 0.5,
                clientLength - 2*NIB_PADDING, thickness - 1);
            ctx.strokeStyle = theme.scrollTrackStrokeStyle;
            ctx.strokeRect(NIB_PADDING + 0.5, 0.5,
                clientLength - 2*NIB_PADDING, thickness - 1);
        }

        var buildHandlePath = function() {
            ctx.beginPath();
            ctx.arc(handleDistance + halfThickness + 0.5,                // x
                halfThickness,                                     // y
                halfThickness - 0.5, Math.PI / 2, 3 * Math.PI / 2, false);
            ctx.arc(handleDistance + handleLength - halfThickness - 0.5, // x
                halfThickness,                                     // y
                halfThickness - 0.5, 3 * Math.PI / 2, Math.PI / 2, false);
            ctx.lineTo(handleDistance + halfThickness + 0.5, thickness - 0.5);
            ctx.closePath();
        };
        buildHandlePath();

        // Paint the interior of the handle path.
        var gradient = ctx.createLinearGradient(handleDistance, 0,
            handleDistance, thickness);
        gradient.addColorStop(0,
            theme.scrollBarFillGradientTopStart.replace(/%a/, alpha));
        gradient.addColorStop(0.4,
            theme.scrollBarFillGradientTopStop.replace(/%a/, alpha));
        gradient.addColorStop(0.41,
            theme.scrollBarFillStyle.replace(/%a/, alpha));
        gradient.addColorStop(0.8,
            theme.scrollBarFillGradientBottomStart.replace(/%a/, alpha));
        gradient.addColorStop(1,
            theme.scrollBarFillGradientBottomStop.replace(/%a/, alpha));
        ctx.fillStyle = gradient;
        ctx.fill();

        // Begin handle shine edge context
        ctx.save();
        ctx.clip();

        // Draw the little shines in the handle.
        ctx.fillStyle = theme.scrollBarFillStyle.replace(/%a/, alpha);
        ctx.beginPath();
        ctx.moveTo(handleDistance + halfThickness * 0.4, halfThickness * 0.6);
        ctx.lineTo(handleDistance + halfThickness * 0.9, thickness * 0.4);
        ctx.lineTo(handleDistance, thickness * 0.4);
        ctx.closePath();
        ctx.fill();
        ctx.beginPath();
        ctx.moveTo(handleDistance + handleLength - (halfThickness * 0.4),
            0 + (halfThickness * 0.6));
        ctx.lineTo(handleDistance + handleLength - (halfThickness * 0.9),
            0 + (thickness * 0.4));
        ctx.lineTo(handleDistance + handleLength, 0 + (thickness * 0.4));
        ctx.closePath();
        ctx.fill();

        ctx.restore();
        // End handle border context

        // Begin handle outline context
        ctx.save();
        buildHandlePath();
        ctx.strokeStyle = theme.scrollTrackStrokeStyle;
        ctx.stroke();
        ctx.restore();
        // End handle outline context

        if (this._isMouseOver === false) {
            ctx.globalAlpha = 1.0;
        }

        this._drawNibs(ctx);

        ctx.restore();
        // End master drawing context
    },

    /**
     * Returns the actual maximum value, which will be less than the maximum
     * due to accounting for the frame length.
     */
    getMaximumValue: function() {
        return Math.max(this.getPath('parentView.maximum') -
            this._getFrameLength(), 0);
    },

    mouseDown: function(evt) {
        var parentView = this.get('parentView');
        var value = parentView.get('value');
        var gutterLength = this._getGutterLength();

        switch (this._segmentForMouseEvent(evt)) {
        case 'nib-start':
            parentView.set('value', value - this.get('lineHeight'));
            break;
        case 'nib-end':
            parentView.set('value', value + this.get('lineHeight'));
            break;
        case 'gutter-before':
            parentView.set('value', value - gutterLength);
            break;
        case 'gutter-after':
            parentView.set('value', value + gutterLength);
            break;
        case 'handle':
            switch (parentView.get('layoutDirection')) {
            case SC.LAYOUT_HORIZONTAL:
                this._mouseDownScreenPoint = evt.clientX;
                break;
            case SC.LAYOUT_VERTICAL:
                this._mouseDownScreenPoint = evt.clientY;
                break;
            default:
                console.assert(false, "unknown layout direction");
                break;
            }
        default:
            console.assert("_segmentForMouseEvent returned an unknown value");
            break;
        }
    },

    mouseDragged: function(evt) {
        var parentView = this.get('parentView');

        var eventDistance;
        switch (parentView.get('layoutDirection')) {
        case SC.LAYOUT_HORIZONTAL:
            eventDistance = evt.clientX;
            break;
        case SC.LAYOUT_VERTICAL:
            eventDistance = evt.clientY;
            break;
        default:
            console.assert(false, "unknown layout direction");
            break;
        }

        var eventDelta = eventDistance - this._mouseDownScreenPoint;

        var maximum = parentView.get('maximum');
        var gutterLength = this._getGutterLength();

        var oldValue = parentView.get('value');
        parentView.set('value', oldValue + eventDelta * maximum /
            gutterLength);

        // If we didn't actually move, don't update the reference point.
        if (parentView.get('value') !== oldValue) {
            this._mouseDownScreenPoint = eventDistance;
        }
    },

    mouseEntered: function(evt) {
        this._isMouseOver = true;
        this.setNeedsDisplay();
    },

    mouseExited: function(evt) {
        this._isMouseOver = false;
        this.setNeedsDisplay();
    },

    mouseUp: function(evt) {
        this._mouseDownScreenPoint = null;
        this._mouseDownValue = null;
    },

    mouseWheel: function(evt) {
        var parentView = this.get('parentView');

        var delta;
        switch (parentView.get('layoutDirection')) {
        case SC.LAYOUT_HORIZONTAL:
            delta = evt.wheelDeltaX;
            break;
        case SC.LAYOUT_VERTICAL:
            delta = evt.wheelDeltaY;
            break;
        default:
            console.assert(false, "unknown layout direction");
            return;
        }

        parentView.set('value', parentView.get('value') + 2*delta);
    }
});

/**
 * @class
 *
 * A canvas-based scroller view.
 */
exports.BespinScrollerView = SC.View.extend({
    _scrollerCanvasView: null,

    _frameChanged: function() {
        this._scrollerCanvasView.adjustFrame();
    }.observes('frame'),

    _maximumChanged: function() {
        this._scrollerCanvasView.adjustFrame();
    }.observes('maximum'),

    _valueChanged: function() {
        var scrollerCanvasView = this._scrollerCanvasView;
        var maximumValue = scrollerCanvasView.getMaximumValue();

        var value = this.get('value');
        if (value < 0) {
            this.set('value', 0);
        } else if (value > maximumValue) {
            this.set('value', maximumValue);
        }

        scrollerCanvasView.setNeedsDisplay();
    }.observes('value'),

    /**
     * @property
     * Specifies the direction of the scroll bar: one of SC.LAYOUT_HORIZONTAL
     * or SC.LAYOUT_VERTICAL.
     *
     * Changes to this value after the view has been created have no effect.
     */
    layoutDirection: SC.LAYOUT_VERTICAL,

    /**
     * @property{Number}
     * The maximum value for the scroll bar.
     *
     * TODO: When set to a value less than the width or height of the knob, the
     * scroll bar is disabled.
     *
     */
    maximum: 0,

    /**
     * @property
     * The dimensions of transparent space inside the frame, given as an object
     * with 'left', 'bottom', 'top', and 'right' properties.
     *
     * Note that the scrollerThickness property includes the padding on the
     * sides of the bar.
     */
    padding: { left: 0, bottom: 0, top: 0, right: 0 },

    /**
     * @property
     * The thickness of this scroll bar. The default is
     * SC.NATURAL_SCROLLER_THICKNESS.
     */
    scrollerThickness: SC.NATURAL_SCROLLER_THICKNESS,

    /**
     * @property
     * The current position that the scroll bar is scrolled to.
     */
    value: 0,

    createChildViews: function() {
        var scrollerCanvasView = this.createChildView(ScrollerCanvasView);
        scrollerCanvasView.adjustFrame();
        this._scrollerCanvasView = scrollerCanvasView;

        this.set('childViews', [ scrollerCanvasView ]);
    }
});


});

tiki.module("Editor:views/text",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var SC = require('sproutcore/runtime').SC;
var CanvasView = require('views/canvas').CanvasView;
var LayoutManager = require('controllers/layoutmanager').LayoutManager;
var MultiDelegateSupport =
    require('mixins/multidelegate').MultiDelegateSupport;
var Range = require('utils/range');
var Rect = require('utils/rect');
var TextInput = require('bespin:editor/mixins/textinput').TextInput;
var keyboardManager = require('Canon:keyboard').keyboardManager;

exports.TextView = CanvasView.extend(MultiDelegateSupport, TextInput, {
    _dragPoint: null,
    _dragTimer: null,
    _inChangeGroup: false,

    // TODO: calculate from the size or let the user override via themes if
    // desired
    _lineAscent: 16,

    _selectedRange: null,
    _selectedRangeEndVirtual: null,

    _beginChangeGroup: function() {
        if (this._inChangeGroup) {
            throw "TextView._beginChangeGroup() called while already in a " +
                "change group";
        }

        this._inChangeGroup = true;
        this.notifyDelegates('textViewBeganChangeGroup', this._selectedRange);
    },

    _drag: function() {
        var point = this.convertFrameFromView(this._dragPoint);
        var offset = Rect.offsetFromRect(this.get('clippingFrame'), point);

        this._moveCursorTo(this._selectionPositionForPoint({
                x:  point.x - offset.x,
                y:  point.y - offset.y
            }), false, true);

        this.becomeFirstResponder();
    },

    // Draws a single insertion point.
    _drawInsertionPoint: function(rect, context) {
        var range = this._selectedRange;
        var characterRect = this.get('layoutManager').
            characterRectForPosition(range.start);

        context.save();

        context.strokeStyle = this.get('theme').cursorStyle;
        context.beginPath();
        context.moveTo(characterRect.x + 0.5, characterRect.y);
        context.lineTo(characterRect.x + 0.5,
            characterRect.y + characterRect.height);
        context.closePath();
        context.stroke();

        context.restore();
    },

    _drawLines: function(rect, context) {
        var layoutManager = this.get('layoutManager');
        var textLines = layoutManager.get('textLines');
        var theme = this.get('theme');
        var lineAscent = this._lineAscent;

        context.save();
        context.font = theme.editorTextFont;

        var range = layoutManager.characterRangeForBoundingRect(rect);
        var rangeStart = range.start, rangeEnd = range.end;
        var startRow = rangeStart.row, endRow = rangeEnd.row;
        for (var row = startRow; row <= endRow; row++) {
            var textLine = textLines[row];
            if (SC.none(textLine)) {
                continue;
            }

            // Clamp the start column and end column to fit within the line
            // text.
            var characters = textLine.characters;
            var length = characters.length;
            var endColumn = Math.min(rangeEnd.column, length);
            var startColumn = rangeStart.column;
            if (startColumn >= length) {
                continue;
            }

            // Figure out which color range to start in.
            var colorRanges = textLine.colors;
            var colorIndex = 0;
            while (startColumn < colorRanges[colorIndex].start) {
                colorIndex++;
            }

            // And finally draw the line.
            var column = startColumn;
            while (column < endColumn) {
                var colorRange = colorRanges[colorIndex];
                var colorRangeEnd = colorRange.end;
                context.fillStyle = colorRange.color;

                var characterRect = layoutManager.characterRectForPosition({
                    row:    row,
                    column: column
                });
                context.fillText(characters.substring(column, colorRangeEnd),
                    characterRect.x, characterRect.y + lineAscent);

                column = colorRangeEnd;
                colorIndex++;
            }
        }

        context.restore();
    },

    // Draws the background highlight for selections.
    _drawSelectionHighlight: function(rect, context) {
        var theme = this.get('theme');
        var fillStyle = this.get('isFirstResponder') ?
            theme.editorSelectedTextBackground :
            theme.unfocusedCursorFillStyle;
        var layoutManager = this.get('layoutManager');

        context.save();

        var range = Range.normalizeRange(this._selectedRange);
        context.fillStyle = fillStyle;
        layoutManager.rectsForRange(range).forEach(function(rect) {
            context.fillRect(rect.x, rect.y, rect.width, rect.height);
        });

        context.restore();
    },

    // Draws either the selection or the insertion point.
    _drawSelection: function(rect, context) {
        if (this._rangeIsInsertionPoint(this._selectedRange)) {
            this._drawInsertionPoint(rect, context);
        } else {
            this._drawSelectionHighlight(rect, context);
        }
    },

    _endChangeGroup: function() {
        if (!this._inChangeGroup) {
            throw "TextView._endChangeGroup() called while not in a change " +
                "group";
        }

        this._inChangeGroup = false;
        this.notifyDelegates('textViewEndedChangeGroup', this._selectedRange);
    },

    _getVirtualSelection: function(startPropertyAsWell) {
        return {
            start:  startPropertyAsWell && this._selectedRangeEndVirtual ?
                    this._selectedRangeEndVirtual : this._selectedRange.start,
            end:    this._selectedRangeEndVirtual || this._selectedRange.end
        };
    },

    // Replaces the selection with the given text and updates the selection
    // boundaries appropriately.
    _insertText: function(text) {
        this._beginChangeGroup();

        var textStorage = this.getPath('layoutManager.textStorage');
        var range = Range.normalizeRange(this._selectedRange);

        this._replaceCharacters(range, text);

        // Update the selection to point immediately after the inserted text.
        var lines = text.split("\n");
        this._moveCursorTo(lines.length > 1 ?
            {
                row:    range.start.row + lines.length - 1,
                column: lines[lines.length - 1].length
            } :
            Range.addPositions(range.start, { row: 0, column: text.length }));

        this._endChangeGroup();
    },

    _invalidateInsertionPointIfNecessary: function(range) {
        if (!this._rangeIsInsertionPoint(range)) {
            return;
        }

        var rect = this.get('layoutManager').
            characterRectForPosition(range.start);
        this.setNeedsDisplayInRect({
            x:      rect.x,
            y:      rect.y,
            width:  1,
            height: rect.height
        });
    },

    _isDelimiter: function(character) {
        return [
            "=", " ", "\t", ">", "<", ".", ",", "(", ")", "{", "}", ":", '"',
            "'", ";"
        ].indexOf(character) > -1;
    },

    _moveCursorTo: function(position, doSaveVirtualEnd, selection) {
        var positionToUse;
        var textStorage = this.getPath('layoutManager.textStorage');

        positionToUse = textStorage.clampPosition(position);

        this.setSelection({
            start:  selection ? this._selectedRange.start : positionToUse,
            end:    positionToUse
        });

        if (doSaveVirtualEnd) {
            if (position.row > 0 && position.row < textStorage.lines.length) {
                this._selectedRangeEndVirtual = position;
            } else {
                this._selectedRangeEndVirtual = position;
            }
        } else {
            this._selectedRangeEndVirtual = null;
        }

        this._scrollToPosition(this._selectedRange.end);
    },

    _moveOrSelectEnd: function(shift, inLine) {
        var lines = this.getPath('layoutManager.textStorage.lines');
        var row = inLine ? this._selectedRange.end.row : lines.length - 1;
        this._moveCursorTo({ row: row, column: lines[row].length }, false,
            shift);
    },

    _moveOrSelectStart: function(shift, inLine) {
        var range = this._selectedRange;
        var row = inLine ? range.end.row : 0;
        var position = {
            row: row,
            column: 0
        };

        this._moveCursorTo(position, false, shift);
    },

    _performBackspaceOrDelete: function(isBackspace) {
        this._beginChangeGroup();

        var textStorage = this.getPath('layoutManager.textStorage');
        var lines = textStorage.get('lines');

        // If the selection is an insertion point...
        var range = Range.normalizeRange(this._selectedRange);
        if (this._rangeIsInsertionPoint(range)) {
            if (isBackspace) {
                // ... extend it backward by one character.
                range = {
                    start:  textStorage.displacePosition(range.start, -1),
                    end:    range.end
                };
            } else {
                // ... otherwise, extend it forward by one character.
                range = {
                    start:  range.start,
                    end:    textStorage.displacePosition(range.end, 1)
                };
            }
        }

        this._replaceCharacters(range, "");

        // Position the insertion point at the start of all the ranges that
        // were just deleted.
        this._moveCursorTo(range.start);

        this._endChangeGroup();
    },

    _performVerticalKeyboardSelection: function(offset) {
        var textStorage = this.getPath('layoutManager.textStorage');
        var oldPosition = this._selectedRangeEndVirtual !== null ?
            this._selectedRangeEndVirtual : this._selectedRange.end;
        var newPosition = Range.addPositions(oldPosition,
            { row: offset, column: 0 });

        this._moveCursorTo(newPosition, true, true);
    },

    _rangeIsInsertionPoint: function(range) {
        return Range.isZeroLength(range);
    },

    _replaceCharacters: function(oldRange, characters) {
        if (!this._inChangeGroup) {
            throw "TextView._replaceCharacters() called without a change " +
                "group";
        }
        oldRange = Range.normalizeRange(oldRange);
        this.notifyDelegates('textViewWillReplaceRange', oldRange);
        this.getPath('layoutManager.textStorage').replaceCharacters(oldRange,
            characters);
        this.notifyDelegates('textViewReplacedCharacters', oldRange,
            characters);
    },

    // Moves the selection, if necessary, to keep all the positions pointing to
    // actual characters.
    _repositionSelection: function() {
        var textLines = this.getPath('layoutManager.textLines');
        var textLineLength = textLines.length;

        var range = this._selectedRange;
        var newStartRow = Math.min(range.start.row, textLineLength - 1);
        var newEndRow = Math.min(range.end.row, textLineLength - 1);
        var startLine = textLines[newStartRow];
        var endLine = textLines[newEndRow];
        this.setSelection({
            start:  {
                row:    newStartRow,
                column: Math.min(range.start.column,
                            startLine.characters.length)
            },
            end:    {
                row:    newEndRow,
                column: Math.min(range.end.column, endLine.characters.length)
            }
        });
    },

    _resize: function() {
        var boundingRect = this.get('layoutManager').boundingRect();
        var padding = this.get('padding');
        var parentFrame = this.getPath('parentView.frame');
        this.set('layout', SC.mixin(SC.clone(this.get('layout')), {
            width:  Math.max(parentFrame.width,
                    boundingRect.width + padding.right),
            height: Math.max(parentFrame.height,
                    boundingRect.height + padding.bottom)
        }));
    },

    _scrollPage: function(scrollUp) {
        var scrollable = this._scrollView();
        var visibleFrame = this.get('clippingFrame');
        scrollable.scrollTo(visibleFrame.x, visibleFrame.y +
            (visibleFrame.height + this._lineAscent) * (scrollUp ? -1 : 1));
    },

    _scrollToPosition: function(position) {
        var scrollable = this._scrollView();
        if (SC.none(scrollable)) {
            return;
        }

        var rect = this.get('layoutManager').
            characterRectForPosition(position);
        var rectX = rect.x, rectY = rect.y;
        var rectWidth = rect.width, rectHeight = rect.height;

        var frame = this.get('clippingFrame');
        var frameX = frame.x, frameY = frame.y;

        var padding = this.get('padding');
        var width = frame.width - padding.right;
        var height = frame.height - padding.bottom;

        scrollable.scrollTo(rectX >= frameX &&
            rectX + rectWidth < frameX + width ?
            frameX : rectX - width / 2 + rectWidth / 2,
            rectY >= frameY &&
            rectY + rectHeight < frameY + height ?
            frameY : rectY - height / 2 + rectHeight / 2);
    },

    /**
     * @private
     *
     * Returns the parent scroll view, if one exists.
     */
    _scrollView: function() {
        var view = this.get('parentView');
        while (!SC.none(view) && !view.get('isScrollable')) {
            view = view.get('parentView');
        }
        return view;
    },

    _scrollWhileDragging: function() {
        var scrollView = this._scrollView();
        if (SC.none(scrollView)) {
            return;
        }

        var offset = Rect.offsetFromRect(this.get('clippingFrame'),
            this.convertFrameFromView(this._dragPoint));
        if (offset.x === 0 && offset.y === 0) {
            return;
        }

        scrollView.scrollBy(offset.x, offset.y);
        this._drag();
    },

    // Returns the character closest to the given point, obeying the selection
    // rules (including the partialFraction field).
    _selectionPositionForPoint: function(point) {
        var position = this.get('layoutManager').characterAtPoint(point);
        return position.partialFraction < 0.5 ? position :
            Range.addPositions(position, { row: 0, column: 1 });
    },

    acceptsFirstResponder: true,

    /**
     * @property{Boolean}
     *
     * This property is always true for objects that expose a padding property.
     * The BespinScrollView uses this.
     */
    hasPadding: true,

    /**
     * @property
     *
     * The layout manager from which this editor view receives text.
     */
    layoutManager: null,

    /**
     * @property
     *
     * The padding to leave inside the clipping frame, given as an object with
     * 'bottom' and 'right' properties. Text content is displayed inside this
     * padding as usual, but the cursor cannot enter it. In a BespinScrollView,
     * this feature is used to prevent the cursor from ever going behind the
     * scroll bars.
     */
    padding: { bottom: 0, right: 0 },

    /**
     * @property
     *
     * The theme to use.
     *
     * TODO: Convert to a SproutCore theme. This is super ugly.
     */
    theme: {
        backgroundStyle: "#2a211c",
        cursorStyle: "#879aff",
        editorTextFont: "10pt Monaco, Lucida Console, monospace",
        editorSelectedTextColor: "rgb(240, 240, 240)",
        editorSelectedTextBackground: "#526da5",
        unfocusedCursorStrokeStyle: "#ff0033",
        unfocusedCursorFillStyle: "#73171e"
    },

    /**
     * Deletes the selection or the previous character, if the selection is an
     * insertion point.
     */
    backspace: function() {
        this._performBackspaceOrDelete(true);
    },

    copy: function() {
        return this.getSelectedCharacters();
    },

    cut: function() {
        var cutData = this.getSelectedCharacters();

        if (cutData != '') {
            this.deleteSelectionOrNextCharacter();
        }

        return cutData;
    },

    /**
     * Deletes the selection or the next character, if the selection is an
     * insertion point.
     */
    deleteSelectionOrNextCharacter: function() {
        this._performBackspaceOrDelete(false);
    },

    /**
     * This is where the editor is painted from head to toe. Pitiful tricks are
     * used to draw as little as possible.
     */
    drawRect: function(rect, context) {
        context.fillStyle = this.get('theme').backgroundStyle;
        context.fillRect(rect.x, rect.y, rect.width, rect.height);

        this._drawSelection(rect, context);
        this._drawLines(rect, context);
    },

    /**
     * Returns the characters that are currently selected as a string, or the
     * empty string if none are selected.
     */
    getSelectedCharacters: function() {
        return this._rangeIsInsertionPoint(this._selectedRange) ? "" :
            this.getPath('layoutManager.textStorage').getCharacters(Range.
            normalizeRange(this._selectedRange));
    },

    init: function() {
        arguments.callee.base.apply(this, arguments);

        this._invalidRange = null;
        this._selectedRange =
            { start: { row: 0, column: 0 }, end: { row: 0, column: 0 } };

        // Allow the user to change the fields of the padding object without
        // screwing up the prototype.
        this.set('padding', SC.clone(this.get('padding')));

        this.get('layoutManager').addDelegate(this);

        this._resize();
    },

    keyDown: function(evt) {
        // SC puts keyDown and keyPress event together. Here we only want to
        // handle the real/browser's keydown event. To do so, we have to check
        // if the evt.charCode value is set. If this isn't set, we have been
        // called after a keypress event took place.
        if (evt.charCode === 0) {
            return keyboardManager.processKeyEvent(evt, this,
                { isTextView: true })
        } else {
            // This is a real keyPress event. This should not be handled,
            // otherwise the textInput mixin can't detect the key events.
            return false;
        }
    },

    /**
     * The layout manager calls this method to signal to the view that the text
     * and/or layout has changed.
     */
    layoutManagerInvalidatedRects: function(sender, rects) {
        rects.forEach(this.setNeedsDisplayInRect, this);
        this._repositionSelection();
        this._resize();
    },

    mouseDown: function(evt) {
        switch (evt.clickCount) {
        case 1:
            var pos = this._selectionPositionForPoint(this.
                convertFrameFromView({
                    x:  evt.clientX,
                    y:  evt.clientY
                }));
            this._moveCursorTo(pos, false, evt.shiftKey);
            break;

        // Select the word under the cursor.
        case 2:
            var pos = this._selectionPositionForPoint(this.
                convertFrameFromView({ x: evt.clientX, y: evt.clientY }));
            var line = this.getPath('layoutManager.textStorage').
                                                        lines[pos.row];

            // If there is nothing to select in this line, then skip.
            if (line.length === 0) {
                return;
            }

            pos.column -= (pos.column == line.length ? 1 : 0);
            var skipOnDelimiter = !this._isDelimiter(line[pos.column]);

            var thisTextView = this;
            var searchForDelimiter = function(pos, dir) {
                for (pos; pos > -1 && pos < line.length; pos += dir) {
                    if (thisTextView._isDelimiter(line[pos]) ===
                            skipOnDelimiter) {
                        break;
                    }
                }
                return pos + (dir == 1 ? 0 : 1);
            }

            var columnFrom = searchForDelimiter(pos.column, -1);
            var columnTo   = searchForDelimiter(pos.column, 1);

            this._moveCursorTo({
                    row: pos.row,
                    column: columnFrom
            });
            this._moveCursorTo({
                    row: pos.row,
                    column: columnTo
            }, false, true);

            break;

        case 3:
            var lines = this.getPath('layoutManager.textStorage').lines;
            var pos = this._selectionPositionForPoint(this.
                convertFrameFromView({ x: evt.clientX, y: evt.clientY }));
            this.setSelection({
                start: {
                    row: pos.row,
                    column: 0
                },
                end: {
                    row: pos.row,
                    column: lines[pos.row].length
                }
            })
            break;
        }

        this._dragPoint = { x: evt.clientX, y: evt.clientY };
        this._dragTimer = SC.Timer.schedule({
            target:     this,
            action:     '_scrollWhileDragging',
            interval:   100,
            repeats:    true
        });

        this.becomeFirstResponder();
    },

    mouseDragged: function(evt) {
        this._dragPoint = { x: evt.clientX, y: evt.clientY };
        this._drag();
    },

    mouseUp: function(evt) {
        if (this._dragTimer !== null) {
            this._dragTimer.invalidate();
        }
    },

    moveDocEnd: function() {
        this._moveOrSelectEnd(false, false);
    },

    moveDocStart: function() {
        this._moveOrSelectStart(false, false);
    },

    moveDown: function() {
        var selection = this._getVirtualSelection();
        var range = Range.normalizeRange(selection);
        var position;
        if (this._rangeIsInsertionPoint(this._selectedRange)) {
            position = range.end;
        } else {
            // Yes, this is actually what Cocoa does... weird, huh?
            position = { row: range.end.row, column: range.start.column };
        }
        position = Range.addPositions(position, { row: 1, column: 0 });

        this._moveCursorTo(position, true);
    },

    moveLeft: function() {
        var range = Range.normalizeRange(this._selectedRange);
        if (this._rangeIsInsertionPoint(range)) {
            this._moveCursorTo(this.getPath('layoutManager.textStorage').
                displacePosition(range.start, -1));
        } else {
            this._moveCursorTo(range.start);
        }
    },

    moveLineEnd: function() {
        this._moveOrSelectEnd(false, true);
    },

    moveLineStart: function () {
        this._moveOrSelectStart(false, true);
    },

    moveRight: function() {
        var range = Range.normalizeRange(this._selectedRange);
        if (this._rangeIsInsertionPoint(range)) {
            this._moveCursorTo(this.getPath('layoutManager.textStorage').
                displacePosition(range.end, 1));
        } else {
            this._moveCursorTo(range.end);
        }
    },

    moveUp: function() {
        var range = Range.normalizeRange(this._getVirtualSelection(true));
        position = Range.addPositions({
            row: range.start.row,
            column: this._getVirtualSelection().end.column
        }, { row: -1, column: 0 });

        this._moveCursorTo(position, true);
    },

    selectDocEnd: function() {
        this._moveOrSelectEnd(true, false);
    },

    selectDocStart: function() {
        this._moveOrSelectStart(true, false);
    },

    selectLineEnd: function() {
        this._moveOrSelectEnd(true, true);
    },

    selectLineStart: function() {
        this._moveOrSelectStart(true, true);
    },

    /**
     * Inserts a newline at the insertion point.
     */
    newline: function() {
        // Insert a newline, and copy the spaces at the beginning of the
        // current row to autoindent.
        var position = this._selectedRange.start;
        this._insertText("\n" + /^\s*/.exec(this.
            getPath('layoutManager.textStorage.lines')[position.row].
            substring(0, position.column))[0]);
    },

    scrollDocStart: function() {
        this._scrollToPosition({ column: 0, row: 0 });
    },

    scrollDocEnd: function() {
        this._scrollToPosition(this.getPath('layoutManager.textStorage').
            range().end);
    },

    scrollPageDown: function() {
        this._scrollPage(false);
    },

    scrollPageUp: function() {
        this._scrollPage(true);
    },

    selectAll: function() {
        var range = this._selectedRange;
        var lines = this.getPath('layoutManager.textStorage.lines');
        var lastRow = lines.length - 1;
        this.setSelection({
            start:  { row: 0, column: 0 },
            end:    { row: lastRow, column: lines[lastRow].length }
        });
    },

    selectDown: function() {
        this._performVerticalKeyboardSelection(1);
    },

    selectLeft: function() {
        this._moveCursorTo((this.getPath('layoutManager.textStorage').
            displacePosition(this._selectedRange.end, -1)), false, true);
    },

    selectRight: function() {
        this._moveCursorTo((this.getPath('layoutManager.textStorage').
            displacePosition(this._selectedRange.end, 1)), false, true);
    },

    selectUp: function() {
        this._performVerticalKeyboardSelection(-1);
    },

    /**
     * Directly replaces the current selection with a new one. No bounds
     * checking is performed, and the user is not able to undo this action.
     */
    setSelection: function(newRange) {
        var lines = this.getPath('layoutManager.textStorage').lines.length - 1;

        var oldRangeOrdered = Range.normalizeRange(this._selectedRange);
        var newRangeOrdered = Range.normalizeRange(newRange);
        this._selectedRange = newRange;

        var layoutManager = this.get('layoutManager');
        layoutManager.rectsForRange(oldRangeOrdered).
                forEach(this.setNeedsDisplayInRect, this);
        layoutManager.rectsForRange(newRangeOrdered).
                forEach(this.setNeedsDisplayInRect, this);

        // Also invalidate any insertion points. These have to be handled
        // separately, because they're drawn outside of their associated
        // character regions.
        this._invalidateInsertionPointIfNecessary(oldRangeOrdered);
        this._invalidateInsertionPointIfNecessary(newRangeOrdered);
    },

    tab: function() {
        this._insertText("        ".substring(0, 8 -
            this._selectedRange.start.column % 8));
    },

    textInserted: function(text) {
        this._insertText(text);
    }
});

exports.textViewCommand = function(instruction, args, command) {
    var view = args.view;
    var methodName = command.name;
    view[methodName]();
};

});

tiki.module("Editor:index",function(require,exports,module){});
;tiki.register("Embedded",{});
tiki.module("Embedded:index",function(require,exports,module) {
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
 
"define metadata";
({
    "depends": [ "AppSupport", "Editor" ],
    "provides": [
        {
            "ep": "factory",
            "name": "view",
            "pointer": "#view",
            "action": "value"
        },
        {
            "ep": "factory",
            "name": "model",
            "pointer": "#model",
            "action": "value"
        }
    ]
});
"end";

/**
 * This file provides an API for embedders to use. Its intent is to abstract
 * out all the complexity of internal APIs and provide something sane, simple
 * and easy to learn to the embedding user.
 */

var SC = require("sproutcore/runtime").SC;
var bespin = require("bespin:index");
var util = require("bespin:util/util");
var EditorView = require('Editor:views/editor').EditorView;
var KeyListener = require('AppSupport:views/keylistener').KeyListener;

var attachEmbeddedEvents = function(pane, bespin) {
    var paneLayer = pane.get('layer');

    var becomeKey = function() { bespin.setFocus(true);     };
    var resignKey = function() { bespin.setFocus(false);    };

    var addFocusEvents = function(element, isPane) {
        while (element !== null) {
            var thisIsPane = isPane || element === paneLayer;
            element.addEventListener('mousedown',
                thisIsPane ? becomeKey : resignKey, true);
            addFocusEvents(element.firstChild, thisIsPane);
            element = element.nextSibling;
        }
    };

    addFocusEvents(document.body, false);
};

var computeLayout = function(element) {
    var layout = {
        top:    0,
        left:   0,
        width:  element.clientWidth,
        height: element.clientHeight
    };
    while (!SC.none(element)) {
        layout.top += element.offsetTop + element.clientTop;
        layout.left += element.offsetLeft + element.clientLeft;
        element = element.offsetParent;
    }
    return layout;
};

exports.EmbeddedEditor = SC.Object.extend({
    _focused: false,

    setContent: function(text) {
        SC.RunLoop.begin();
        this.setPath("editorPane.editorView.layoutManager.textStorage.value", text);
        SC.RunLoop.end();
    },
    
    getContent: function() {
        return this.getPath("editorPane.editorView.layoutManager.textStorage.value");
    },
    
    setFocus: function(makeFocused) {
        var editorPane = this.get("editorPane");
        if (this._focused === makeFocused) {
            return;
        }

        this._focused = makeFocused;
        if (makeFocused) {
            editorPane.becomeKeyPane();
        } else {
            editorPane.resignKeyPane();
        }
    },
    
    /**
     * This function must be called whenever the position or size of the
     * element containing the Bespin editor might have changed. It triggers a
     * layout change.
     */
    dimensionsChanged: function() {
        SC.RunLoop.begin();

        var pane = this.get("editorPane");
        var oldLayout = pane.get('layout');
        var newLayout = computeLayout(this.get("element"));

        if (!SC.rectsEqual(oldLayout, newLayout)) {
            pane.adjust(newLayout);
            pane.updateLayout();    // writes the layoutStyle to the DOM
        }

        SC.RunLoop.end();
    },
    
    /**
     * jump to the line number given. This will clear any selected
     * ranges from the view. The line number is 1-based.
     */
    setLineNumber: function(line) {
        SC.RunLoop.begin();
        var textView = this.getPath("editorPane.editorView.textView");
        var point = {row: line-1, column: 0, partialFraction: 0};
        textView.setSelection([{start: point, end: point}]);
        SC.RunLoop.end();
    }
});

/**
 * Initialize a Bespin component on a given element.
 */
exports.useBespin = function(element, options) {
    options = options || {};
    
    if (util.isString(element)) {
        element = document.getElementById(element);
    }

    if (!element) {
        throw new Error("useBespin requires a element parameter to attach to");
    }

    // Creating the editor alters the components innerHTML
    var content = element.innerHTML;
    
    var editorPane = SC.Pane.create({
        childViews: "editorView".w(),
        defaultResponder: KeyListener.create(),
        editorView: EditorView.design(),
        
        layout: computeLayout(element),

        // Tell SproutCore to keep its paws off the CSS properties 'top'
        // and 'left'.
        layoutStyle: function() {
            var layout = this.get('layout');
            var style = {
                width:  '' + layout.width + 'px',
                height: '' + layout.height + 'px'
            };
            return style;
        }.property('layout')
        
    });
    
    var bespin = exports.EmbeddedEditor.create({
        editorPane: editorPane,
        element: element
    });
    
    
    SC.run(function() {
        SC.$(element).css('position', 'relative');
        element.innerHTML = "";
        bespin.get("editorPane").appendTo(element);
        
        exports.view = editorPane.getPath("editorView.textView");
        exports.model = editorPane.getPath('editorView.layoutManager.' +
            'textStorage');
        
        
        if (options.initialContent) {
            content = options.initialContent;
        }
        
        bespin.setContent(content);
        
        element.bespin = bespin;
        
        // XXX need to reengage these settings, since this is now wired
        // up completely differently.

        // Call editor.setSetting on any settings passed in options.settings
        // if (options.settings) {
        //     for (var key in options.settings) {
        //         if (options.settings.hasOwnProperty(key)) {
        //             editor.setSetting(key, options.settings[key]);
        //         }
        //     }
        // }

        // stealFocus makes us take focus on startup
        if (options.stealFocus) {
            bespin.setFocus(true);
        }

        // Move to a given line if requested
        // if (options.lineNumber) {
        //     editor.setLineNumber(options.lineNumber);
        // }

        // Hook the window.onresize event; this catches most of the common
        // scenarios that result in element resizing.
        if (SC.none(options.dontHookWindowResizeEvent) ||
                !options.dontHookWindowResizeEvent) {
            var handler = function() {
                bespin.dimensionsChanged();
            };

            if (!SC.none(window.addEventListener)) {
                window.addEventListener('resize', handler, false);
            } else if (!SC.none(window.attachEvent)) {
                window.addEventListener('onresize', handler);
            }
        }

        attachEmbeddedEvents(editorPane, bespin);
    });
    
    return bespin;
};


});

tiki.require("bespin:plugins").catalog.load({"AppSupport": {"depends": [], "resourceURL": "resources/AppSupport/", "provides": [{"predicates": ["isApplication", true], "pointer": "controllers/undomanager#undoManagerCommand", "ep": "command", "key": ["ctrl_shift_z", "meta_shift_z"], "name": "redo"}, {"predicates": ["isApplication", true], "pointer": "controllers/undomanager#undoManagerCommand", "ep": "command", "key": ["ctrl_z", "meta_z"], "name": "undo"}]}, "Canon": {"depends": [], "resourceURL": "resources/Canon/", "provides": [{"indexOn": "name", "description": "Commands can accept various arguments that the user enters or that are automatically supplied by the environment. Those arguments have types that define how they are supplied or completed. The pointer points to an object with methods convert(str value) and getDefault(). Both functions have `this` set to the command's `takes` parameter. If getDefault is not defined, the default on the command's `takes` is used, if there is one. The object can have a noInput property that is set to true to reflect that this type is provided directly by the system. getDefault must be defined in that case.", "ep": "extensionpoint", "name": "argumentType"}, {"description": "Text that the user needs to enter.", "pointer": "arguments#string", "ep": "argumentType", "name": "string"}, {"description": "An integer", "pointer": "arguments#integer", "ep": "argumentType", "name": "int"}, {"noInput": true, "description": "the editor model", "pointer": "arguments#model", "ep": "argumentType", "name": "model"}, {"noInput": true, "description": "the editor view", "pointer": "arguments#view", "ep": "argumentType", "name": "view"}, {"register": "directory#newCommandHandler", "ep": "extensionhandler", "name": "command"}]}, "Editor": {"depends": ["AppSupport", "Canon"], "resourceURL": "resources/Editor/", "provides": [{"takes": [{"type": "view"}], "key": "backspace", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "backspace"}, {"takes": [{"type": "view"}], "key": "delete", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "deleteSelectionOrNextCharacter"}, {"takes": [{"type": "view"}], "key": "down", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "moveDown"}, {"takes": [{"type": "view"}], "key": "left", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "moveLeft"}, {"takes": [{"type": "view"}], "key": "right", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "moveRight"}, {"takes": [{"type": "view"}], "key": "up", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "moveUp"}, {"takes": [{"type": "view"}], "key": "ctrl_home", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "scrollDocStart"}, {"takes": [{"type": "view"}], "key": "ctrl_up", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "moveDocStart"}, {"takes": [{"type": "view"}], "key": ["ctrl_a", "meta_a"], "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "selectAll"}, {"takes": [{"type": "view"}], "key": "ctrl_shift_up", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "selectDocStart"}, {"takes": [{"type": "view"}], "key": "ctrl_end", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "scrollDocEnd"}, {"takes": [{"type": "view"}], "key": "ctrl_down", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "moveDocEnd"}, {"takes": [{"type": "view"}], "key": "ctrl_shift_down", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "selectDocEnd"}, {"takes": [{"type": "view"}], "key": ["home", "ctrl_left"], "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "moveLineStart"}, {"takes": [{"type": "view"}], "key": ["shift_home", "ctrl_shift_left"], "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "selectLineStart"}, {"takes": [{"type": "view"}], "key": ["end", "ctrl_right"], "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "moveLineEnd"}, {"takes": [{"type": "view"}], "key": ["shift_end", "ctrl_shift_right"], "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "selectLineEnd"}, {"takes": [{"type": "view"}], "key": "return", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "newline"}, {"takes": [{"type": "view"}], "key": "pagedown", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "scrollPageDown"}, {"takes": [{"type": "view"}], "key": "pageup", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "scrollPageUp"}, {"takes": [{"type": "view"}], "key": "shift_down", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "selectDown"}, {"takes": [{"type": "view"}], "key": "shift_left", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "selectLeft"}, {"takes": [{"type": "view"}], "key": "shift_right", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "selectRight"}, {"takes": [{"type": "view"}], "key": "shift_up", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "selectUp"}, {"takes": [{"type": "view"}], "key": "tab", "predicates": ["isTextView", true], "pointer": "views/text#textViewCommand", "ep": "command", "name": "tab"}, {"takes": ["value"], "completeText": "add the line number to move to in the file", "preview": "move it! make the editor head to a line number.", "pointer": "commands/editor#gotoCommand", "ep": "command", "name": "goto"}, {"takes": ["search", "replace"], "completeText": "add the search regex, and then the replacement text", "preview": "s/foo/bar/g", "pointer": "commands/editor#replaceCommand", "ep": "command", "name": "replace"}, {"takes": ["direction"], "completeText": "optionally, sort descending", "preview": "sort the current buffer", "pointer": "commands/editor#sortCommand", "ep": "command", "name": "sort"}, {"takes": ["tabsize"], "completeText": "Optionally, specify a tab size. (Defaults to setting.)", "preview": "Convert spaces to tabs.", "pointer": "commands/editor#entabCommand", "ep": "command", "name": "entab"}, {"takes": ["tabsize"], "completeText": "Optionally, specify a tab size. (Defaults to setting.)", "preview": "Convert tabs to spaces.", "pointer": "commands/editor#detabCommand", "ep": "command", "name": "detab"}, {"takes": ["side"], "completeText": "optionally, give a side of left, right, or both (defaults to right)", "preview": "trim trailing or leading whitespace", "pointer": "commands/editor#trimCommand", "ep": "command", "name": "trim"}, {"withKey": "CMD SHIFT U", "preview": "Change all selected text to uppercase", "pointer": "commands/editor#ucCommand", "ep": "command", "name": "uc"}, {"withKey": "CMD SHIFT L", "preview": "Change all selected text to lowercase", "pointer": "commands/editor#lcCommand", "ep": "command", "name": "lc"}]}, "Embedded": {"depends": ["AppSupport", "Editor"], "resourceURL": "resources/Embedded/", "provides": [{"action": "value", "pointer": "#view", "ep": "factory", "name": "view"}, {"action": "value", "pointer": "#model", "ep": "factory", "name": "model"}]}});
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bespin.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bespin Team (bespin@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// This module is set up to be dependency-less so that Narwhal
// will be able to run it right away.


SC.ready(function() {
    var embed = tiki.require("Embedded");
    var nodes = document.querySelectorAll(".bespin");
    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var options = node.getAttribute('data-bespinoptions');
        embed.useBespin(node, JSON.parse(options));
    }

    // If users want a custom startup
    if (window.onBespinLoad) {
        window.onBespinLoad();
    }
}
);

// Let SC know when the page is loaded
document.addEventListener("DOMContentLoaded", SC.didLoad, false);

// Set up any globals that should leak out of our closure
window.tiki = tiki;
