text: // ==========================================================================
  // Project:   Greenhouse.libraryController
  // Copyright: ©2010 Mike Ball
  // ==========================================================================
  /*globals Greenhouse */
  
  /** @class
    Based on SCUI.SearchableTreeController
    http://github.com/etgryphon/sproutcore-ui/blob/master/frameworks/foundation/controllers/searchable_tree.js  
    Thanks to Evin Grano and Brandon Blatnick!
    
    @extends SC.ArrayController
  */
  
  Greenhouse.libraryController = SC.TreeController.create( SC.CollectionViewDelegate,
  /** @scope Greenhouse.libraryController.prototype */ {
    /**
      Call this method whenever you want to reload the library from hte server
    */
    reload: function() {
      var configQuery = Greenhouse.CONFIG_QUERY, target = Greenhouse.targetController.get('content');
      configQuery.set('app', target.get('name'));
      var files = Greenhouse.store.find(configQuery), root = SC.Object.create({treeItemIsExpanded: YES});
      root.set('contents', files);
      this.set('content', root);
    },
    
    views: SC.outlet('content.treeItemChildren.0.treeItemChildren'),
    // ..........................................................
    // Collection View delegate drag methods
    //    
    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 ['SC.Object']; },
  
    /**
      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) {
      var ret = (dataType === 'SC.Object') ? this.get('selection').firstObject() : null;
      return ret ;
    },
    
    ghostActsLikeCursor: YES,
    
    /**
      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) {
      var selectedItemIndex = dragContent.firstObject();
      var itemView = view.itemViewForContentIndex(selectedItemIndex);
      return itemView;
    },
  
    /**
      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: YES,
  
  
    // ..........................................................
    // type ahead search code
    // 
    search: null,
    searchResults: [],
    searchKey: 'name',
    iconKey: 'icon',
    nameKey: 'name',
  
    init: function(){
      sc_super();
      this.set('searchResults', []);
      this._runSearch();
    },
  
    _searchDidChange: function(){
      this._runSearch();
    }.observes('search', 'content'),
  
   _sanitizeSearchString: function(str){
     var specials = [
         '/', '.', '*', '+', '?', '|',
         '(', ')', '[', ']', '{', '}', '\\'
     ];
     var s = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
     return str.replace(s, '\\$1');
   },
  
   _runSearch: function(){
     var searchResults = [];
     var search = this.get('search');
     var c = this.get('content');
     if(search === null || search === '' || search === undefined){ 
       this.set('searchResults', c);
     }
     else {
       search = this._sanitizeSearchString(search).toLowerCase();
       var searchRegex = new RegExp(search,'i');
       var searchKey = this.get('searchKey');
       this._iconKey = this.get('iconKey');
       this._nameKey = this.get('nameKey');
       searchResults = this._runSearchOnItem(c, search, searchRegex, searchKey);
  
       // create the root search tree
       var searchedTree = SC.Object.create({
         treeItemIsExpanded: YES,
         treeItemChildren: searchResults
       });
       this.set('searchResults', searchedTree);
     }
   },
  
   /** 
     @private
     Returns a flat list of matches for the foldered tree item.
   */
   _runSearchOnItem: function(treeItem, search, searchRegex, searchKey) {
     var searchMatches = [], iconKey = this.get('iconKey'),
         searchedList, key, searchLen, 
         children, nameKey = this._nameKey, that;
  
     if (SC.none(treeItem)) return searchMatches;
  
     children = treeItem.get('treeItemChildren');
     if (!children) children = treeItem.get('children');
     that = this;
     children.forEach( function(child){      
       if (child.treeItemChildren) {
         var searchedList = that._runSearchOnItem(child, search, searchRegex, searchKey);
         searchedList.forEach( function(m){ searchMatches.push(m); });
       }
  
       if (searchKey && child.get(searchKey)) {
         key = child.get(searchKey).toLowerCase();
         if(key.match(searchRegex)){
           var match = SC.Object.create({});
           match[searchKey]  = child.get(searchKey);
           match[nameKey]    = child.get(nameKey);
           match.treeItem    = child;
           match.icon        = child.get(this._iconKey);
           searchMatches.push(match);
         } 
       }
     });
  
     return searchMatches;
   }
    
  }) ;
  