    /**
     * The TreePanel provides tree-structured UI representation of tree-structured data.
     * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
     * multiple columns through the {@link #columns} configuration.
     *
     * Simple TreePanel using inline data.
     *
     * {@img Ext.tree.Panel/Ext.tree.Panel1.png Ext.tree.Panel component}
     *
     * Code:
     *
     *     var store = Ext.create('Ext.data.TreeStore', {
     *         root: {
     *             expanded: true,
     *             children: [
     *                 { text: "detention", leaf: true },
     *                 { text: "homework", expanded: true, children: [
     *                     { text: "book report", leaf: true },
     *                     { text: "alegrbra", leaf: true}
     *                 ] },
     *                 { text: "buy lottery tickets", leaf: true }
     *             ]
     *         }
     *     });
     *
     *     Ext.create('Ext.tree.Panel', {
     *         title: 'Simple Tree',
     *         width: 200,
     *         height: 150,
     *         store: store,
     *         rootVisible: false,
     *         renderTo: Ext.getBody()
     *     });
     */
    Ext.define('Ext.tree.Panel', {
        extend: 'Ext.panel.Table',
        alias: 'widget.treepanel',
        alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
        requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
        viewType: 'treeview',
        selType: 'treemodel',

        treeCls: Ext.baseCSSPrefix + 'tree-panel',

        deferRowRender: false,

        /**
         * @cfg {Boolean} lines False to disable tree lines. Defaults to true.
         */
        lines: true,

        /**
         * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree. Defaults to false.
         */
        useArrows: false,

        /**
         * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded. Defaults to false.
         */
        singleExpand: false,

        ddConfig: {
            enableDrag: true,
            enableDrop: true
        },

        /**
         * @cfg {Boolean} animate True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
         */

        /**
         * @cfg {Boolean} rootVisible False to hide the root node. Defaults to true.
         */
        rootVisible: true,

        /**
         * @cfg {Boolean} displayField The field inside the model that will be used as the node's text. Defaults to 'text'.
         */
        displayField: 'text',

        /**
         * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
         * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
         * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
         * to that store. For example:
         *
         *     Ext.create('Ext.tree.Panel', {
         *         title: 'Simple Tree',
         *         root: {
         *             text: "Root node",
         *             expanded: true,
         *             children: [
         *                 { text: "Child 1", leaf: true },
         *                 { text: "Child 2", leaf: true }
         *             ]
         *         },
         *         renderTo: Ext.getBody()
         *     });
         */
        root: null,

        // Required for the Lockable Mixin. These are the configurations which will be copied to the
        // normal and locked sub tablepanels
        normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
        lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],

        /**
         * @cfg {Boolean} hideHeaders True to hide the headers. Defaults to `undefined`.
         */

        /**
         * @cfg {Boolean} folderSort True to automatically prepend a leaf sorter to the store. Defaults to `undefined`.
         */

        constructor: function(config) {
            config = config || {};
            if (config.animate === undefined) {
                config.animate = Ext.enableFx;
            }
            this.enableAnimations = config.animate;
            delete config.animate;

            this.callParent([config]);
        },

        initComponent: function() {
            var me = this,
                cls = [me.treeCls];

            if (me.useArrows) {
                cls.push(Ext.baseCSSPrefix + 'tree-arrows');
                me.lines = false;
            }

            if (me.lines) {
                cls.push(Ext.baseCSSPrefix + 'tree-lines');
            } else if (!me.useArrows) {
                cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
            }

            if (Ext.isString(me.store)) {
                me.store = Ext.StoreMgr.lookup(me.store);
            } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
                me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
                    root: me.root,
                    fields: me.fields,
                    model: me.model,
                    folderSort: me.folderSort
                }));
            } else if (me.root) {
                me.store = Ext.data.StoreManager.lookup(me.store);
                me.store.setRootNode(me.root);
                if (me.folderSort !== undefined) {
                    me.store.folderSort = me.folderSort;
                    me.store.sort();
                }
            }

            // I'm not sure if we want to this. It might be confusing
            // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
            //     me.rootVisible = false;
            // }

            me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
                rootVisible: me.rootVisible,
                animate: me.enableAnimations,
                singleExpand: me.singleExpand,
                node: me.store.getRootNode(),
                hideHeaders: me.hideHeaders
            });

            me.mon(me.store, {
                scope: me,
                rootchange: me.onRootChange,
                clear: me.onClear
            });

            me.relayEvents(me.store, [
                /**
                 * @event beforeload
                 * Event description
                 * @param {Ext.data.Store} store This Store
                 * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store
                 */
                'beforeload',

                /**
                 * @event load
                 * Fires whenever the store reads data from a remote data source.
                 * @param {Ext.data.store} this
                 * @param {Array} records An array of records
                 * @param {Boolean} successful True if the operation was successful.
                 */
                'load'
            ]);

            me.store.on({
                /**
                 * @event itemappend
                 * Fires when a new child node is appended to a node in the tree.
                 * @param {Tree} tree The owner tree
                 * @param {Node} parent The parent node
                 * @param {Node} node The newly appended node
                 * @param {Number} index The index of the newly appended node
                 */
                append: me.createRelayer('itemappend'),

                /**
                 * @event itemremove
                 * Fires when a child node is removed from a node in the tree
                 * @param {Tree} tree The owner tree
                 * @param {Node} parent The parent node
                 * @param {Node} node The child node removed
                 */
                remove: me.createRelayer('itemremove'),

                /**
                 * @event itemmove
                 * Fires when a node is moved to a new location in the tree
                 * @param {Tree} tree The owner tree
                 * @param {Node} node The node moved
                 * @param {Node} oldParent The old parent of this node
                 * @param {Node} newParent The new parent of this node
                 * @param {Number} index The index it was moved to
                 */
                move: me.createRelayer('itemmove'),

                /**
                 * @event iteminsert
                 * Fires when a new child node is inserted in a node in tree
                 * @param {Tree} tree The owner tree
                 * @param {Node} parent The parent node
                 * @param {Node} node The child node inserted
                 * @param {Node} refNode The child node the node was inserted before
                 */
                insert: me.createRelayer('iteminsert'),

                /**
                 * @event beforeitemappend
                 * Fires before a new child is appended to a node in this tree, return false to cancel the append.
                 * @param {Tree} tree The owner tree
                 * @param {Node} parent The parent node
                 * @param {Node} node The child node to be appended
                 */
                beforeappend: me.createRelayer('beforeitemappend'),

                /**
                 * @event beforeitemremove
                 * Fires before a child is removed from a node in this tree, return false to cancel the remove.
                 * @param {Tree} tree The owner tree
                 * @param {Node} parent The parent node
                 * @param {Node} node The child node to be removed
                 */
                beforeremove: me.createRelayer('beforeitemremove'),

                /**
                 * @event beforeitemmove
                 * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
                 * @param {Tree} tree The owner tree
                 * @param {Node} node The node being moved
                 * @param {Node} oldParent The parent of the node
                 * @param {Node} newParent The new parent the node is moving to
                 * @param {Number} index The index it is being moved to
                 */
                beforemove: me.createRelayer('beforeitemmove'),

                /**
                 * @event beforeiteminsert
                 * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
                 * @param {Tree} tree The owner tree
                 * @param {Node} parent The parent node
                 * @param {Node} node The child node to be inserted
                 * @param {Node} refNode The child node the node is being inserted before
                 */
                beforeinsert: me.createRelayer('beforeiteminsert'),

                /**
                 * @event itemexpand
                 * Fires when a node is expanded.
                 * @param {Node} this The expanding node
                 */
                expand: me.createRelayer('itemexpand'),

                /**
                 * @event itemcollapse
                 * Fires when a node is collapsed.
                 * @param {Node} this The collapsing node
                 */
                collapse: me.createRelayer('itemcollapse'),

                /**
                 * @event beforeitemexpand
                 * Fires before a node is expanded.
                 * @param {Node} this The expanding node
                 */
                beforeexpand: me.createRelayer('beforeitemexpand'),

                /**
                 * @event beforeitemcollapse
                 * Fires before a node is collapsed.
                 * @param {Node} this The collapsing node
                 */
                beforecollapse: me.createRelayer('beforeitemcollapse')
            });

            // If the user specifies the headers collection manually then dont inject our own
            if (!me.columns) {
                if (me.initialConfig.hideHeaders === undefined) {
                    me.hideHeaders = true;
                }
                me.columns = [{
                    xtype    : 'treecolumn',
                    text     : 'Name',
                    flex     : 1,
                    dataIndex: me.displayField
                }];
            }

            if (me.cls) {
                cls.push(me.cls);
            }
            me.cls = cls.join(' ');
            me.callParent();

            me.relayEvents(me.getView(), [
                /**
                 * @event checkchange
                 * Fires when a node with a checkbox's checked property changes
                 * @param {Ext.data.Model} node The node who's checked property was changed
                 * @param {Boolean} checked The node's new checked state
                 */
                'checkchange'
            ]);

            // If the root is not visible and there is no rootnode defined, then just lets load the store
            if (!me.getView().rootVisible && !me.getRootNode()) {
                me.setRootNode({
                    expanded: true
                });
            }
        },

        onClear: function(){
            this.view.onClear();
        },

        setRootNode: function() {
            return this.store.setRootNode.apply(this.store, arguments);
        },

        getRootNode: function() {
            return this.store.getRootNode();
        },

        onRootChange: function(root) {
            this.view.setRootNode(root);
        },

        /**
         * Retrieve an array of checked records.
         * @return {Array} An array containing the checked records
         */
        getChecked: function() {
            return this.getView().getChecked();
        },

        isItemChecked: function(rec) {
            return rec.get('checked');
        },

        /**
         * Expand all nodes
         * @param {Function} callback (optional) A function to execute when the expand finishes.
         * @param {Object} scope (optional) The scope of the callback function
         */
        expandAll : function(callback, scope) {
            var root = this.getRootNode();
            if (root) {
                root.expand(true, callback, scope);
            }
        },

        /**
         * Collapse all nodes
         * @param {Function} callback (optional) A function to execute when the collapse finishes.
         * @param {Object} scope (optional) The scope of the callback function
         */
        collapseAll : function(callback, scope) {
            var root = this.getRootNode();
            if (root) {
                if (this.getView().rootVisible) {
                    root.collapse(true, callback, scope);
                }
                else {
                    root.collapseChildren(true, callback, scope);
                }
            }
        },

        /**
         * Expand the tree to the path of a particular node.
         * @param {String} path The path to expand. The path should include a leading separator.
         * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
         * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
         * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
         * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
         * @param {Object} scope (optional) The scope of the callback function
         */
        expandPath: function(path, field, separator, callback, scope) {
            var me = this,
                current = me.getRootNode(),
                index = 1,
                view = me.getView(),
                keys,
                expander;

            field = field || me.getRootNode().idProperty;
            separator = separator || '/';

            if (Ext.isEmpty(path)) {
                Ext.callback(callback, scope || me, [false, null]);
                return;
            }

            keys = path.split(separator);
            if (current.get(field) != keys[1]) {
                // invalid root
                Ext.callback(callback, scope || me, [false, current]);
                return;
            }

            expander = function(){
                if (++index === keys.length) {
                    Ext.callback(callback, scope || me, [true, current]);
                    return;
                }
                var node = current.findChild(field, keys[index]);
                if (!node) {
                    Ext.callback(callback, scope || me, [false, current]);
                    return;
                }
                current = node;
                current.expand(false, expander);
            };
            current.expand(false, expander);
        },

        /**
         * Expand the tree to the path of a particular node, then selecti t.
         * @param {String} path The path to select. The path should include a leading separator.
         * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
         * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
         * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
         * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
         * @param {Object} scope (optional) The scope of the callback function
         */
        selectPath: function(path, field, separator, callback, scope) {
            var me = this,
                keys,
                last;

            field = field || me.getRootNode().idProperty;
            separator = separator || '/';

            keys = path.split(separator);
            last = keys.pop();

            me.expandPath(keys.join(separator), field, separator, function(success, node){
                var doSuccess = false;
                if (success && node) {
                    node = node.findChild(field, last);
                    if (node) {
                        me.getSelectionModel().select(node);
                        Ext.callback(callback, scope || me, [true, node]);
                        doSuccess = true;
                    }
                } else if (node === me.getRootNode()) {
                    doSuccess = true;
                }
                Ext.callback(callback, scope || me, [doSuccess, node]);
            }, me);
        }
    });