    /*!
     * Ext JS Library 3.3.0
     * Copyright(c) 2006-2010 Ext JS, Inc.
     * licensing@extjs.com
     * http://www.extjs.com/license
     */
    /**
     * @class Ext.ux.tree.TreeGrid
     * @extends Ext.tree.TreePanel
     *
     * @xtype treegrid
     */
    Ext.ux.tree.TreeGrid = Ext.extend(Ext.tree.TreePanel, {
        rootVisible : false,
        useArrows : true,
        lines : false,
        borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
        cls : 'x-treegrid',

        columnResize : true,
        enableSort : true,
        reserveScrollOffset : true,
        enableHdMenu : true,

        columnsText : 'Columns',

        initComponent : function() {
            if(!this.root) {
                this.root = new Ext.tree.AsyncTreeNode({text: 'Root'});
            }

            // initialize the loader
            var l = this.loader;
            if(!l){
                l = new Ext.ux.tree.TreeGridLoader({
                    dataUrl: this.dataUrl,
                    requestMethod: this.requestMethod,
                    store: this.store
                });
            }else if(Ext.isObject(l) && !l.load){
                l = new Ext.ux.tree.TreeGridLoader(l);
            }
            this.loader = l;

            Ext.ux.tree.TreeGrid.superclass.initComponent.call(this);

            this.initColumns();

            if(this.enableSort) {
                this.treeGridSorter = new Ext.ux.tree.TreeGridSorter(this, this.enableSort);
            }

            if(this.columnResize){
                this.colResizer = new Ext.tree.ColumnResizer(this.columnResize);
                this.colResizer.init(this);
            }

            var c = this.columns;
            if(!this.internalTpl){
                this.internalTpl = new Ext.XTemplate(
                    '',
                        '',
                            '',
                                '',
                                '',
                                '',
                                '',
                                    '',
                                         this.enableHdMenu ? '' : '',
                                         '{header}',
                                     '',
                                '',
                                '',
                            '',
                        '',
                    '',
                    '',
                        '',
                    ''
                );
            }

            if(!this.colgroupTpl) {
                this.colgroupTpl = new Ext.XTemplate(
                    ''
                );
            }
        },

        initColumns : function() {
            var cs = this.columns,
                len = cs.length,
                columns = [],
                i, c;

            for(i = 0; i < len; i++){
                c = cs[i];
                if(!c.isColumn) {
                    c.xtype = c.xtype ? (/^tg/.test(c.xtype) ? c.xtype : 'tg' + c.xtype) : 'tgcolumn';
                    c = Ext.create(c);
                }
                c.init(this);
                columns.push(c);

                if(this.enableSort !== false && c.sortable !== false) {
                    c.sortable = true;
                    this.enableSort = true;
                }
            }

            this.columns = columns;
        },

        onRender : function(){
            Ext.tree.TreePanel.superclass.onRender.apply(this, arguments);

            this.el.addClass('x-treegrid');

            this.outerCt = this.body.createChild({
                cls:'x-tree-root-ct x-treegrid-ct ' + (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')
            });

            this.internalTpl.overwrite(this.outerCt, {columns: this.columns});

            this.mainHd = Ext.get(this.outerCt.dom.firstChild);
            this.innerHd = Ext.get(this.mainHd.dom.firstChild);
            this.innerBody = Ext.get(this.outerCt.dom.lastChild);
            this.innerCt = Ext.get(this.innerBody.dom.firstChild);

            this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});

            if(this.hideHeaders){
                this.el.child('.x-grid3-header').setDisplayed('none');
            }
            else if(this.enableHdMenu !== false){
                this.hmenu = new Ext.menu.Menu({id: this.id + '-hctx'});
                if(this.enableColumnHide !== false){
                    this.colMenu = new Ext.menu.Menu({id: this.id + '-hcols-menu'});
                    this.colMenu.on({
                        scope: this,
                        beforeshow: this.beforeColMenuShow,
                        itemclick: this.handleHdMenuClick
                    });
                    this.hmenu.add({
                        itemId:'columns',
                        hideOnClick: false,
                        text: this.columnsText,
                        menu: this.colMenu,
                        iconCls: 'x-cols-icon'
                    });
                }
                this.hmenu.on('itemclick', this.handleHdMenuClick, this);
            }
        },

        setRootNode : function(node){
            node.attributes.uiProvider = Ext.ux.tree.TreeGridRootNodeUI;
            node = Ext.ux.tree.TreeGrid.superclass.setRootNode.call(this, node);
            if(this.innerCt) {
                this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});
            }
            return node;
        },

        clearInnerCt : function(){
            if(Ext.isIE){
                var dom = this.innerCt.dom;
                while(dom.firstChild){
                    dom.removeChild(dom.firstChild);
                }
            }else{
                Ext.ux.tree.TreeGrid.superclass.clearInnerCt.call(this);
            }
        },

        initEvents : function() {
            Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments);

            this.mon(this.innerBody, 'scroll', this.syncScroll, this);
            this.mon(this.innerHd, 'click', this.handleHdDown, this);
            this.mon(this.mainHd, {
                scope: this,
                mouseover: this.handleHdOver,
                mouseout: this.handleHdOut
            });
        },

        onResize : function(w, h) {
            Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments);

            var bd = this.innerBody.dom;
            var hd = this.innerHd.dom;

            if(!bd){
                return;
            }

            if(Ext.isNumber(h)){
                bd.style.height = this.body.getHeight(true) - hd.offsetHeight + 'px';
            }

            if(Ext.isNumber(w)){
                var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
                if(this.reserveScrollOffset || ((bd.offsetWidth - bd.clientWidth) > 10)){
                    this.setScrollOffset(sw);
                }else{
                    var me = this;
                    setTimeout(function(){
                        me.setScrollOffset(bd.offsetWidth - bd.clientWidth > 10 ? sw : 0);
                    }, 10);
                }
            }
        },

        updateColumnWidths : function() {
            var cols = this.columns,
                colCount = cols.length,
                groups = this.outerCt.query('colgroup'),
                groupCount = groups.length,
                c, g, i, j;

            for(i = 0; i<colCount; i++) {
                c = cols[i];
                for(j = 0; j<groupCount; j++) {
                    g = groups[j];
                    g.childNodes[i].style.width = (c.hidden ? 0 : c.width) + 'px';
                }
            }

            for(i = 0, groups = this.innerHd.query('td'), len = groups.length; i<len; i++) {
                c = Ext.fly(groups[i]);
                if(cols[i] && cols[i].hidden) {
                    c.addClass('x-treegrid-hd-hidden');
                }
                else {
                    c.removeClass('x-treegrid-hd-hidden');
                }
            }

            var tcw = this.getTotalColumnWidth();
            Ext.fly(this.innerHd.dom.firstChild).setWidth(tcw + (this.scrollOffset || 0));
            this.outerCt.select('table').setWidth(tcw);
            this.syncHeaderScroll();
        },

        getVisibleColumns : function() {
            var columns = [],
                cs = this.columns,
                len = cs.length,
                i;

            for(i = 0; i<len; i++) {
                if(!cs[i].hidden) {
                    columns.push(cs[i]);
                }
            }
            return columns;
        },

        getTotalColumnWidth : function() {
            var total = 0;
            for(var i = 0, cs = this.getVisibleColumns(), len = cs.length; i<len; i++) {
                total += cs[i].width;
            }
            return total;
        },

        setScrollOffset : function(scrollOffset) {
            this.scrollOffset = scrollOffset;
            this.updateColumnWidths();
        },

        // private
        handleHdDown : function(e, t){
            var hd = e.getTarget('.x-treegrid-hd');

            if(hd && Ext.fly(t).hasClass('x-grid3-hd-btn')){
                var ms = this.hmenu.items,
                    cs = this.columns,
                    index = this.findHeaderIndex(hd),
                    c = cs[index],
                    sort = c.sortable;

                e.stopEvent();
                Ext.fly(hd).addClass('x-grid3-hd-menu-open');
                this.hdCtxIndex = index;

                this.fireEvent('headerbuttonclick', ms, c, hd, index);

                this.hmenu.on('hide', function(){
                    Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
                }, this, {single:true});

                this.hmenu.show(t, 'tl-bl?');
            }
            else if(hd) {
                var index = this.findHeaderIndex(hd);
                this.fireEvent('headerclick', this.columns[index], hd, index);
            }
        },

        // private
        handleHdOver : function(e, t){
            var hd = e.getTarget('.x-treegrid-hd');
            if(hd && !this.headersDisabled){
                index = this.findHeaderIndex(hd);
                this.activeHdRef = t;
                this.activeHdIndex = index;
                var el = Ext.get(hd);
                this.activeHdRegion = el.getRegion();
                el.addClass('x-grid3-hd-over');
                this.activeHdBtn = el.child('.x-grid3-hd-btn');
                if(this.activeHdBtn){
                    this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
                }
            }
        },

        // private
        handleHdOut : function(e, t){
            var hd = e.getTarget('.x-treegrid-hd');
            if(hd && (!Ext.isIE || !e.within(hd, true))){
                this.activeHdRef = null;
                Ext.fly(hd).removeClass('x-grid3-hd-over');
                hd.style.cursor = '';
            }
        },

        findHeaderIndex : function(hd){
            hd = hd.dom || hd;
            var cs = hd.parentNode.childNodes;
            for(var i = 0, c; c = cs[i]; i++){
                if(c == hd){
                    return i;
                }
            }
            return -1;
        },

        // private
        beforeColMenuShow : function(){
            var cols = this.columns,
                colCount = cols.length,
                i, c;
            this.colMenu.removeAll();
            for(i = 1; i < colCount; i++){
                c = cols[i];
                if(c.hideable !== false){
                    this.colMenu.add(new Ext.menu.CheckItem({
                        itemId: 'col-' + i,
                        text: c.header,
                        checked: !c.hidden,
                        hideOnClick:false,
                        disabled: c.hideable === false
                    }));
                }
            }
        },

        // private
        handleHdMenuClick : function(item){
            var index = this.hdCtxIndex,
                id = item.getItemId();

            if(this.fireEvent('headermenuclick', this.columns[index], id, index) !== false) {
                index = id.substr(4);
                if(index > 0 && this.columns[index]) {
                    this.setColumnVisible(index, !item.checked);
                }
            }

            return true;
        },

        setColumnVisible : function(index, visible) {
            this.columns[index].hidden = !visible;
            this.updateColumnWidths();
        },

        /**
         * Scrolls the grid to the top
         */
        scrollToTop : function(){
            this.innerBody.dom.scrollTop = 0;
            this.innerBody.dom.scrollLeft = 0;
        },

        // private
        syncScroll : function(){
            this.syncHeaderScroll();
            var mb = this.innerBody.dom;
            this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
        },

        // private
        syncHeaderScroll : function(){
            var mb = this.innerBody.dom;
            this.innerHd.dom.scrollLeft = mb.scrollLeft;
            this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
        },

        registerNode : function(n) {
            Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n);
            if(!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) {
                n.ui = new Ext.ux.tree.TreeGridNodeUI(n);
            }
        }
    });

    Ext.reg('treegrid', Ext.ux.tree.TreeGrid);