/**
 * Ext.ux.grid.livegrid.GridPanel
 * Copyright (c) 2007-2008, http://www.siteartwork.de
 *
 * Ext.ux.grid.livegrid.GridPanel is licensed under the terms of the
 *                  GNU Open Source GPL 3.0
 * license.
 *
 * Commercial use is prohibited. Visit <http://www.siteartwork.de/livegrid>
 * if you need to obtain a commercial license.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 */

Ext.namespace('Ext.ux.grid.livegrid');

/**
 * @class Ext.ux.grid.livegrid.GridPanel
 * @extends Ext.grid.GridPanel
 * @constructor
 * @param {Object} config
 *
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 */
Ext.ux.grid.livegrid.GridPanel = Ext.extend(Ext.grid.GridPanel, {

    initComponent : function()
    {
        if (this.cls) {
            this.cls += ' ext-ux-livegrid';
        } else {
            this.cls = 'ext-ux-livegrid';
        }

        Ext.ux.grid.livegrid.GridPanel.superclass.initComponent.call(this);
    },

    /**
     * Overriden to make sure the attached store loads only when the
     * grid has been fully rendered if, and only if the store's
     * "autoLoad" property is set to true.
     *
     */
    onRender : function(ct, position)
    {
        Ext.ux.grid.livegrid.GridPanel.superclass.onRender.call(this, ct, position);

        var ds = this.getStore();

        if (ds._autoLoad === true) {
            delete ds._autoLoad;
            ds.load();
        }
    },

    /**
     * Overriden since the original implementation checks for
     * getCount() of the store, not getTotalCount().
     *
     */
    walkCells : function(row, col, step, fn, scope)
    {
        var ds  = this.store;
        var _oF = ds.getCount;

        ds.getCount = ds.getTotalCount;

        var ret = Ext.ux.grid.livegrid.GridPanel.superclass.walkCells.call(this, row, col, step, fn, scope);

        ds.getCount = _oF;

        return ret;
    }

});/**
 * Ext.ux.grid.livegrid.GridView
 * Copyright (c) 2007-2008, http://www.siteartwork.de
 *
 * Ext.ux.grid.livegrid.GridView is licensed under the terms of the
 *                  GNU Open Source GPL 3.0
 * license.
 *
 * Commercial use is prohibited. Visit <http://www.siteartwork.de/livegrid>
 * if you need to obtain a commercial license.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 */

Ext.namespace('Ext.ux.grid.livegrid');

/**
 * @class Ext.ux.grid.livegrid.GridView
 * @extends Ext.grid.GridView
 * @constructor
 * @param {Object} config
 *
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 */
Ext.ux.grid.livegrid.GridView = function(config) {

    this.addEvents({
        /**
         * @event beforebuffer
         * Fires when the store is about to buffer new data.
         * @param {Ext.ux.BufferedGridView} this
         * @param {Ext.data.Store} store The store
         * @param {Number} rowIndex
         * @param {Number} visibleRows
         * @param {Number} totalCount
         * @param {Number} options The options with which the buffer request was called
         */
        'beforebuffer' : true,
        /**
         * @event buffer
         * Fires when the store is finsihed buffering new data.
         * @param {Ext.ux.BufferedGridView} this
         * @param {Ext.data.Store} store The store
         * @param {Number} rowIndex
         * @param {Number} visibleRows
         * @param {Number} totalCount
         * @param {Object} options
         */
        'buffer' : true,
        /**
         * @event bufferfailure
         * Fires when buffering failed.
         * @param {Ext.ux.BufferedGridView} this
         * @param {Ext.data.Store} store The store
         * @param {Object} options The options the buffer-request was initiated with
         */
        'bufferfailure' : true,
        /**
         * @event cursormove
         * Fires when the the user scrolls through the data.
         * @param {Ext.ux.BufferedGridView} this
         * @param {Number} rowIndex The index of the first visible row in the
         *                          grid absolute to it's position in the model.
         * @param {Number} visibleRows The number of rows visible in the grid.
         * @param {Number} totalCount
         */
        'cursormove' : true,
        /**
         * @event abortrequest
         * Fires when the store is about to reload (this does NOT mean buffering).
         * If you are using a custom proxy in your store, you should listen to this event
         * and abort any ongoing server request established in your custom proxy.
         * @param {Ext.data.Store} store
         * @param {Object} options
         */
        'abortrequest' : true

    });

    /**
     * @cfg {Number} scrollDelay The number of microseconds a call to the
     * onLiveScroll-lisener should be delayed when the scroll event fires
     */

    /**
     * @cfg {Number} bufferSize The number of records that will at least always
     * be available in the store for rendering. This value will be send to the
     * server as the <tt>limit</tt> parameter and should not change during the
     * lifetime of a grid component. Note: In a paging grid, this number would
     * indicate the page size.
     * The value should be set high enough to make a userfirendly scrolling
     * possible and should be greater than the sum of {nearLimit} and
     * {visibleRows}. Usually, a value in between 150 and 200 is good enough.
     * A lesser value will more often make the store re-request new data, while
     * a larger number will make loading times higher.
     */

    /**
     * @cfg {Number} nearLimit This value represents a near value that is responsible
     * for deciding if a request for new data is needed. The lesser the number, the
     * more often new data will be requested. The number should be set to a value
     * that lies in between 1/4 to 1/2 of the {bufferSize}.
     */

    /**
     * @cfg {Number} horizontalScrollOffset The height of a horizontal aligned
     * scrollbar.  The scrollbar is shown if the total width of all visible
     * columns exceeds the width of the grid component.
     * On Windows XP (IE7, FF2), this value defaults to 17.
     */
    this.horizontalScrollOffset = 17;

    /**
     * @type {Boolean} _checkEmptyBody Since Ext 3.0, &nbsp; would initially added to the mainBody
     * as the first child if there are no rows to render. This element has to be removed when
     * the first rows get added so the UI does not crash. This property is here to determine if
     * this element was already removed, so we don't have to query innerHTML all the time.
     */
    this._checkEmptyBody = true;

    Ext.apply(this, config);

    this.templates = {};
    /**
     * The master template adds an addiiotnal scrollbar to make cursoring in the
     * data possible.
     */
    this.templates.master = new Ext.Template(
        '<div class="x-grid3" hidefocus="true"><div class="liveScroller"><div></div></div>',
            '<div class="x-grid3-viewport"">',
                '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset" style="{ostyle}">{header}</div></div><div class="x-clear"></div></div>',
                '<div class="x-grid3-scroller" style="overflow-y:hidden !important;"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
            "</div>",
            '<div class="x-grid3-resize-marker">&#160;</div>',
            '<div class="x-grid3-resize-proxy">&#160;</div>',
        "</div>"
    );

    // shorthands for often used parent classes
    this._gridViewSuperclass = Ext.ux.grid.livegrid.GridView.superclass;

    this._gridViewSuperclass.constructor.call(this);


};


Ext.extend(Ext.ux.grid.livegrid.GridView, Ext.grid.GridView, {

// {{{ --------------------------properties-------------------------------------

    /**
     * Stores the height of the header. Needed for recalculating scroller inset height.
     * @param {Number}
     */
    hdHeight : 0,

    /**
     * Indicates wether the last row in the grid is clipped and thus not fully display.
     * 1 if clipped, otherwise 0.
     * @param {Number}
     */
    rowClipped : 0,


    /**
     * This is the actual y-scroller that does control sending request to the server
     * based upon the position of the scrolling cursor.
     * @param {Ext.Element}
     */
    liveScroller : null,

    /**
     * This is the panel that represents the amount of data in a given repository.
     * The height gets computed via the total amount of records multiplied with
     * the fixed(!) row height
     * @param {native HTMLObject}
     */
    liveScrollerInset : null,

    /**
     * The <b>fixed</b> row height for <b>every</b> row in the grid. The value is
     * computed once the store has been loaded for the first time and used for
     * various calculations during the lifetime of the grid component, such as
     * the height of the scroller and the number of visible rows.
     * @param {Number}
     */
    rowHeight : -1,

    /**
     * Stores the number of visible rows that have to be rendered.
     * @param {Number}
     */
    visibleRows : 1,

    /**
     * Stores the last offset relative to a previously scroll action. This is
     * needed for deciding wether the user scrolls up or down.
     * @param {Number}
     */
    lastIndex : -1,

    /**
     * Stores the last visible row at position "0" in the table view before
     * a new scroll event was created and fired.
     * @param {Number}
     */
    lastRowIndex : 0,

    /**
     * Stores the value of the <tt>liveScroller</tt>'s <tt>scrollTop</tt> DOM
     * property.
     * @param {Number}
     */
    lastScrollPos : 0,

    /**
     * The current index of the row in the model that is displayed as the first
     * visible row in the view.
     * @param {Number}
     */
    rowIndex : 0,

    /**
    * Set to <tt>true</tt> if the store is busy with loading new data.
    * @param {Boolean}
    */
    isBuffering : false,

	/**
	 * If a request for new data was made and the user scrolls to a new position
	 * that lays not within the requested range of the new data, the queue will
	 * hold the latest requested position. If the buffering succeeds and the value
	 * of requestQueue is not within the range of the current buffer, data may be
	 * re-requested.
	 *
	 * @param {Number}
	 */
    requestQueue : -1,

    /**
     * An {@Ext.LoadMask} config that will be shown when a request to data was made
     * and there are no rows in the buffer left to render.
     * @param {Object}
     */
    loadMask : false,

    /**
     * Set to <tt>true</tt> if a request for new data has been made while there
     * are still rows in the buffer that can be rendered before the request
     * finishes.
     * @param {Boolean}
     */
    isPrebuffering : false,

    /**
     * The dom node for which the node mask will be rendered.
     * @type {Ext.Element}
     * @private
     */
    _loadMaskAnchor : null,

// }}}

// {{{ --------------------------public API methods-----------------------------

    /**
     * Resets the view to display the first row in the data model. This will
     * change the scrollTop property of the scroller and may trigger a request
     * to buffer new data, if the row index "0" is not within the buffer range and
     * forceReload is set to true.
     *
     * @param {Boolean} forceReload <tt>true</tt> to reload the buffers contents,
     *                              othwerwise <tt>false</tt>
     *
     * @return {Boolean} Whether the store loads after reset(true); returns false
     * if any of the attached beforeload listeners cancels the load-event
     */
    reset : function(forceReload)
    {
        if (forceReload === false) {
            this.ds.modified = [];
            //this.grid.selModel.clearSelections(true);
            this.rowIndex      = 0;
            this.lastScrollPos = 0;
            this.lastRowIndex = 0;
            this.lastIndex    = 0;
            this.adjustVisibleRows();
            this.adjustScrollerPos(-this.liveScroller.dom.scrollTop, true);
            this.showLoadMask(false);
            this.refresh(true);
            //this.replaceLiveRows(0, true);
            this.fireEvent('cursormove', this, 0,
                           Math.min(this.ds.totalLength, this.visibleRows-this.rowClipped),
                           this.ds.totalLength);
            return false;
        } else {

            var params = {};
            var sInfo = this.ds.sortInfo;

            if (sInfo) {
                params = {
                    dir  : sInfo.direction,
                    sort : sInfo.field
                };
            }

            return this.ds.load({params : params});
        }

    },

// {{{ ------------adjusted methods for applying custom behavior----------------

    /**
     * Overwritten so the {@link Ext.ux.grid.livegrid.DragZone} can be used
     * with this view implementation.
     *
     * Since detaching a previously created DragZone from a grid panel seems to
     * be impossible, a little workaround will tell the parent implementation
     * that drad/drop is not enabled for this view's grid, and right after that
     * the custom DragZone will be created, if neccessary.
     */
    renderUI : function()
    {
        var g = this.grid;
        var dEnabled = g.enableDragDrop || g.enableDrag;

        g.enableDragDrop = false;
        g.enableDrag     = false;

        this._gridViewSuperclass.renderUI.call(this);

        var g = this.grid;

        g.enableDragDrop = dEnabled;
        g.enableDrag     = dEnabled;

        if(dEnabled){
            this.dragZone = new Ext.ux.grid.livegrid.DragZone(g, {
                ddGroup : g.ddGroup || 'GridDD'
            });
        }

        if (this.loadMask) {
            this._loadMaskAnchor = Ext.get(this.mainBody.dom.parentNode.parentNode);
            Ext.apply(this.loadMask,{
                msgCls : 'x-mask-loading'
            });
            this._loadMaskAnchor.mask(
                this.loadMask.msg, this.loadMask.msgCls
            );
            var dom  = this._loadMaskAnchor.dom;
            var data = Ext.Element.data;
            data(dom, 'mask').addClass('ext-ux-livegrid');
            data(dom, 'mask').setDisplayed(false);
            data(dom, 'maskMsg').setDisplayed(false);
        }
    },

    /**
     * The extended implementation attaches an listener to the beforeload
     * event of the store of the grid. It is guaranteed that the listener will
     * only be executed upon reloading of the store, sorting and initial loading
     * of data. When the store does "buffer", all events are suspended and the
     * beforeload event will not be triggered.
     *
     * @param {Ext.grid.GridPanel} grid The grid panel this view is attached to
     */
    init: function(grid)
    {
        this._gridViewSuperclass.init.call(this, grid);

        grid.on('expand', this._onExpand, this);
    },

    initData : function(ds, cm)
    {
        if(this.ds){
            this.ds.un('bulkremove', this.onBulkRemove, this);
            this.ds.un('beforeload', this.onBeforeLoad, this);
        }
        if(ds){
            ds.on('bulkremove', this.onBulkRemove, this);
            ds.on('beforeload', this.onBeforeLoad, this);
        }

        this._gridViewSuperclass.initData.call(this, ds, cm);
    },

    /**
     * Only render the viewable rect of the table. The number of rows visible to
     * the user is defined in <tt>visibleRows</tt>.
     * This implementation does completely overwrite the parent's implementation.
     */
    // private
    renderBody : function()
    {
        var markup = this.renderRows(0, this.visibleRows-1);
        return this.templates.body.apply({rows: markup});
    },

    /**
     * Overriden so the renderer of the specific cells gets the index of the
     * row as available in the view passed (row's rowIndex property)-
     *
     */
    doRender : function(cs, rs, ds, startRow, colCount, stripe)
    {
        return this._gridViewSuperclass.doRender.call(
            this, cs, rs, ds, startRow + this.ds.bufferRange[0], colCount, stripe
        );

    },

    /**
     * Inits the DOM native elements for this component.
     * The properties <tt>liveScroller</tt> and <tt>liveScrollerInset</tt> will
     * be respected as provided by the master template.
     * The <tt>scroll</tt> listener for the <tt>liverScroller</tt> will also be
     * added here as the <tt>mousewheel</tt> listener.
     * This method overwrites the parents implementation.
     */
    // private
    initElements : function()
    {
        var E = Ext.Element;

        var el = this.grid.getGridEl().dom.firstChild;
	    var cs = el.childNodes;

	    this.el = new E(el);

        this.mainWrap = new E(cs[1]);

        // liveScroller and liveScrollerInset
        this.liveScroller       = new E(cs[0]);
        this.liveScrollerInset  = this.liveScroller.dom.firstChild;
        this.liveScroller.on('scroll', this.onLiveScroll,  this, {buffer : this.scrollDelay});

        var thd = this.mainWrap.dom.firstChild;
	    this.mainHd = new E(thd);

	    this.hdHeight = thd.offsetHeight;

	    this.innerHd = this.mainHd.dom.firstChild;
        this.scroller = new E(this.mainWrap.dom.childNodes[1]);
        if(this.forceFit){
            this.scroller.setStyle('overflow-x', 'hidden');
        }
        this.mainBody = new E(this.scroller.dom.firstChild);

        // addd the mousewheel event to the table's body
        this.mainBody.on('mousewheel', this.handleWheel,  this);

	    this.focusEl = new E(this.scroller.dom.childNodes[1]);
        this.focusEl.swallowEvent("click", true);

        this.resizeMarker = new E(cs[2]);
        this.resizeProxy = new E(cs[3]);

    },

	/**
	 * Layouts the grid's view taking the scroller into account. The height
	 * of the scroller gets adjusted depending on the total width of the columns.
	 * The width of the grid view will be adjusted so the header and the rows do
	 * not overlap the scroller.
	 * This method will also compute the row-height based on the first row this
	 * grid displays and will adjust the number of visible rows if a resize
	 * of the grid component happened.
	 * This method overwrites the parents implementation.
	 */
	//private
    layout : function()
    {
        if(!this.mainBody){
            return; // not rendered
        }
        var g = this.grid;
        var c = g.getGridEl(), cm = this.cm,
                expandCol = g.autoExpandColumn,
                gv = this;

        var csize = c.getSize(true);

        // set vw to 19 to take scrollbar width into account!
        var vw = csize.width;

        if(!g.hideHeaders && vw < 20 || csize.height < 20){ // display: none?
            return;
        }

        if(g.autoHeight){
            this.scroller.dom.style.overflow = 'visible';
            if(Ext.isWebKit){
                this.scroller.dom.style.position = 'static';
            }
        }else{
            this.el.setSize(csize.width, csize.height);

            var hdHeight = this.mainHd.getHeight();
            var vh = csize.height - (hdHeight);

            this.scroller.setSize(vw, vh);
            if(this.innerHd){
                this.innerHd.style.width = (vw)+'px';
            }
        }

        this.liveScroller.dom.style.top = this.hdHeight+"px";

        if(this.forceFit){
            if(this.lastViewWidth != vw){
                this.fitColumns(false, false);
                this.lastViewWidth = vw;
            }
        }else {
            this.autoExpand();
        }

        // adjust the number of visible rows and the height of the scroller.
        this.adjustVisibleRows();
        this.adjustBufferInset();

        this.onLayout(vw, vh);
    },

    /**
     * Overriden for Ext 2.2 to prevent call to focus Row.
     *
     */
    removeRow : function(row)
    {
        Ext.removeNode(this.getRow(row));
    },

    /**
     * Overriden for Ext 2.2 to prevent call to focus Row.
     * This method i s here for dom operations only - the passed arguments are the
     * index of the nodes in the dom, not in the model.
     *
     */
    removeRows : function(firstRow, lastRow)
    {
        var bd = this.mainBody.dom;
        for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
            Ext.removeNode(bd.childNodes[firstRow]);
        }
    },

// {{{ ----------------------dom/mouse listeners--------------------------------

    /**
     * Tells the view to recalculate the number of rows displayable
     * and the buffer inset, when it gets expanded after it has been
     * collapsed.
     *
     */
    _onExpand : function(panel)
    {
        this.adjustVisibleRows();
        this.adjustBufferInset();
        this.adjustScrollerPos(this.rowHeight*this.rowIndex, true);
    },

    // private
    onColumnMove : function(cm, oldIndex, newIndex)
    {
        this.indexMap = null;
        this.replaceLiveRows(this.rowIndex, true);
        this.updateHeaders();
        this.updateHeaderSortState();
        this.afterMove(newIndex);
        this.grid.fireEvent('columnmove', oldIndex, newIndex);
    },


    /**
     * Called when a column width has been updated. Adjusts the scroller height
     * and the number of visible rows wether the horizontal scrollbar is shown
     * or not.
     */
    onColumnWidthUpdated : function(col, w, tw)
    {
        this.adjustVisibleRows();
        this.adjustBufferInset();
    },

    /**
     * Called when the width of all columns has been updated. Adjusts the scroller
     * height and the number of visible rows wether the horizontal scrollbar is shown
     * or not.
     */
    onAllColumnWidthsUpdated : function(ws, tw)
    {
        this.adjustVisibleRows();
        this.adjustBufferInset();
    },

    /**
     * Callback for selecting a row. The index of the row is the absolute index
     * in the datamodel. If the row is not rendered, this method will do nothing.
     */
    // private
    onRowSelect : function(row)
    {
        if (row < this.rowIndex || row > this.rowIndex+this.visibleRows) {
            return;
        }

        this.addRowClass(row, this.selectedRowClass);
    },

    /**
     * Callback for deselecting a row. The index of the row is the absolute index
     * in the datamodel. If the row is not currently rendered in the view, this method
     * will do nothing.
     */
    // private
    onRowDeselect : function(row)
    {
        if (row < this.rowIndex || row > this.rowIndex+this.visibleRows) {
            return;
        }

        this.removeRowClass(row, this.selectedRowClass);
    },


// {{{ ----------------------data listeners-------------------------------------
    /**
     * Called when the buffer gets cleared. Simply calls the updateLiveRows method
     * with the adjusted index and should force the store to reload
     */
    // private
    onClear : function()
    {
        this.reset(false);
    },

    /**
     * Callback for the "bulkremove" event of the attached datastore.
     *
     * @param {Ext.ux.grid.livegrid.Store} store
     * @param {Array} removedData
     *
     */
    onBulkRemove : function(store, removedData)
    {
        var record    = null;
        var index     = 0;
        var viewIndex = 0;
        var len       = removedData.length;

        var removedInView    = false;
        var removedAfterView = false;
        var scrollerAdjust   = 0;

        if (len == 0) {
            return;
        }

        var tmpRowIndex   = this.rowIndex;
        var removedBefore = 0;
        var removedAfter  = 0;
        var removedIn     = 0;

        for (var i = 0; i < len; i++) {
            record = removedData[i][0];
            index  = removedData[i][1];

            viewIndex = (index != Number.MIN_VALUE && index != Number.MAX_VALUE)
                      ? index + this.ds.bufferRange[0]
                      : index;

            if (viewIndex < this.rowIndex) {
                removedBefore++;
            } else if (viewIndex >= this.rowIndex && viewIndex <= this.rowIndex+(this.visibleRows-1)) {
                removedIn++;
            } else if (viewIndex >= this.rowIndex+this.visibleRows) {
                removedAfter++;
            }

            this.fireEvent("beforerowremoved", this, viewIndex, record);
            this.fireEvent("rowremoved",       this, viewIndex, record);
        }

        var totalLength = this.ds.totalLength;
        this.rowIndex   = Math.max(0, Math.min(this.rowIndex - removedBefore, totalLength-(this.visibleRows-1)));

        this.lastRowIndex = this.rowIndex;

        this.adjustScrollerPos(-(removedBefore*this.rowHeight), true);
        this.updateLiveRows(this.rowIndex, true);
        this.adjustBufferInset();
        this.processRows(0, undefined, false);

    },


    /**
     * Callback for the underlying store's remove method. The current
     * implementation does only remove the selected row which record is in the
     * current store.
     *
     * @see onBulkRemove()
     */
    // private
    onRemove : function(ds, record, index)
    {
        this.onBulkRemove(ds, [[record, index]]);
    },

    /**
     * The callback for the underlying data store when new data was added.
     * If <tt>index</tt> equals to <tt>Number.MIN_VALUE</tt> or <tt>Number.MAX_VALUE</tt>, the
     * method can't tell at which position in the underlying data model the
     * records where added. However, if <tt>index</tt> equals to <tt>Number.MIN_VALUE</tt>,
     * the <tt>rowIndex</tt> property will be adjusted to <tt>rowIndex+records.length</tt>,
     * and the <tt>liveScroller</tt>'s properties get adjusted so it matches the
     * new total number of records of the underlying data model.
     * The same will happen to any records that get added at the store index which
     * is currently represented by the first visible row in the view.
     * Any other value will cause the method to compute the number of rows that
     * have to be (re-)painted and calling the <tt>insertRows</tt> method, if
     * neccessary.
     *
     * This method triggers the <tt>beforerowsinserted</tt> and <tt>rowsinserted</tt>
     * event, passing the indexes of the records as they may default to the
     * positions in the underlying data model. However, due to the fact that
     * any sort algorithm may have computed the indexes of the records, it is
     * not guaranteed that the computed indexes equal to the indexes of the
     * underlying data model.
     *
     * @param {Ext.ux.grid.livegrid.Store} ds The datastore that buffers records
     *                                       from the underlying data model
     * @param {Array} records An array containing the newly added
     *                        {@link Ext.data.Record}s
     * @param {Number} index The index of the position in the underlying
     *                       {@link Ext.ux.grid.livegrid.Store} where the rows
     *                       were added.
     */
    // private
    onAdd : function(ds, records, index)
    {
        if (this._checkEmptyBody) {
            if (this.mainBody.dom.innerHTML == '&nbsp;') {
                this.mainBody.dom.innerHTML = '';
            }
            this._checkEmptyBody = false;
        }

        var recordLen = records.length;

        // values of index which equal to Number.MIN_VALUE or Number.MAX_VALUE
        // indicate that the records were not added to the store. The component
        // does not know which index those records do have in the underlying
        // data model
        if (index == Number.MAX_VALUE || index == Number.MIN_VALUE) {
            this.fireEvent("beforerowsinserted", this, index, index);

            // if index equals to Number.MIN_VALUE, shift rows!
            if (index == Number.MIN_VALUE) {

                this.rowIndex     = this.rowIndex + recordLen;
                this.lastRowIndex = this.rowIndex;

                this.adjustBufferInset();
                this.adjustScrollerPos(this.rowHeight*recordLen, true);

                this.fireEvent("rowsinserted", this, index, index, recordLen);
                this.processRows(0, undefined, false);
                // the cursor did virtually move
                this.fireEvent('cursormove', this, this.rowIndex,
                               Math.min(this.ds.totalLength, this.visibleRows-this.rowClipped),
                               this.ds.totalLength);

                return;
            }

            this.adjustBufferInset();
            this.fireEvent("rowsinserted", this, index, index, recordLen);
            return;
        }

        // only insert the rows which affect the current view.
        var start = index+this.ds.bufferRange[0];
        var end   = start + (recordLen-1);
        var len   = this.getRows().length;

        var firstRow = 0;
        var lastRow  = 0;

        // rows would be added at the end of the rows which are currently
        // displayed, so fire the event, resize buffer and adjust visible
        // rows and return
        if (start > this.rowIndex+(this.visibleRows-1)) {
            this.fireEvent("beforerowsinserted", this, start, end);
            this.fireEvent("rowsinserted",       this, start, end, recordLen);

            this.adjustVisibleRows();
            this.adjustBufferInset();

        }

        // rows get added somewhere in the current view.
        else if (start >= this.rowIndex && start <= this.rowIndex+(this.visibleRows-1)) {
            firstRow = index;
            // compute the last row that would be affected of an insert operation
            lastRow  = index+(recordLen-1);
            this.lastRowIndex  = this.rowIndex;
            this.rowIndex      = (start > this.rowIndex) ? this.rowIndex : start;

            this.insertRows(ds, firstRow, lastRow);

            if (this.lastRowIndex != this.rowIndex) {
                this.fireEvent('cursormove', this, this.rowIndex,
                               Math.min(this.ds.totalLength, this.visibleRows-this.rowClipped),
                               this.ds.totalLength);
            }

            this.adjustVisibleRows();
            this.adjustBufferInset();
        }

        // rows get added before the first visible row, which would not affect any
        // rows to be re-rendered
        else if (start < this.rowIndex) {
            this.fireEvent("beforerowsinserted", this, start, end);

            this.rowIndex     = this.rowIndex+recordLen;
            this.lastRowIndex = this.rowIndex;

            this.adjustVisibleRows();
            this.adjustBufferInset();

            this.adjustScrollerPos(this.rowHeight*recordLen, true);

            this.fireEvent("rowsinserted", this, start, end, recordLen);
            this.processRows(0, undefined, true);

            this.fireEvent('cursormove', this, this.rowIndex,
                           Math.min(this.ds.totalLength, this.visibleRows-this.rowClipped),
                           this.ds.totalLength);
        }




    },

// {{{ ----------------------store listeners------------------------------------
    /**
     * This callback for the store's "beforeload" event will adjust the start
     * position and the limit of the data in the model to fetch. It is guaranteed
     * that this method will only be called when the store initially loads,
     * remeote-sorts or reloads.
     * All other load events will be suspended when the view requests buffer data.
     * See {updateLiveRows}.
     * Note:
     * If you are using a custom proxy, such as {Ext.data.DirectProxy}, you should listen
     * to the 'abortrequest'-event, which will tell that an ongoing "read" request should be
     * aborted, since the grid's store gets refreshed.
     * If the store is using an instance of {Ext.data.HttpProxy}, the method will still be
     * fired, but the request made through this proxy will be aborted automatically.
     *
     *
     * @param {Ext.data.Store} store The store the Grid Panel uses
     * @param {Object} options The configuration object for the proxy that loads
     *                         data from the server
     */
    onBeforeLoad : function(store, options)
    {
        var proxy = store.proxy;
        if (proxy.activeRequest && proxy.activeRequest[Ext.data.Api.actions.read]) {
            proxy.getConnection().abort(proxy.activeRequest[Ext.data.Api.actions.read]);
        }
        this.fireEvent('abortrequest', store, options);

        this.isBuffering    = false;
        this.isPreBuffering = false;

        options.params = options.params || {};

        var apply = Ext.apply;

        apply(options, {
            scope    : this,
            callback : function(){
                this.reset(false);
            },
            suspendLoadEvent : false
        });

        apply(options.params, {
            start    : 0,
            limit    : this.ds.bufferSize
        });

        return true;
    },

    /**
     * Method is used as a callback for the load-event of the attached data store.
     * Adjusts the buffer inset based upon the <tt>totalCount</tt> property
     * returned by the response.
     * Overwrites the parent's implementation.
     */
    onLoad : function(o1, o2, options)
    {
        this.adjustBufferInset();
    },

    /**
     * This will be called when the data in the store has changed, i.e. a
     * re-buffer has occured. If the table was not rendered yet, a call to
     * <tt>refresh</tt> will initially render the table, which DOM elements will
     * then be used to re-render the table upon scrolling.
     *
     */
    // private
    onDataChange : function(store)
    {
        this.updateHeaderSortState();
    },

    /**
     * A callback for the store when new data has been buffered successfully.
     * If the current row index is not within the range of the newly created
     * data buffer or another request to new data has been made while the store
     * was loading, new data will be re-requested.
     *
     * Additionally, if there are any rows that have been selected which were not
     * in the data store, the method will request the pending selections from
     * the grid's selection model and add them to the selections if available.
     * This is because the component assumes that a user who scrolls through the
     * rows and updates the view's buffer during scrolling, can check the selected
     * rows which come into the view for integrity. It is up to the user to
     * deselect those rows not matchuing the selection.
     * Additionally, if the version of the store changes during various requests
     * and selections are still pending, the versionchange event of the store
     * can delete the pending selections after a re-bufer happened and before this
     * method was called.
     *
     */
    // private
    liveBufferUpdate : function(records, options, success)
    {
        if (success === true) {
            this.adjustBufferInset();

            this.fireEvent('buffer', this, this.ds, this.rowIndex,
                Math.min(this.ds.totalLength, this.visibleRows-this.rowClipped),
                this.ds.totalLength,
                options
            );

            // this is needed since references to records which have been unloaded
            // get lost when the store gets loaded with new data.
            // from the store
            this.grid.selModel.replaceSelections(records);

            this.isBuffering    = false;
            this.isPrebuffering = false;
            this.showLoadMask(false);

            if (this.requestQueue >= 0) {
                var offset = this.requestQueue;
                this.requestQueue = -1;
                this.updateLiveRows(offset);
                return;
            }

            if (this.isInRange(this.rowIndex)) {
                this.replaceLiveRows(this.rowIndex, options.forceRepaint);
            } else {
                this.updateLiveRows(this.rowIndex);
            }


            return;
        } else {
            this.fireEvent('bufferfailure', this, this.ds, options);
        }

        this.requestQueue   = -1;
        this.isBuffering    = false;
        this.isPrebuffering = false;
        this.showLoadMask(false);
    },


// {{{ ----------------------scroll listeners------------------------------------
    /**
     * Handles mousewheel event on the table's body. This is neccessary since the
     * <tt>liveScroller</tt> element is completely detached from the table's body.
     *
     * @param {Ext.EventObject} e The event object
     */
    handleWheel : function(e)
    {
        if (this.rowHeight == -1) {
            e.stopEvent();
            return;
        }
        var d = e.getWheelDelta();

        this.adjustScrollerPos(-(d*this.rowHeight));

        e.stopEvent();
    },

    /**
     * Handles scrolling through the grid. Since the grid is fixed and rows get
     * removed/ added subsequently, the only way to determine the actual row in
     * view is to measure the <tt>scrollTop</tt> property of the <tt>liveScroller</tt>'s
     * DOM element.
     *
     */
    onLiveScroll : function()
    {
        var scrollTop = this.liveScroller.dom.scrollTop;

        var cursor = Math.floor((scrollTop)/this.rowHeight);

        this.rowIndex = cursor;
        // the lastRowIndex will be set when refreshing the view has finished
        if (cursor == this.lastRowIndex) {
            return;
        }

        this.updateLiveRows(cursor);

        this.lastScrollPos = this.liveScroller.dom.scrollTop;
    },



// {{{ --------------------------helpers----------------------------------------

    // private
    refreshRow : function(record)
    {
        var ds = this.ds, index;
        if(typeof record == 'number'){
            index = record;
            record = ds.getAt(index);
        }else{
            index = ds.indexOf(record);
        }

        var viewIndex = index + this.ds.bufferRange[0];

        if (viewIndex < this.rowIndex || viewIndex >= this.rowIndex + this.visibleRows) {
            this.fireEvent("rowupdated", this, viewIndex, record);
            return;
        }

        this.insertRows(ds, index, index, true);
        this.fireEvent("rowupdated", this, viewIndex, record);
    },

    /**
     * Overwritten so the rowIndex can be changed to the absolute index.
     *
     * If the third parameter equals to <tt>true</tt>, the method will also
     * repaint the selections.
     */
    // private
    processRows : function(startRow, skipStripe, paintSelections)
    {
        if(!this.ds || this.ds.getCount() < 1){
            return;
        }

        var cursor = this.rowIndex;
        skipStripe   = skipStripe || !this.grid.stripeRows;
        var rows     = this.getRows();
        var index    = 0;

        Ext.each(rows, function(row, idx) {
            row.rowIndex = index = cursor+idx;
            row.className = row.className.replace(this.rowClsRe, ' ');
            if (!skipStripe && (index + 1) % 2 === 0) {
                row.className += ' x-grid3-row-alt';
            }

            if (paintSelections !== false) {
                if (this.grid.selModel.isSelected(this.ds.getAt(index)) === true) {
                    this.addRowClass(index, this.selectedRowClass);
                } else {
                    this.removeRowClass(index, this.selectedRowClass);
                }
                this.fly(row).removeClass("x-grid3-row-over");
            }
        }, this);

        // add first/last-row classes
        if(cursor === 0){
            Ext.fly(rows[0]).addClass(this.firstRowCls);
        } else if (cursor + rows.length == this.ds.totalLength) {
            Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
        }
    },

    /**
     * API only, since the passed arguments are the indexes in the buffer store.
     * However, the method will try to compute the indexes so they might match
     * the indexes of the records in the underlying data model.
     *
     */
    // private
    insertRows : function(dm, firstRow, lastRow, isUpdate)
    {
        var viewIndexFirst = firstRow + this.ds.bufferRange[0];
        var viewIndexLast  = lastRow  + this.ds.bufferRange[0];

        if (!isUpdate) {
            this.fireEvent("beforerowsinserted", this, viewIndexFirst, viewIndexLast);
        }

        // first off, remove the rows at the bottom of the view to match the
        // visibleRows value and to not cause any spill in the DOM
        if (isUpdate !== true && (this.getRows().length + (lastRow-firstRow)) >= this.visibleRows) {
            this.removeRows((this.visibleRows-1)-(lastRow-firstRow), this.visibleRows-1);
        } else if (isUpdate) {
            this.removeRows(viewIndexFirst-this.rowIndex, viewIndexLast-this.rowIndex);
        }

        // compute the range of possible records which could be drawn into the view without
        // causing any spill
        var lastRenderRow = (firstRow == lastRow)
                          ? lastRow
                          : Math.min(lastRow,  (this.rowIndex-this.ds.bufferRange[0])+(this.visibleRows-1));

        var html = this.renderRows(firstRow, lastRenderRow);

        var before = this.getRow(viewIndexFirst);

        if (before) {
            Ext.DomHelper.insertHtml('beforeBegin', before, html);
        } else {
            Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
        }

        // if a row is replaced, we need to set the row index for this
        // row
        if (isUpdate === true) {
            var rows   = this.getRows();
            var cursor = this.rowIndex;
            for (var i = 0, max_i = rows.length; i < max_i; i++) {
                rows[i].rowIndex = cursor+i;
            }
        }

        if (!isUpdate) {
            this.fireEvent("rowsinserted", this, viewIndexFirst, viewIndexLast, (viewIndexLast-viewIndexFirst)+1);
            this.processRows(0, undefined, true);
        }
    },

    /**
     * Return the <TR> HtmlElement which represents a Grid row for the specified index.
     * The passed argument is assumed to be the absolute index and will get translated
     * to the index of the row that represents the data in the view.
     *
     * @param {Number} index The row index
     *
     * @return {null|HtmlElement} The <TR> element, or null if the row is not rendered
     * in the view.
     */
    getRow : function(row)
    {
        if (row-this.rowIndex < 0) {
            return null;
        }

        return this.getRows()[row-this.rowIndex];
    },

    /**
     * Returns the grid's <TD> HtmlElement at the specified coordinates.
     * Returns null if the specified row is not currently rendered.
     *
     * @param {Number} row The row index in which to find the cell.
     * @param {Number} col The column index of the cell.
     * @return {HtmlElement} The &lt;TD> at the specified coordinates.
     */
    getCell : function(row, col)
    {
        var row = this.getRow(row);

        return row
               ? row.getElementsByTagName('td')[col]
               : null;
    },

    /**
     * Focuses the specified cell.
     * @param {Number} row The row index
     * @param {Number} col The column index
     */
    focusCell : function(row, col, hscroll)
    {
        var xy = this.ensureVisible(row, col, hscroll);

        if (!xy) {
        	return;
		}

		this.focusEl.setXY(xy);

        if(Ext.isGecko){
            this.focusEl.focus();
        }else{
            this.focusEl.focus.defer(1, this.focusEl);
        }

    },

    /**
     * Makes sure that the requested /row/col is visible in the viewport.
     * The method may invoke a request for new buffer data and triggers the
     * scroll-event of the <tt>liveScroller</tt> element.
     *
     */
    // private
    ensureVisible : function(row, col, hscroll)
    {
        if(typeof row != "number"){
            row = row.rowIndex;
        }

        if(row < 0 || row >= this.ds.totalLength){
            return;
        }

        col = (col !== undefined ? col : 0);

        var rowInd = row-this.rowIndex;

        if (this.rowClipped && row == this.rowIndex+this.visibleRows-1) {
            this.adjustScrollerPos(this.rowHeight );
        } else if (row >= this.rowIndex+this.visibleRows) {
            this.adjustScrollerPos(((row-(this.rowIndex+this.visibleRows))+1)*this.rowHeight);
        } else if (row <= this.rowIndex) {
            this.adjustScrollerPos((rowInd)*this.rowHeight);
        }

        var rowEl = this.getRow(row), cellEl;

        if(!rowEl){
            return;
        }

        if(!(hscroll === false && col === 0)){
            while(this.cm.isHidden(col)){
                col++;
            }
            cellEl = this.getCell(row, col);
        }

        var c = this.scroller.dom;

        if(hscroll !== false){
            var cleft = parseInt(cellEl.offsetLeft, 10);
            var cright = cleft + cellEl.offsetWidth;

            var sleft = parseInt(c.scrollLeft, 10);
            var sright = sleft + c.clientWidth;
            if(cleft < sleft){
                c.scrollLeft = cleft;
            }else if(cright > sright){
                c.scrollLeft = cright-c.clientWidth;
            }
        }


        return cellEl ?
            Ext.fly(cellEl).getXY() :
            [c.scrollLeft+this.el.getX(), Ext.fly(rowEl).getY()];
    },

    /**
     * Return strue if the passed record is in the visible rect of this view.
     *
     * @param {Ext.data.Record} record
     *
     * @return {Boolean} true if the record is rendered in the view, otherwise false.
     */
    isRecordRendered : function(record)
    {
        var ind = this.ds.indexOf(record);

        if (ind >= this.rowIndex && ind < this.rowIndex+this.visibleRows) {
            return true;
        }

        return false;
    },

    /**
     * Checks if the passed argument <tt>cursor</tt> lays within a renderable
     * area. The area is renderable, if the sum of cursor and the visibleRows
     * property does not exceed the current upper buffer limit.
     *
     * If this method returns <tt>true</tt>, it's basically save to re-render
     * the view with <tt>cursor</tt> as the absolute position in the model
     * as the first visible row.
     *
     * @param {Number} cursor The absolute position of the row in the data model.
     *
     * @return {Boolean} <tt>true</tt>, if the row can be rendered, otherwise
     *                   <tt>false</tt>
     *
     */
    isInRange : function(rowIndex)
    {
        var lastRowIndex = Math.min(this.ds.totalLength-1,
                                    rowIndex + (this.visibleRows-1));

        return (rowIndex     >= this.ds.bufferRange[0]) &&
               (lastRowIndex <= this.ds.bufferRange[1]);
    },

    /**
     * Calculates the bufferRange start index for a buffer request
     *
     * @param {Boolean} inRange If the index is within the current buffer range
     * @param {Number} index The index to use as a reference for the calculations
     * @param {Boolean} down Wether the calculation was requested when the user scrolls down
     */
    getPredictedBufferIndex : function(index, inRange, down)
    {
        if (!inRange) {
            if (index + this.ds.bufferSize >= this.ds.totalLength) {
                return this.ds.totalLength - this.ds.bufferSize;
            }
            // we need at last to render the index + the visible Rows
            return Math.max(0, (index + this.visibleRows) - Math.round(this.ds.bufferSize/2));
        }
        if (!down) {
            return Math.max(0, (index-this.ds.bufferSize)+this.visibleRows);
        }

        if (down) {
            return Math.max(0, Math.min(index, this.ds.totalLength-this.ds.bufferSize));
        }
    },


    /**
     * Updates the table view. Removes/appends rows as needed and fetches the
     * cells content out of the available store. If the needed rows are not within
     * the buffer, the method will advise the store to update it's contents.
     *
     * The method puts the requested cursor into the queue if a previously called
     * buffering is in process.
     *
     * @param {Number} cursor The row's position, absolute to it's position in the
     *                        data model
     *
     */
    updateLiveRows: function(index, forceRepaint, forceReload)
    {
        var inRange = this.isInRange(index);

        if (this.isBuffering) {
            if (this.isPrebuffering) {
                if (inRange) {
                    this.replaceLiveRows(index, forceRepaint);
                } else {
                    this.showLoadMask(true);
                }
            }

            this.fireEvent('cursormove', this, index,
                           Math.min(this.ds.totalLength,
                           this.visibleRows-this.rowClipped),
                           this.ds.totalLength);

            this.requestQueue = index;
            return;
        }

        var lastIndex  = this.lastIndex;
        this.lastIndex = index;
        var inRange    = this.isInRange(index);

        var down = false;

        if (inRange && forceReload !== true) {

            // repaint the table's view
            this.replaceLiveRows(index, forceRepaint);
            // has to be called AFTER the rowIndex was recalculated
            this.fireEvent('cursormove', this, index,
                       Math.min(this.ds.totalLength,
                       this.visibleRows-this.rowClipped),
                       this.ds.totalLength);
            // lets decide if we can void this method or stay in here for
            // requesting a buffer update
            if (index > lastIndex) { // scrolling down

                down = true;
                var totalCount = this.ds.totalLength;

                // while scrolling, we have not yet reached the row index
                // that would trigger a re-buffer
                if (index+this.visibleRows+this.nearLimit <= this.ds.bufferRange[1]) {
                    return;
                }

                // If we have already buffered the last range we can ever get
                // by the queried data repository, we don't need to buffer again.
                // This basically means that a re-buffer would only occur again
                // if we are scrolling up.
                if (this.ds.bufferRange[1]+1 >= totalCount) {
                    return;
                }
            } else if (index < lastIndex) { // scrolling up

                down = false;
                // We are scrolling up in the first buffer range we can ever get
                // Re-buffering would only occur upon scrolling down.
                if (this.ds.bufferRange[0] <= 0) {
                    return;
                }

                // if we are scrolling up and we are moving in an acceptable
                // buffer range, lets return.
                if (index - this.nearLimit > this.ds.bufferRange[0]) {
                    return;
                }
            } else {
                return;
            }

            this.isPrebuffering = true;
        }

        // prepare for rebuffering
        this.isBuffering = true;

        var bufferOffset = this.getPredictedBufferIndex(index, inRange, down);

        if (!inRange) {
            this.showLoadMask(true);
        }

        this.ds.suspendEvents();
        var sInfo  = this.ds.sortInfo;

        var params = {};
        if (this.ds.lastOptions) {
            Ext.apply(params, this.ds.lastOptions.params);
        }

        params.start = bufferOffset;
        params.limit = this.ds.bufferSize;

        if (sInfo) {
            params.dir  = sInfo.direction;
            params.sort = sInfo.field;
        }

        var opts = {
            forceRepaint     : forceRepaint,
            callback         : this.liveBufferUpdate,
            scope            : this,
            params           : params,
            suspendLoadEvent : true
        };

        this.fireEvent('beforebuffer', this, this.ds, index,
            Math.min(this.ds.totalLength, this.visibleRows-this.rowClipped),
            this.ds.totalLength, opts
        );

        this.ds.load(opts);
        this.ds.resumeEvents();
    },

    /**
     * Shows this' view own load mask to indicate that a large amount of buffer
     * data was requested by the store.
     * @param {Boolean} show <tt>true</tt> to show the load mask, otherwise
     *                       <tt>false</tt>
     */
    showLoadMask : function(show)
    {
        if (!this.loadMask) {
            return;
        }

        var dom  = this._loadMaskAnchor.dom;
        var data = Ext.Element.data;

        var mask    = data(dom, 'mask');
        var maskMsg = data(dom, 'maskMsg');

        if (show) {
            mask.setDisplayed(true);
            maskMsg.setDisplayed(true);
            maskMsg.center(this._loadMaskAnchor);
            // this lines will help IE8 to re-calculate the height of the loadmask
            if(Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && this._loadMaskAnchor.getStyle('height') == 'auto'){
	            mask.setSize(undefined, this._loadMaskAnchor.getHeight());
	        }
        } else {
            mask.setDisplayed(false);
            maskMsg.setDisplayed(false);
        }
    },

    /**
     * Renders the table body with the contents of the model. The method will
     * prepend/ append rows after removing from either the end or the beginning
     * of the table DOM to reduce expensive DOM calls.
     * It will also take care of rendering the rows selected, taking the property
     * <tt>bufferedSelections</tt> of the {@link BufferedRowSelectionModel} into
     * account.
     * Instead of calling this method directly, the <tt>updateLiveRows</tt> method
     * should be called which takes care of rebuffering if needed, since this method
     * will behave erroneous if data of the buffer is requested which may not be
     * available.
     *
     * @param {Number} cursor The position of the data in the model to start
     *                        rendering.
     *
     * @param {Boolean} forceReplace <tt>true</tt> for recomputing the DOM in the
     *                               view, otherwise <tt>false</tt>.
     */
    // private
    replaceLiveRows : function(cursor, forceReplace, processRows)
    {
        var spill = cursor-this.lastRowIndex;

        if (spill == 0 && forceReplace !== true) {
            return;
        }

        // decide wether to prepend or append rows
        // if spill is negative, we are scrolling up. Thus we have to prepend
        // rows. If spill is positive, we have to append the buffers data.
        var append = spill > 0;

        // abs spill for simplyfiying append/prepend calculations
        spill = Math.abs(spill);

        // adjust cursor to the buffered model index
        var bufferRange = this.ds.bufferRange;
        var cursorBuffer = cursor-bufferRange[0];

        // compute the last possible renderindex
        var lpIndex = Math.min(cursorBuffer+this.visibleRows-1, bufferRange[1]-bufferRange[0]);
        // we can skip checking for append or prepend if the spill is larger than
        // visibleRows. We can paint the whole rows new then-
        if (spill >= this.visibleRows || spill == 0) {
            this.mainBody.update(this.renderRows(cursorBuffer, lpIndex));
        } else {
            if (append) {

                this.removeRows(0, spill-1);

                if (cursorBuffer+this.visibleRows-spill <= bufferRange[1]-bufferRange[0]) {
                    var html = this.renderRows(
                        cursorBuffer+this.visibleRows-spill,
                        lpIndex
                    );
                    Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);

                }

            } else {
                this.removeRows(this.visibleRows-spill, this.visibleRows-1);
                var html = this.renderRows(cursorBuffer, cursorBuffer+spill-1);
                Ext.DomHelper.insertHtml('beforeBegin', this.mainBody.dom.firstChild, html);

            }
        }

        if (processRows !== false) {
            this.processRows(0, undefined, true);
        }
        this.lastRowIndex = cursor;
    },



    /**
    * Adjusts the scroller height to make sure each row in the dataset will be
    * can be displayed, no matter which value the current height of the grid
    * component equals to.
    */
    // protected
    adjustBufferInset : function()
    {
        var liveScrollerDom = this.liveScroller.dom;
        var g = this.grid, ds = g.store;
        var c  = g.getGridEl();
        var elWidth = c.getSize().width;

        // hidden rows is the number of rows which cannot be
        // displayed and for which a scrollbar needs to be
        // rendered. This does also take clipped rows into account
        var hiddenRows = (ds.totalLength == this.visibleRows-this.rowClipped)
                       ? 0
                       : Math.max(0, ds.totalLength-(this.visibleRows-this.rowClipped));

        if (hiddenRows == 0) {
            this.scroller.setWidth(elWidth);
            liveScrollerDom.style.display = 'none';
            return;
        } else {
            this.scroller.setWidth(elWidth-this.scrollOffset);
            liveScrollerDom.style.display = '';
        }

        var scrollbar = this.cm.getTotalWidth()+this.scrollOffset > elWidth;

        // adjust the height of the scrollbar
        var contHeight = liveScrollerDom.parentNode.offsetHeight +
                         ((ds.totalLength > 0 && scrollbar)
                         ? - this.horizontalScrollOffset
                         : 0)
                         - this.hdHeight;

        liveScrollerDom.style.height = Math.max(contHeight, this.horizontalScrollOffset*2)+"px";

        if (this.rowHeight == -1) {
            return;
        }

        this.liveScrollerInset.style.height = (hiddenRows == 0 ? 0 : contHeight+(hiddenRows*this.rowHeight))+"px";
    },

    /**
     * Recomputes the number of visible rows in the table based upon the height
     * of the component. The method adjusts the <tt>rowIndex</tt> property as
     * needed, if the sum of visible rows and the current row index exceeds the
     * number of total data available.
     */
    // protected
    adjustVisibleRows : function()
    {
        if (this.rowHeight == -1) {
            if (this.getRows()[0]) {
                this.rowHeight = this.getRows()[0].offsetHeight;

                if (this.rowHeight <= 0) {
                    this.rowHeight = -1;
                    return;
                }

            } else {
                return;
            }
        }


        var g = this.grid, ds = g.store;

        var c     = g.getGridEl();
        var cm    = this.cm;
        var size  = c.getSize();
        var width = size.width;
        var vh    = size.height;

        var vw = width-this.scrollOffset;
        // horizontal scrollbar shown?
        if (cm.getTotalWidth() > vw) {
            // yes!
            vh -= this.horizontalScrollOffset;
        }

        vh -= this.mainHd.getHeight();

        var totalLength = ds.totalLength || 0;

        var visibleRows = Math.max(1, Math.floor(vh/this.rowHeight));

        this.rowClipped = 0;
        // only compute the clipped row if the total length of records
        // exceeds the number of visible rows displayable
        if (totalLength > visibleRows && this.rowHeight / 3 < (vh - (visibleRows*this.rowHeight))) {
            visibleRows = Math.min(visibleRows+1, totalLength);
            this.rowClipped = 1;
        }

        // if visibleRows   didn't change, simply void and return.
        if (this.visibleRows == visibleRows) {
            return;
        }

        this.visibleRows = visibleRows;

        // skip recalculating the row index if we are currently buffering, but not if we
        // are just pre-buffering
        if (this.isBuffering && !this.isPrebuffering) {
            return;
        }

        // when re-rendering, doe not take the clipped row into account
        if (this.rowIndex + (visibleRows-this.rowClipped) > totalLength) {
            this.rowIndex     = Math.max(0, totalLength-(visibleRows-this.rowClipped));
            this.lastRowIndex = this.rowIndex;
        }

        this.updateLiveRows(this.rowIndex, true);
    },


    adjustScrollerPos : function(pixels, suspendEvent)
    {
        if (pixels == 0) {
            return;
        }
        var liveScroller = this.liveScroller;
        var scrollDom    = liveScroller.dom;

        if (suspendEvent === true) {
            liveScroller.un('scroll', this.onLiveScroll, this);
        }
        this.lastScrollPos   = scrollDom.scrollTop;
        scrollDom.scrollTop += pixels;

        if (suspendEvent === true) {
            scrollDom.scrollTop = scrollDom.scrollTop;
            liveScroller.on('scroll', this.onLiveScroll, this, {buffer : this.scrollDelay});
        }

    }



});/**
 * Ext.ux.grid.livegrid.JsonReader
 * Copyright (c) 2007-2008, http://www.siteartwork.de
 *
 * Ext.ux.grid.livegrid.JsonReader is licensed under the terms of the
 *                  GNU Open Source GPL 3.0
 * license.
 *
 * Commercial use is prohibited. Visit <http://www.siteartwork.de/livegrid>
 * if you need to obtain a commercial license.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 */

Ext.namespace('Ext.ux.grid.livegrid');

/**
 * @class Ext.ux.grid.livegrid.JsonReader
 * @extends Ext.data.JsonReader
 * @constructor
 * @param {Object} config
 *
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 */
Ext.ux.grid.livegrid.JsonReader = function(meta, recordType){

    Ext.ux.grid.livegrid.JsonReader.superclass.constructor.call(this, meta, recordType);
};


Ext.extend(Ext.ux.grid.livegrid.JsonReader, Ext.data.JsonReader, {

    /**
     * @cfg {String} versionProperty Name of the property from which to retrieve the
     *                               version of the data repository this reader parses
     *                               the reponse from
     */



    /**
     * Create a data block containing Ext.data.Records from a JSON object.
     * @param {Object} o An object which contains an Array of row objects in the property specified
     * in the config as 'root, and optionally a property, specified in the config as 'totalProperty'
     * which contains the total size of the dataset.
     * @return {Object} data A data block which is used by an Ext.data.Store object as
     * a cache of Ext.data.Records.
     */
    readRecords : function(o)
    {
        var s = this.meta;

        if(!this.ef && s.versionProperty) {
            this.getVersion = this.getJsonAccessor(s.versionProperty);
        }

        // shorten for future calls
        if (!this.__readRecords) {
            this.__readRecords = Ext.ux.grid.livegrid.JsonReader.superclass.readRecords;
        }

        var intercept = this.__readRecords.call(this, o);


        if (s.versionProperty) {
            var v = this.getVersion(o);
            intercept.version = (v === undefined || v === "") ? null : v;
        }


        return intercept;
    }

});/**
 * Ext.ux.grid.livegrid.RowSelectionModel
 * Copyright (c) 2007-2008, http://www.siteartwork.de
 *
 * Ext.ux.grid.livegrid.RowSelectionModel is licensed under the terms of the
 *                  GNU Open Source GPL 3.0
 * license.
 *
 * Commercial use is prohibited. Visit <http://www.siteartwork.de/livegrid>
 * if you need to obtain a commercial license.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 */

Ext.namespace('Ext.ux.grid.livegrid');

/**
 * @class Ext.ux.grid.livegrid.RowSelectionModel
 * @extends Ext.grid.RowSelectionModel
 * @constructor
 * @param {Object} config
 *
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 */
Ext.ux.grid.livegrid.RowSelectionModel = function(config) {


    this.addEvents({
        /**
         * The selection dirty event will be triggered in case records were
         * inserted/ removed at view indexes that may affect the current
         * selection ranges which are only represented by view indexes, but not
         * current record-ids
         */
        'selectiondirty' : true
    });

    Ext.apply(this, config);

    this.pendingSelections = {};

    Ext.ux.grid.livegrid.RowSelectionModel.superclass.constructor.call(this);

};

Ext.extend(Ext.ux.grid.livegrid.RowSelectionModel, Ext.grid.RowSelectionModel, {


 // private
    initEvents : function()
    {
        Ext.ux.grid.livegrid.RowSelectionModel.superclass.initEvents.call(this);

        this.grid.view.on('rowsinserted',    this.onAdd,            this);
        this.grid.store.on('selectionsload', this.onSelectionsLoad, this);
    },

    /**
     * Callback is called when a row gets removed in the view. The process to
     * invoke this method is as follows:
     *
     * <ul>
     *  <li>1. store.remove(record);</li>
     *  <li>2. view.onRemove(store, record, indexInStore, isUpdate)<br />
     *   [view triggers rowremoved event]</li>
     *  <li>3. this.onRemove(view, indexInStore, record)</li>
     * </ul>
     *
     * If r defaults to <tt>null</tt> and index is within the pending selections
     * range, the selectionchange event will be called, too.
     * Additionally, the method will shift all selections and trigger the
     * selectiondirty event if any selections are pending.
     *
     */
    onRemove : function(v, index, r)
    {
        var ranges           = this.getPendingSelections();
        var rangesLength     = ranges.length;
        var selectionChanged = false;

        // if index equals to Number.MIN_VALUE or Number.MAX_VALUE, mark current
        // pending selections as dirty
        if (index == Number.MIN_VALUE || index == Number.MAX_VALUE) {

            if (r) {
                // if the record is part of the current selection, shift the selection down by 1
                // if the index equals to Number.MIN_VALUE
                if (this.isIdSelected(r.id) && index == Number.MIN_VALUE) {
                    // bufferRange already counted down when this method gets
                    // called
                    this.shiftSelections(this.grid.store.bufferRange[1], -1);
                }
                this.selections.remove(r);
                selectionChanged = true;
            }

            // clear all pending selections that are behind the first
            // bufferrange, and shift all pending Selections that lay in front
            // front of the second bufferRange down by 1!
            if (index == Number.MIN_VALUE) {
                this.clearPendingSelections(0, this.grid.store.bufferRange[0]);
            } else {
                // clear pending selections that are in front of bufferRange[1]
                this.clearPendingSelections(this.grid.store.bufferRange[1]);
            }

            // only fire the selectiondirty event if there were pendning ranges
            if (rangesLength != 0) {
                this.fireEvent('selectiondirty', this, index, 1);
            }

        } else {

            selectionChanged = this.isIdSelected(r.id);

            // if the record was not part of the selection, return
            if (!selectionChanged) {
                return;
            }

            this.selections.remove(r);
            //this.last = false;
            // if there are currently pending selections, look up the interval
            // to tell whether removing the record would mark the selection dirty
            if (rangesLength != 0) {

                var startRange = ranges[0];
                var endRange   = ranges[rangesLength-1];
                if (index <= endRange || index <= startRange) {
                    this.shiftSelections(index, -1);
                    this.fireEvent('selectiondirty', this, index, 1);
                }
             }

        }

        if (selectionChanged) {
            this.fireEvent('selectionchange', this);
        }
    },


    /**
     * If records where added to the store, this method will work as a callback,
     * called by the views' rowsinserted event.
     * Selections will be shifted down if, and only if, the listeners for the
     * selectiondirty event will return <tt>true</tt>.
     *
     */
    onAdd : function(store, index, endIndex, recordLength)
    {
        var ranges       = this.getPendingSelections();
        var rangesLength = ranges.length;

        // if index equals to Number.MIN_VALUE or Number.MAX_VALUE, mark current
        // pending selections as dirty
        if ((index == Number.MIN_VALUE || index == Number.MAX_VALUE)) {

            if (index == Number.MIN_VALUE) {
                // bufferRange already counted down when this method gets
                // called
                this.clearPendingSelections(0, this.grid.store.bufferRange[0]);
                this.shiftSelections(this.grid.store.bufferRange[1], recordLength);
            } else {
                this.clearPendingSelections(this.grid.store.bufferRange[1]);
            }

            // only fire the selectiondirty event if there were pendning ranges
            if (rangesLength != 0) {
                this.fireEvent('selectiondirty', this, index, r);
            }

            return;
        }

        // it is safe to say that the selection is dirty when the inserted index
        // is less or equal to the first selection range index or less or equal
        // to the last selection range index
        var startRange = ranges[0];
        var endRange   = ranges[rangesLength-1];
        var viewIndex  = index;
        if (viewIndex <= endRange || viewIndex <= startRange) {
            this.fireEvent('selectiondirty', this, viewIndex, recordLength);
            this.shiftSelections(viewIndex, recordLength);
        }
    },



    /**
     * Shifts current/pending selections. This method can be used when rows where
     * inserted/removed and the selection model has to synchronize itself.
     */
    shiftSelections : function(startRow, length)
    {
        var index         = 0;
        var newIndex      = 0;
        var newRequests   = {};

        var ds            = this.grid.store;
        var storeIndex    = startRow-ds.bufferRange[0];
        var newStoreIndex = 0;
        var totalLength   = this.grid.store.totalLength;
        var rec           = null;

        //this.last = false;

        var ranges       = this.getPendingSelections();
        var rangesLength = ranges.length;

        if (rangesLength == 0) {
            return;
        }

        for (var i = 0; i < rangesLength; i++) {
            index = ranges[i];

            if (index < startRow) {
                continue;
            }

            newIndex      = index+length;
            newStoreIndex = storeIndex+length;
            if (newIndex >= totalLength) {
                break;
            }

            rec = ds.getAt(newStoreIndex);
            if (rec) {
                this.selections.add(rec);
            } else {
                newRequests[newIndex] = true;
            }
        }

        this.pendingSelections = newRequests;
    },

    /**
     *
     * @param {Array} records The records that have been loaded
     * @param {Array} ranges  An array representing the model index ranges the
     *                        reords have been loaded for.
     */
    onSelectionsLoad : function(store, records, ranges)
    {
        this.replaceSelections(records);
    },

    /**
     * Returns true if there is a next record to select
     * @return {Boolean}
     */
    hasNext : function()
    {
        return this.last !== false && (this.last+1) < this.grid.store.getTotalCount();
    },

    /**
     * Gets the number of selected rows.
     * @return {Number}
     */
    getCount : function()
    {
        return this.selections.length + this.getPendingSelections().length;
    },

    /**
     * Returns True if the specified row is selected.
     *
     * @param {Number/Record} record The record or index of the record to check
     * @return {Boolean}
     */
    isSelected : function(index)
    {
        if (typeof index == "number") {
            var orgInd = index;
            index = this.grid.store.getAt(orgInd);
            if (!index) {
                var ind = this.getPendingSelections().indexOf(orgInd);
                if (ind != -1) {
                    return true;
                }

                return false;
            }
        }

        var r = index;
        return (r && this.selections.key(r.id) ? true : false);
    },


    /**
     * Deselects a record.
     * The emthod assumes that the record is physically available, i.e.
     * pendingSelections will not be taken into account
     */
    deselectRecord : function(record, preventViewNotify)
    {
        if(this.locked) {
            return;
        }

        var isSelected = this.selections.key(record.id);

        if (!isSelected) {
            return;
        }

        var store = this.grid.store;
        var index = store.indexOfId(record.id);

        if (index == -1) {
            index = store.findInsertIndex(record);
            if (index != Number.MIN_VALUE && index != Number.MAX_VALUE) {
                index += store.bufferRange[0];
            }
        } else {
            // just to make sure, though this should not be
            // set if the record was availablein the selections
            delete this.pendingSelections[index];
        }

        if (this.last == index) {
            this.last = false;
        }

        if (this.lastActive == index) {
            this.lastActive = false;
        }

        this.selections.remove(record);

        if(!preventViewNotify){
            this.grid.getView().onRowDeselect(index);
        }

        this.fireEvent("rowdeselect", this, index, record);
        this.fireEvent("selectionchange", this);
    },

    /**
     * Deselects a row.
     * @param {Number} row The index of the row to deselect
     */
    deselectRow : function(index, preventViewNotify)
    {
        if(this.locked) return;
        if(this.last == index){
            this.last = false;
        }

        if(this.lastActive == index){
            this.lastActive = false;
        }
        var r = this.grid.store.getAt(index);

        delete this.pendingSelections[index];

        if (r) {
            this.selections.remove(r);
        }
        if(!preventViewNotify){
            this.grid.getView().onRowDeselect(index);
        }
        this.fireEvent("rowdeselect", this, index, r);
        this.fireEvent("selectionchange", this);
    },


    /**
     * Selects a row.
     * @param {Number} row The index of the row to select
     * @param {Boolean} keepExisting (optional) True to keep existing selections
     */
    selectRow : function(index, keepExisting, preventViewNotify)
    {
        if(//this.last === index
           //||
           this.locked
           || index < 0
           || index >= this.grid.store.getTotalCount()) {
            return;
        }

        var r = this.grid.store.getAt(index);

        if(this.fireEvent("beforerowselect", this, index, keepExisting, r) !== false){
            if(!keepExisting || this.singleSelect){
                this.clearSelections();
            }

            if (r) {
                this.selections.add(r);
                delete this.pendingSelections[index];
            } else {
                this.pendingSelections[index] = true;
            }

            this.last = this.lastActive = index;

            if(!preventViewNotify){
                this.grid.getView().onRowSelect(index);
            }

            this.fireEvent("rowselect", this, index, r);
            this.fireEvent("selectionchange", this);
        }
    },

    clearPendingSelections : function(startIndex, endIndex)
    {
        if (endIndex == undefined) {
            endIndex = Number.MAX_VALUE;
        }

        var newSelections = {};

        var ranges       = this.getPendingSelections();
        var rangesLength = ranges.length;

        var index = 0;

        for (var i = 0; i < rangesLength; i++) {
            index = ranges[i];
            if (index <= endIndex && index >= startIndex) {
                continue;
            }

            newSelections[index] = true;
        }

        this.pendingSelections = newSelections;
    },

    /**
     * Replaces already set data with new data from the store if those
     * records can be found within this.selections or this.pendingSelections
     *
     * @param {Array} An array with records buffered by the store
     */
    replaceSelections : function(records)
    {
        if (!records || records.length == 0) {
            return;
        }

        var ds  = this.grid.store;
        var rec = null;

        var assigned     = [];
        var ranges       = this.getPendingSelections();
        var rangesLength = ranges.length

        var selections = this.selections;
        var index      = 0;

        for (var i = 0; i < rangesLength; i++) {
            index = ranges[i];
            rec   = ds.getAt(index);
            if (rec) {
                selections.add(rec);
                assigned.push(rec.id);
                delete this.pendingSelections[index];
            }
        }

        var id  = null;
        for (i = 0, len = records.length; i < len; i++) {
            rec = records[i];
            id  = rec.id;
            if (assigned.indexOf(id) == -1 && selections.containsKey(id)) {
                selections.add(rec);
            }
        }

    },

    getPendingSelections : function(asRange)
    {
        var index         = 1;
        var ranges        = [];
        var currentRange  = 0;
        var tmpArray      = [];

        for (var i in this.pendingSelections) {
            tmpArray.push(parseInt(i));
        }

        tmpArray.sort(function(o1,o2){
            if (o1 > o2) {
                return 1;
            } else if (o1 < o2) {
                return -1;
            } else {
                return 0;
            }
        });

        if (!asRange) {
            return tmpArray;
        }

        var max_i = tmpArray.length;

        if (max_i == 0) {
            return [];
        }

        ranges[currentRange] = [tmpArray[0], tmpArray[0]];
        for (var i = 0, max_i = max_i-1; i < max_i; i++) {
            if (tmpArray[i+1] - tmpArray[i] == 1) {
                ranges[currentRange][1] = tmpArray[i+1];
            } else {
                currentRange++;
                ranges[currentRange] = [tmpArray[i+1], tmpArray[i+1]];
            }
        }

        return ranges;
    },

    /**
     * Clears all selections.
     */
    clearSelections : function(fast)
    {
        if(this.locked) return;
        if(fast !== true){
            var ds  = this.grid.store;
            var s   = this.selections;
            var ind = -1;
            s.each(function(r){
                ind = ds.indexOfId(r.id);
                if (ind != -1) {
                    this.deselectRow(ind+ds.bufferRange[0]);
                }
            }, this);
            s.clear();

            this.pendingSelections = {};

        }else{
            this.selections.clear();
            this.pendingSelections    = {};
        }
        this.last = false;
    },


    /**
     * Selects a range of rows. All rows in between startRow and endRow are also
     * selected.
     *
     * @param {Number} startRow The index of the first row in the range
     * @param {Number} endRow The index of the last row in the range
     * @param {Boolean} keepExisting (optional) True to retain existing selections
     */
    selectRange : function(startRow, endRow, keepExisting)
    {
        if(this.locked) {
            return;
        }

        if(!keepExisting) {
            this.clearSelections();
        }

        if (startRow <= endRow) {
            for(var i = startRow; i <= endRow; i++) {
                this.selectRow(i, true);
            }
        } else {
            for(var i = startRow; i >= endRow; i--) {
                this.selectRow(i, true);
            }
        }

    }

});


/**
 * Ext.ux.grid.livegrid.Store
 * Copyright (c) 2007-2008, http://www.siteartwork.de
 *
 * Ext.ux.grid.livegrid.Store is licensed under the terms of the
 *                  GNU Open Source GPL 3.0
 * license.
 *
 * Commercial use is prohibited. Visit <http://www.siteartwork.de/livegrid>
 * if you need to obtain a commercial license.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 */

Ext.namespace('Ext.ux.grid.livegrid');

/**
 * @class Ext.ux.grid.livegrid.Store
 * @extends Ext.data.Store
 *
 * The BufferedGridSore is a special implementation of a Ext.data.Store. It is used
 * for loading chunks of data from the underlying data repository as requested
 * by the Ext.ux.BufferedGridView. It's size is limited to the config parameter
 * bufferSize and is thereby guaranteed to never hold more than this amount
 * of records in the store.
 *
 * Requesting selection ranges:
 * ----------------------------
 * This store implementation has 2 Http-proxies: A data proxy for requesting data
 * from the server for displaying and another proxy to request pending selections:
 * Pending selections are represented by row indexes which have been selected but
 * which records have not yet been available in the store. The loadSelections method
 * will initiate a request to the data repository (same url as specified in the
 * url config parameter for the store) to fetch the pending selections. The additional
 * parameter send to the server is the "ranges" parameter, which will hold a json
 * encoded string representing ranges of row indexes to load from the data repository.
 * As an example, pending selections with the indexes 1,2,3,4,5,9,10,11,16 would
 * have to be translated to [1,5],[9,11],[16].
 * Please note, that by indexes we do not understand (primary) keys of the data,
 * but indexes as represented by the view. To get the ranges of pending selections,
 * you can use the getPendingSelections method of the BufferedRowSelectionModel, which
 * should be used as the default selection model of the grid.
 *
 * Version-property:
 * -----------------
 * This implementation does also introduce a new member called "version". The version
 * property will help you in determining if any pending selections indexes are still
 * valid or may have changed. This is needed to reduce the danger of data inconsitence
 * when you are requesting data from the server: As an example, a range of indexes must
 * be read from the server but may have been become invalid when the row represented
 * by the index is no longer available in teh underlying data store, caused by a
 * delete or insert operation. Thus, you have to take care of the version property
 * by yourself (server side) and change this value whenever a row was deleted or
 * inserted. You can specify the path to the version property in the BufferedJsonReader,
 * which should be used as the default reader for this store. If the store recognizes
 * a version change, it will fire the versionchange event. It is up to the user
 * to remove all selections which are pending, or use them anyway.
 *
 * Inserting data:
 * ---------------
 * Another thing to notice is the way a user inserts records into the data store.
 * A user should always provide a sortInfo for the grid, so the findInsertIndex
 * method can return a value that comes close to the value as it would have been
 * computed by the underlying store's sort algorithm. Whenever a record should be
 * added to the store, the insert index should be calculated and the used as the
 * parameter for the insert method. The findInsertIndex method will return a value
 * that equals to Number.MIN_VALUE or Number.MAX_VALUE if the added record would not
 * change the current state of the store. If that happens, this data is not available
 * in the store, and may be requested later on when a new request for new data is made.
 *
 * Sorting:
 * --------
 * remoteSort will always be set to true, no matter what value the user provides
 * using the config object.
 *
 * @constructor
 * Creates a new Store.
 * @param {Object} config A config object containing the objects needed for the Store to access data,
 * and read the data into Records.
 *
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 */
Ext.ux.grid.livegrid.Store = function(config) {

    config = config || {};

    // remoteSort will always be set to true.
    config.remoteSort = true;

    // we will intercept the autoLoad property and set it to false so we do not
    // load any contents of the store before the View has not fully initialized
    // itself. if autoLoad was set to true, the Ext.ux.grid.livegrid.GridPanel
    // will take care of loading the store once it has been rendered
    this._autoLoad  = config.autoLoad ? true : false;
    config.autoLoad = false;

    this.addEvents(
         /**
          * @event bulkremove
          * Fires when a bulk remove operation was finished.
          * @param {Ext.ux.BufferedGridStore} this
          * @param {Array} An array with the records that have been removed.
          * The values for each array index are
          * record - the record that was removed
          * index - the index of the removed record in the store
          */
        'bulkremove',
         /**
          * @event versionchange
          * Fires when the version property has changed.
          * @param {Ext.ux.BufferedGridStore} this
          * @param {String} oldValue
          * @param {String} newValue
          */
        'versionchange',
         /**
          * @event beforeselectionsload
          * Fires before the store sends a request for ranges of records to
          * the server.
          * @param {Ext.ux.BufferedGridStore} this
          * @param {Array} ranges
          */
        'beforeselectionsload',
         /**
          * @event selectionsload
          * Fires when selections have been loaded.
          * @param {Ext.ux.BufferedGridStore} this
          * @param {Array} records An array containing the loaded records from
          * the server.
          * @param {Array} ranges An array containing the ranges of indexes this
          * records may represent.
          */
        'selectionsload'
    );

    Ext.ux.grid.livegrid.Store.superclass.constructor.call(this, config);

    this.totalLength = 0;


    /**
     * The array represents the range of rows available in the buffer absolute to
     * the indexes of the data model. Initialized with  [-1, -1] which tells that no
     * records are currrently buffered
     * @param {Array}
     */
    this.bufferRange = [-1, -1];

    this.on('clear', function (){
        this.bufferRange = [-1, -1];
    }, this);

    if(this.url && !this.selectionsProxy){
        this.selectionsProxy = new Ext.data.HttpProxy({url: this.url});
    }

};

Ext.extend(Ext.ux.grid.livegrid.Store, Ext.data.Store, {

    /**
     * The version of the data in the store. This value is represented by the
     * versionProperty-property of the BufferedJsonReader.
     * @property
     */
    version : null,

    /**
     * Inserts a record at the position as specified in index.
     * If the index equals to Number.MIN_VALUE or Number.MAX_VALUE, the record will
     * not be added to the store, but still fire the add-event to indicate that
     * the set of data in the underlying store has been changed.
     * If the index equals to 0 and the length of data in the store equals to
     * bufferSize, the add-event will be triggered with Number.MIN_VALUE to
     * indicate that a record has been prepended. If the index equals to
     * bufferSize, the method will assume that the record has been appended and
     * trigger the add event with index set to Number.MAX_VALUE.
     *
     * Note:
     * -----
     * The index parameter is not a view index, but a value in the range of
     * [0, this.bufferSize].
     *
     * You are strongly advised to not use this method directly. Instead, call
     * findInsertIndex wirst and use the return-value as the first parameter for
     * for this method.
     */
    insert : function(index, records)
    {
        // hooray for haskell!
        records = [].concat(records);

        index = index >= this.bufferSize ? Number.MAX_VALUE : index;

        if (index == Number.MIN_VALUE || index == Number.MAX_VALUE) {
            var l = records.length;
            if (index == Number.MIN_VALUE) {
                this.bufferRange[0] += l;
                this.bufferRange[1] += l;
            }

            this.totalLength += l;
            this.fireEvent("add", this, records, index);
            return;
        }

        var split = false;
        var insertRecords = records;
        if (records.length + index >= this.bufferSize) {
            split = true;
            insertRecords = records.splice(0, this.bufferSize-index)
        }
        this.totalLength += insertRecords.length;

        // if the store was loaded without data and the bufferRange
        // has to be filled first
        if (this.bufferRange[0] <= -1) {
            this.bufferRange[0] = 0;
        }
        if (this.bufferRange[1] < (this.bufferSize-1)) {
            this.bufferRange[1] = Math.min(this.bufferRange[1] + insertRecords.length, this.bufferSize-1);
        }

        for (var i = 0, len = insertRecords.length; i < len; i++) {
            this.data.insert(index, insertRecords[i]);
            insertRecords[i].join(this);
        }

        while (this.getCount() > this.bufferSize) {
            this.data.remove(this.data.last());
        }

        this.fireEvent("add", this, insertRecords, index);

        if (split == true) {
            this.fireEvent("add", this, records, Number.MAX_VALUE);
        }
    },

    /**
     * Remove a Record from the Store and fires the remove event.
     *
     * This implementation will check for the appearance of the record id
     * in the store. The record to be removed does not neccesarily be bound
     * to the instance of this store.
     * If the record is not within the store, the method will try to guess it's
     * index by calling findInsertIndex.
     *
     * Please note that this method assumes that the records that's about to
     * be removed from the store does belong to the data within the store or the
     * underlying data store, thus the remove event will always be fired.
     * This may lead to inconsitency if you have to stores up at once. Let A
     * be the store that reads from the data repository C, and B the other store
     * that only represents a subset of data of the data repository C. If you
     * now remove a record X from A, which has not been in the store, but is assumed
     * to be available in the data repository, and would like to sync the available
     * data of B, then you have to check first if X may have apperead in the subset
     * of data C represented by B before calling remove from the B store (because
     * the remove operation will always trigger the "remove" event, no matter what).
     * (Common use case: you have selected a range of records which are then stored in
     * the row selection model. User scrolls through the data and the store's buffer
     * gets refreshed with new data for displaying. Now you want to remove all records
     * which are within the rowselection model, but not anymore within the store.)
     * One possible workaround is to only remove the record X from B if, and only
     * if the return value of a call to [object instance of store B].data.indexOf(X)
     * does not return a value less than 0. Though not removing the record from
     * B may not update the view of an attached BufferedGridView immediately.
     *
     * @param {Ext.data.Record} record
     * @param {Boolean} suspendEvent true to suspend the "remove"-event
     *
     * @return Number the index of the record removed.
     */
    remove : function(record, suspendEvent)
    {
        // check wether the record.id can be found in this store
        var index = this._getIndex(record);

        if (index < 0) {
            this.totalLength -= 1;
            if(this.pruneModifiedRecords){
                this.modified.remove(record);
            }
            // adjust the buffer range if a record was removed
            // in the range that is actually behind the bufferRange
            this.bufferRange[0] = Math.max(-1, this.bufferRange[0]-1);
            this.bufferRange[1] = Math.max(-1, this.bufferRange[1]-1);

            if (suspendEvent !== true) {
                this.fireEvent("remove", this, record, index);
            }
            return index;
        }

        this.bufferRange[1] = Math.max(-1, this.bufferRange[1]-1);
        this.data.removeAt(index);

        if(this.pruneModifiedRecords){
            this.modified.remove(record);
        }

        this.totalLength -= 1;
        if (suspendEvent !== true) {
            this.fireEvent("remove", this, record, index);
        }

        return index;
    },

    _getIndex : function(record)
    {
        var index = this.indexOfId(record.id);

        if (index < 0) {
            index = this.findInsertIndex(record);
        }

        return index;
    },

    /**
     * Removes a larger amount of records from the store and fires the "bulkremove"
     * event.
     * This helps listeners to determine whether the remove operation of multiple
     * records is still pending.
     *
     * @param {Array} records
     */
    bulkRemove : function(records)
    {
        var rec  = null;
        var recs = [];
        var ind  = 0;
        var len  = records.length;

        var orgIndexes = [];
        for (var i = 0; i < len; i++) {
            rec = records[i];

            orgIndexes[rec.id] = this._getIndex(rec);
        }

        for (var i = 0; i < len; i++) {
            rec = records[i];
            this.remove(rec, true);
            recs.push([rec, orgIndexes[rec.id]]);
        }

        this.fireEvent("bulkremove", this, recs);
    },

    /**
     * Remove all Records from the Store and fires the clear event.
     * The method assumes that there will be no data available anymore in the
     * underlying data store.
     */
    removeAll : function()
    {
        this.totalLength = 0;
        this.bufferRange = [-1, -1];
        this.data.clear();

        if(this.pruneModifiedRecords){
            this.modified = [];
        }
        this.fireEvent("clear", this);
    },

    /**
     * Requests a range of data from the underlying data store. Similiar to the
     * start and limit parameter usually send to the server, the method needs
     * an array of ranges of indexes.
     * Example: To load all records at the positions 1,2,3,4,9,12,13,14, the supplied
     * parameter should equal to [[1,4],[9],[12,14]].
     * The request will only be done if the beforeselectionsloaded events return
     * value does not equal to false.
     */
    loadRanges : function(ranges)
    {
        var max_i = ranges.length;

        if(max_i > 0 && !this.selectionsProxy.activeRequest[Ext.data.Api.actions.read]
           && this.fireEvent("beforeselectionsload", this, ranges) !== false){

            var lParams = this.lastOptions.params;

            var params = {};
            params.ranges = Ext.encode(ranges);

            if (lParams) {
                if (lParams.sort) {
                    params.sort = lParams.sort;
                }
                if (lParams.dir) {
                    params.dir = lParams.dir;
                }
            }

            var options = {};
            for (var i in this.lastOptions) {
                options.i = this.lastOptions.i;
            }

            options.ranges = params.ranges;

            this.selectionsProxy.load(params, this.reader,
                            this.selectionsLoaded, this,
                            options);
        }
    },

    /**
     * Alias for loadRanges.
     */
    loadSelections : function(ranges)
    {
        if (ranges.length == 0) {
            return;
        }
        this.loadRanges(ranges);
    },

    /**
     * Called as a callback by the proxy which loads pending selections.
     * Will fire the selectionsload event with the loaded records if, and only
     * if the return value of the checkVersionChange event does not equal to
     * false.
     */
    selectionsLoaded : function(o, options, success)
    {
        if (this.checkVersionChange(o, options, success) !== false) {

            var r = o.records;
            for(var i = 0, len = r.length; i < len; i++){
                r[i].join(this);
            }

            this.fireEvent("selectionsload", this, o.records, Ext.decode(options.ranges));
        } else {
            this.fireEvent("selectionsload", this, [], Ext.decode(options.ranges));
        }
    },

    /**
     * Checks if the version supplied in <tt>o</tt> differs from the version
     * property of the current instance of this object and fires the versionchange
     * event if it does.
     */
    // private
    checkVersionChange : function(o, options, success)
    {
        if(o && success !== false){
            if (o.version !== undefined) {
                var old      = this.version;
                this.version = o.version;
                if (this.version !== old) {
                    return this.fireEvent('versionchange', this, old, this.version);
                }
            }
        }
    },

    /**
     * The sort procedure tries to respect the current data in the buffer. If the
     * found index would not be within the bufferRange, Number.MIN_VALUE is returned to
     * indicate that the record would be sorted below the first record in the buffer
     * range, while Number.MAX_VALUE would indicate that the record would be added after
     * the last record in the buffer range.
     *
     * The method is not guaranteed to return the relative index of the record
     * in the data model as returned by the underlying domain model.
     */
    findInsertIndex : function(record)
    {
        this.remoteSort = false;
        var index = Ext.ux.grid.livegrid.Store.superclass.findInsertIndex.call(this, record);
        this.remoteSort = true;

        // special case... index is 0 and we are at the very first record
        // buffered
        if (this.bufferRange[0] <= 0 && index == 0) {
            return index;
        } else if (this.bufferRange[0] > 0 && index == 0) {
            return Number.MIN_VALUE;
        } else if (index >= this.bufferSize) {
            return Number.MAX_VALUE;
        }

        return index;
    },

    /**
     * Removed snapshot check
     */
    // private
    sortData : function(f, direction)
    {
        direction = direction || 'ASC';
        var st = this.fields.get(f).sortType;
        var fn = function(r1, r2){
            var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
            return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
        };
        this.data.sort(direction, fn);
    },



    /**
     * @cfg {Number} bufferSize The number of records that will at least always
     * be available in the store for rendering. This value will be send to the
     * server as the <tt>limit</tt> parameter and should not change during the
     * lifetime of a grid component. Note: In a paging grid, this number would
     * indicate the page size.
     * The value should be set high enough to make a userfirendly scrolling
     * possible and should be greater than the sum of {nearLimit} and
     * {visibleRows}. Usually, a value in between 150 and 200 is good enough.
     * A lesser value will more often make the store re-request new data, while
     * a larger number will make loading times higher.
     */


    // private
    onMetaChange : function(meta, rtype, o)
    {
        this.version = null;
        Ext.ux.grid.livegrid.Store.superclass.onMetaChange.call(this, meta, rtype, o);
    },


    /**
     * Will fire the versionchange event if the version of incoming data has changed.
     */
    // private
    loadRecords : function(o, options, success)
    {
        this.checkVersionChange(o, options, success);

        // we have to stay in sync with rows that may have been skipped while
        // the request was loading.
        // if the response didn't make it through, set buffer range to -1,-1
        if (!o) {
            this.bufferRange = [-1,-1];
        } else {
            this.bufferRange = [
                options.params.start,
                Math.max(0, Math.min((options.params.start+options.params.limit)-1, o.totalRecords-1))
            ];
        }

        if (options.suspendLoadEvent === true) {
            this.suspendEvents();
        }
        Ext.ux.grid.livegrid.Store.superclass.loadRecords.call(this, o, options, success);
        if (options.suspendLoadEvent === true) {
            this.resumeEvents();
        }
    },

    /**
     * Get the Record at the specified index.
     * The function will take the bufferRange into account and translate the passed argument
     * to the index of the record in the current buffer.
     *
     * @param {Number} index The index of the Record to find.
     * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
     */
    getAt : function(index)
    {
        //anything buffered yet?
        if (this.bufferRange[0] == -1) {
            return undefined;
        }

        var modelIndex = index - this.bufferRange[0];
        return this.data.itemAt(modelIndex);
    },

//--------------------------------------EMPTY-----------------------------------
    // no interface concept, so simply overwrite and leave them empty as for now
    clearFilter : function(){},
    isFiltered : function(){},
    collect : function(){},
    createFilterFn : function(){},
    sum : function(){},
    filter : function(){},
    filterBy : function(){},
    query : function(){},
    queryBy : function(){},
    find : function(){},
    findBy : function(){}

});/**
 * Ext.ux.grid.livegrid.Toolbar
 * Copyright (c) 2007-2008, http://www.siteartwork.de
 *
 * Ext.ux.grid.livegrid.Toolbar is licensed under the terms of the
 *                  GNU Open Source GPL 3.0
 * license.
 *
 * Commercial use is prohibited. Visit <http://www.siteartwork.de/livegrid>
 * if you need to obtain a commercial license.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 */

Ext.namespace('Ext.ux.grid.livegrid');

/**
 * toolbar that is bound to a {@link Ext.ux.grid.livegrid.GridView}
 * and provides information about the indexes of the requested data and the buffer
 * state.
 *
 * @class Ext.ux.grid.livegrid.Toolbar
 * @extends Ext.Toolbar
 * @constructor
 * @param {Object} config
 *
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 */
Ext.ux.grid.livegrid.Toolbar = Ext.extend(Ext.Toolbar, {

    /**
     * @cfg {Ext.grid.GridPanel} grid
     * The grid the toolbar is bound to. If ommited, use the cfg property "view"
     */

    /**
     * @cfg {Ext.grid.GridView} view The view the toolbar is bound to
     * The grid the toolbar is bound to. If ommited, use the cfg property "grid"
     */

    /**
     * @cfg {Boolean} displayInfo
     * True to display the displayMsg (defaults to false)
     */

    /**
     * @cfg {String} displayMsg
     * The paging status message to display (defaults to "Displaying {start} - {end} of {total}")
     */
    displayMsg : 'Displaying {0} - {1} of {2}',

    /**
     * @cfg {String} emptyMsg
     * The message to display when no records are found (defaults to "No data to display")
     */
    emptyMsg : 'No data to display',

    /**
     * Value to display as the tooltip text for the refresh button. Defaults to
     * "Refresh"
     * @param {String}
     */
    refreshText : "Refresh",

    initComponent : function()
    {
        Ext.ux.grid.livegrid.Toolbar.superclass.initComponent.call(this);

        if (this.grid) {
            this.view = this.grid.getView();
        }

        var me = this;
        this.view.init = this.view.init.createSequence(function(){
            me.bind(this);
        }, this.view);
    },

    // private
    updateInfo : function(rowIndex, visibleRows, totalCount)
    {
        if(this.displayEl){
            var msg = totalCount == 0 ?
                this.emptyMsg :
                String.format(this.displayMsg, rowIndex+1,
                              rowIndex+visibleRows, totalCount);
            this.displayEl.update(msg);
        }
    },

    /**
     * Unbinds the toolbar.
     *
     * @param {Ext.grid.GridView|Ext.gid.GridPanel} view Either The view to unbind
     * or the grid
     */
    unbind : function(view)
    {
        var st;
        var vw;

        if (view instanceof Ext.grid.GridView) {
            vw = view;
        } else {
            // assuming parameter is of type Ext.grid.GridPanel
            vw = view.getView();
        }

        st = view.ds;

        st.un('loadexception', this.enableLoading,  this);
        st.un('beforeload',    this.disableLoading, this);
        st.un('load',          this.enableLoading,  this);
        vw.un('rowremoved',    this.onRowRemoved,   this);
        vw.un('rowsinserted',  this.onRowsInserted, this);
        vw.un('beforebuffer',  this.beforeBuffer,   this);
        vw.un('cursormove',    this.onCursorMove,   this);
        vw.un('buffer',        this.onBuffer,       this);
        vw.un('bufferfailure', this.enableLoading,  this);

        this.view = undefined;
    },

    /**
     * Binds the toolbar to the specified {@link Ext.ux.grid.Livegrid}
     *
     * @param {Ext.grird.GridView} view The view to bind
     */
    bind : function(view)
    {
        this.view = view;
        var st = view.ds;

        st.on('loadexception',   this.enableLoading,  this);
        st.on('beforeload',      this.disableLoading, this);
        st.on('load',            this.enableLoading,  this);
        view.on('rowremoved',    this.onRowRemoved,   this);
        view.on('rowsinserted',  this.onRowsInserted, this);
        view.on('beforebuffer',  this.beforeBuffer,   this);
        view.on('cursormove',    this.onCursorMove,   this);
        view.on('buffer',        this.onBuffer,       this);
        view.on('bufferfailure', this.enableLoading,  this);
    },

// ----------------------------------- Listeners -------------------------------
    enableLoading : function()
    {
        this.loading.setDisabled(false);
    },

    disableLoading : function()
    {
        this.loading.setDisabled(true);
    },

    onCursorMove : function(view, rowIndex, visibleRows, totalCount)
    {
        this.updateInfo(rowIndex, visibleRows, totalCount);
    },

    // private
    onRowsInserted : function(view, start, end)
    {
        this.updateInfo(view.rowIndex, Math.min(view.ds.totalLength, view.visibleRows-view.rowClipped),
                        view.ds.totalLength);
    },

    // private
    onRowRemoved : function(view, index, record)
    {
        this.updateInfo(view.rowIndex, Math.min(view.ds.totalLength, view.visibleRows-view.rowClipped),
                        view.ds.totalLength);
    },

    // private
    beforeBuffer : function(view, store, rowIndex, visibleRows, totalCount, options)
    {
        this.loading.disable();
        this.updateInfo(rowIndex, visibleRows, totalCount);
    },

    // private
    onBuffer : function(view, store, rowIndex, visibleRows, totalCount)
    {
        this.loading.enable();
        this.updateInfo(rowIndex, visibleRows, totalCount);
    },

    // private
    onClick : function(type)
    {
        switch (type) {
            case 'refresh':
                if (this.view.reset(true)) {
                    this.loading.disable();
                } else {
                    this.loading.enable();
                }
            break;

        }
    },

    // private
    onRender : function(ct, position)
    {
        Ext.PagingToolbar.superclass.onRender.call(this, ct, position);

        this.loading = new Ext.Toolbar.Button({
            tooltip : this.refreshText,
            iconCls : "x-tbar-loading",
            handler : this.onClick.createDelegate(this, ["refresh"])
        });

        this.addButton(this.loading);

        this.addSeparator();

        if(this.displayInfo){
            this.displayEl = Ext.fly(this.el.dom).createChild({cls:'x-paging-info'});
        }
    }
});/**
 * Ext.ux.grid.livegrid.DragZone
 * Copyright (c) 2007-2008, http://www.siteartwork.de
 *
 * Ext.ux.grid.livegrid.DragZone is licensed under the terms of the
 *                  GNU Open Source GPL 3.0
 * license.
 *
 * Commercial use is prohibited. Visit <http://www.siteartwork.de/livegrid>
 * if you need to obtain a commercial license.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 */

Ext.namespace('Ext.ux.grid.livegrid');

/**
 * @class Ext.ux.grid.livegrid.DragZone
 * @extends Ext.dd.DragZone
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 */
Ext.ux.grid.livegrid.DragZone = function(grid, config){

    Ext.ux.grid.livegrid.DragZone.superclass.constructor.call(this, grid, config);

    this.view.ds.on('beforeselectionsload', this._onBeforeSelectionsLoad, this);
    this.view.ds.on('selectionsload',       this._onSelectionsLoad,       this);
};

Ext.extend(Ext.ux.grid.livegrid.DragZone, Ext.grid.GridDragZone, {

    /**
     * Tells whether a drop is valid. Used inetrnally to determine if pending
     * selections need to be loaded/ have been loaded.
     * @type {Boolean}
     */
    isDropValid : true,

    /**
     * Overriden for loading pending selections if needed.
     */
    onInitDrag : function(e)
    {
        this.view.ds.loadSelections(this.grid.selModel.getPendingSelections(true));

        Ext.ux.grid.livegrid.DragZone.superclass.onInitDrag.call(this, e);
    },

    /**
     * Gets called before pending selections are loaded. Any drop
     * operations are invalid/get paused if the component needs to
     * wait for selections to load from the server.
     *
     */
    _onBeforeSelectionsLoad : function()
    {
        this.isDropValid = false;
        Ext.fly(this.proxy.el.dom.firstChild).addClass('ext-ux-livegrid-drop-waiting');
    },

    /**
     * Gets called after pending selections have been loaded.
     * Any paused drop operation will be resumed.
     *
     */
    _onSelectionsLoad : function()
    {
        this.isDropValid = true;
        this.ddel.innerHTML = this.grid.getDragDropText();
        Ext.fly(this.proxy.el.dom.firstChild).removeClass('ext-ux-livegrid-drop-waiting');
    }
});/**
 * Ext.ux.grid.livegrid.EditorGridPanel
 * Copyright (c) 2007-2008, http://www.siteartwork.de
 *
 * Ext.ux.grid.livegrid.EditorGridPanel is licensed under the terms of the
 *                  GNU Open Source GPL 3.0
 * license.
 *
 * Commercial use is prohibited. Visit <http://www.siteartwork.de/livegrid>
 * if you need to obtain a commercial license.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
 *
 */

Ext.namespace('Ext.ux.grid.livegrid');

/**
 * @class Ext.ux.grid.livegrid.EditorGridPanel
 * @extends Ext.grid.EditorGridPanel
 * @constructor
 * @param {Object} config
 *
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 */
Ext.ux.grid.livegrid.EditorGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {

    /**
     * Overriden so the panel listens to the "cursormove" event for
     * cancelling any edit that is in progress.
     *
     * @private
     */
    initEvents : function()
    {
        Ext.ux.grid.livegrid.EditorGridPanel.superclass.initEvents.call(this);

        this.view.on("cursormove", this.stopEditing, this, [true]);
    },

    /**
     * Starts editing the specified for the specified row/column
     * Will be cancelled if the requested row index to edit is not
     * represented by data due to out of range regarding the view's
     * store buffer.
     *
     * @param {Number} rowIndex
     * @param {Number} colIndex
     */
    startEditing : function(row, col)
    {
        this.stopEditing();
        if(this.colModel.isCellEditable(col, row)){
            this.view.ensureVisible(row, col, true);
            if (!this.store.getAt(row)) {
                return;
            }
        }

        return Ext.ux.grid.livegrid.EditorGridPanel.superclass.startEditing.call(this, row, col);
    },

// Since we do not have multiple inheritance, we need to override the
// same methods in this class we have overriden for
// Ext.ux.grid.livegrid.GridPanel
    walkCells : function(row, col, step, fn, scope)
    {
        return Ext.ux.grid.livegrid.GridPanel.prototype.walkCells.call(this, row, col, step, fn, scope);
    },

    onRender : function(ct, position)
    {
        return Ext.ux.grid.livegrid.GridPanel.prototype.onRender.call(this, ct, position);
    },

    initComponent : function()
    {
        if (this.cls) {
            this.cls += ' ext-ux-livegrid';
        } else {
            this.cls = 'ext-ux-livegrid';
        }

        return Ext.ux.grid.livegrid.EditorGridPanel.superclass.initComponent.call(this);
    }

});// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
 * Search plugin for Ext.grid.GridPanel, Ext.grid.EditorGrid ver. 2.x or subclasses of them
 *
 * @author    Ing. Jozef Sakalos
 * @copyright (c) 2008, by Ing. Jozef Sakalos
 * @date      17. January 2008
 * @version   $Id: Ext.ux.grid.Search.js 220 2008-04-29 21:46:51Z jozo $
 *
 * @license Ext.ux.grid.Search is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * License details: http://www.gnu.org/licenses/lgpl.html
 */

/*global Ext */

Ext.ns('Ext.ux.grid');

/**
 * @class Ext.ux.grid.Search
 * @extends Ext.util.Observable
 * @param {Object} config configuration object
 * @constructor
 */
Ext.ux.grid.Search = function(config) {
	Ext.apply(this, config);
	Ext.ux.grid.Search.superclass.constructor.call(this);
}; // eo constructor

Ext.extend(Ext.ux.grid.Search, Ext.util.Observable, {
	/**
	 * cfg {Boolean} autoFocus true to try to focus the input field on each store load (defaults to undefined)
	 */

	/**
	 * @cfg {String} searchText Text to display on menu button
	 */
	 searchText:'Search'

	/**
	 * @cfg {String} searchTipText Text to display as input tooltip. Set to '' for no tooltip
	 */ 
	,searchTipText:'Type a text to search and press Enter'

	/**
	 * @cfg {String} selectAllText Text to display on menu item that selects all fields
	 */
	,selectAllText:'Select All'

	/**
	 * @cfg {String} position Where to display the search controls. Valid values are top and bottom (defaults to bottom)
	 * Corresponding toolbar has to exist at least with mimimum configuration tbar:[] for position:top or bbar:[]
	 * for position bottom. Plugin does NOT create any toolbar.
	 */
	,position:'bottom'

	/**
	 * @cfg {String} iconCls Icon class for menu button (defaults to icon-magnifier)
	 */
	,iconCls:'icon-magnifier'

	/**
	 * @cfg {String/Array} checkIndexes Which indexes to check by default. Can be either 'all' for all indexes
	 * or array of dataIndex names, e.g. ['persFirstName', 'persLastName']
	 */
	,checkIndexes:'all'

	/**
	 * @cfg {Array} disableIndexes Array of index names to disable (not show in the menu), e.g. ['persTitle', 'persTitle2']
	 */
	,disableIndexes:[]

	/**
	 * @cfg {String} dateFormat how to format date values. If undefined (the default) 
	 * date is formatted as configured in colummn model
	 */
	,dateFormat:undefined

	/**
	 * @cfg {Boolean} showSelectAll Select All item is shown in menu if true (defaults to true)
	 */
	,showSelectAll:true

	/**
	 * @cfg {String} menuStyle Valid values are 'checkbox' and 'radio'. If menuStyle is radio
	 * then only one field can be searched at a time and selectAll is automatically switched off.
	 */
	,menuStyle:'checkbox'

	/**
	 * @cfg {Number} minChars minimum characters to type before the request is made. If undefined (the default)
	 * the trigger field shows magnifier icon and you need to click it or press enter for search to start. If it
	 * is defined and greater than 0 then maginfier is not shown and search starts after minChars are typed.
	 */

	/**
	 * @cfg {String} minCharsTipText Tooltip to display if minChars is > 0
	 */
	,minCharsTipText:'Type at least {0} characters'

	/**
	 * @cfg {String} mode Use 'remote' for remote stores or 'local' for local stores. If mode is local
	 * no data requests are sent to server the grid's store is filtered instead (defaults to 'remote')
	 */
	,mode:'remote'

	/**
	 * @cfg {Array} readonlyIndexes Array of index names to disable (show in menu disabled), e.g. ['persTitle', 'persTitle2']
	 */

	/**
	 * @cfg {Number} width Width of input field in pixels (defaults to 100)
	 */
	,width:100

	/**
	 * @cfg {String} xtype xtype is usually not used to instantiate this plugin but you have a chance to identify it
	 */
	,xtype:'gridsearch'

	/**
	 * @cfg {Object} paramNames Params name map (defaults to {fields:'fields', query:'query'}
	 */
	,paramNames: {
		 fields:'fields'
		,query:'query'
	}

	/**
	 * @cfg {String} shortcutKey Key to fucus the input field (defaults to r = Sea_r_ch). Empty string disables shortcut
	 */
	,shortcutKey:'r'

	/**
	 * @cfg {String} shortcutModifier Modifier for shortcutKey. Valid values: alt, ctrl, shift (defaults to alt)
	 */
	,shortcutModifier:'alt'

	/**
	 * @cfg {String} align 'left' or 'right' (defaults to 'left')
	 */

	/**
	 * @cfg {Number} minLength force user to type this many character before he can make a search
	 */

	/**
	 * @cfg {Ext.Panel/String} toolbarContainer Panel (or id of the panel) which contains toolbar we want to render
	 * search controls to (defaults to this.grid, the grid this plugin is plugged-in into)
	 */
	
	// {{{
	/**
	 * private
	 * @param {Ext.grid.GridPanel/Ext.grid.EditorGrid} grid reference to grid this plugin is used for
	 */
	,init:function(grid) {
		this.grid = grid;

		// setup toolbar container if id was given
		if('string' === typeof this.toolbarContainer) {
			this.toolbarContainer = Ext.getCmp(this.toolbarContainer);
		}

		// do our processing after grid render and reconfigure
		grid.onRender = grid.onRender.createSequence(this.onRender, this);
		grid.reconfigure = grid.reconfigure.createSequence(this.reconfigure, this);
	} // eo function init
	// }}}
	// {{{
	/**
	 * private add plugin controls to <b>existing</b> toolbar and calls reconfigure
	 */
	,onRender:function() {
		var panel = this.toolbarContainer || this.grid;
		var tb = 'bottom' === this.position ? panel.bottomToolbar : panel.topToolbar;

		// add menu
		this.menu = new Ext.menu.Menu();

		// handle position
		if('right' === this.align) {
			tb.addFill();
		}
		else {
			if(0 < tb.items.getCount()) {
				tb.addSeparator();
			}
		}

		// add menu button
		tb.add({
			 text:this.searchText
			,menu:this.menu
			,iconCls:this.iconCls
		});

		// add input field (TwinTriggerField in fact)
		this.field = new Ext.form.TwinTriggerField({
			 width:this.width
			,selectOnFocus:undefined === this.selectOnFocus ? true : this.selectOnFocus
			,trigger1Class:'x-form-clear-trigger'
			,trigger2Class:this.minChars ? 'x-hidden' : 'x-form-search-trigger'
			,onTrigger1Click:this.minChars ? Ext.emptyFn : this.onTriggerClear.createDelegate(this)
			,onTrigger2Click:this.onTriggerSearch.createDelegate(this)
			,minLength:this.minLength
		});

		// install event handlers on input field
		this.field.on('render', function() {
			this.field.el.dom.qtip = this.minChars ? String.format(this.minCharsTipText, this.minChars) : this.searchTipText;

			if(this.minChars) {
				this.field.el.on({scope:this, buffer:300, keyup:this.onKeyUp});
			}

			// install key map
			var map = new Ext.KeyMap(this.field.el, [{
				 key:Ext.EventObject.ENTER
				,scope:this
				,fn:this.onTriggerSearch
			},{
				 key:Ext.EventObject.ESC
				,scope:this
				,fn:this.onTriggerClear
			}]);
			map.stopEvent = true;
		}, this, {single:true});

		tb.add(this.field);

		// reconfigure
		this.reconfigure();

		// keyMap
		if(this.shortcutKey && this.shortcutModifier) {
			var shortcutEl = this.grid.getEl();
			var shortcutCfg = [{
				 key:this.shortcutKey
				,scope:this
				,stopEvent:true
				,fn:function() {
					this.field.focus();
				}
			}];
			shortcutCfg[0][this.shortcutModifier] = true;
			this.keymap = new Ext.KeyMap(shortcutEl, shortcutCfg);
		}

		if(true === this.autoFocus) {
			this.grid.store.on({scope:this, load:function(){this.field.focus();}});
		}
	} // eo function onRender
	// }}}
	// {{{
	/**
	 * field el keypup event handler. Triggers the search
	 * @private
	 */
	,onKeyUp:function() {
		var length = this.field.getValue().toString().length;
		if(0 === length || this.minChars <= length) {
			this.onTriggerSearch();
		}
	} // eo function onKeyUp
	// }}}
	// {{{
	/**
	 * private Clear Trigger click handler
	 */
	,onTriggerClear:function() {
		if(this.field.getValue()) {
			this.field.setValue('');
			this.field.focus();
			this.onTriggerSearch();
		}
	} // eo function onTriggerClear
	// }}}
	// {{{
	/**
	 * private Search Trigger click handler (executes the search, local or remote)
	 */
	,onTriggerSearch:function() {
		if(!this.field.isValid()) {
			return;
		}
		var val = this.field.getValue();
		var store = this.grid.store;

		// grid's store filter
		if('local' === this.mode) {
			store.clearFilter();
			if(val) {
				store.filterBy(function(r) {
					var retval = false;
					this.menu.items.each(function(item) {
						if(!item.checked || retval) {
							return;
						}
						var rv = r.get(item.dataIndex);
						rv = rv instanceof Date ? rv.format(this.dateFormat || r.fields.get(item.dataIndex).dateFormat) : rv;
						var re = new RegExp(val, 'gi');
						retval = re.test(rv);
					}, this);
					if(retval) {
						return true;
					}
					return retval;
				}, this);
			}
			else {
			}
		}
		// ask server to filter records
		else {
			// clear start (necessary if we have paging)
			if(store.lastOptions && store.lastOptions.params) {
				store.lastOptions.params[store.paramNames.start] = 0;
			}

			// get fields to search array
			var fields = [];
			this.menu.items.each(function(item) {
				if(item.checked) {
					fields.push(item.dataIndex);
				}
			});

			// add fields and query to baseParams of store
			delete(store.baseParams[this.paramNames.fields]);
			delete(store.baseParams[this.paramNames.query]);
			if (store.lastOptions && store.lastOptions.params) {
				delete(store.lastOptions.params[this.paramNames.fields]);
				delete(store.lastOptions.params[this.paramNames.query]);
			}
			if(fields.length) {
				store.baseParams[this.paramNames.fields] = Ext.encode(fields);
				store.baseParams[this.paramNames.query] = val;
			}

			// reload store
			store.reload();
		}

	} // eo function onTriggerSearch
	// }}}
	// {{{
	/**
	 * @param {Boolean} true to disable search (TwinTriggerField), false to enable
	 */
	,setDisabled:function() {
		this.field.setDisabled.apply(this.field, arguments);
	} // eo function setDisabled
	// }}}
	// {{{
	/**
	 * Enable search (TwinTriggerField)
	 */
	,enable:function() {
		this.setDisabled(false);
	} // eo function enable
	// }}}
	// {{{
	/**
	 * Enable search (TwinTriggerField)
	 */
	,disable:function() {
		this.setDisabled(true);
	} // eo function disable
	// }}}
	// {{{
	/**
	 * private (re)configures the plugin, creates menu items from column model
	 */
	,reconfigure:function() {

		// {{{
		// remove old items
		var menu = this.menu;
		menu.removeAll();

		// add Select All item plus separator
		if(this.showSelectAll && 'radio' !== this.menuStyle) {
			menu.add(new Ext.menu.CheckItem({
				 text:this.selectAllText
				,checked:!(this.checkIndexes instanceof Array)
				,hideOnClick:false
				,handler:function(item) {
					var checked = ! item.checked;
					item.parentMenu.items.each(function(i) {
						if(item !== i && i.setChecked && !i.disabled) {
							i.setChecked(checked);
						}
					});
				}
			}),'-');
		}

		// }}}
		// {{{
		// add new items
		var cm = this.grid.colModel;
		var group = undefined;
		if('radio' === this.menuStyle) {
			group = 'g' + (new Date).getTime();	
		}
		Ext.each(cm.config, function(config) {
			var disable = false;
			if(config.header && config.dataIndex) {
				Ext.each(this.disableIndexes, function(item) {
					disable = disable ? disable : item === config.dataIndex;
				});
				if(!disable) {
					menu.add(new Ext.menu.CheckItem({
						 text:config.header
						,hideOnClick:false
						,group:group
						,checked:'all' === this.checkIndexes
						,dataIndex:config.dataIndex
					}));
				}
			}
		}, this);
		// }}}
		// {{{
		// check items
		if(this.checkIndexes instanceof Array) {
			Ext.each(this.checkIndexes, function(di) {
				var item = menu.items.find(function(itm) {
					return itm.dataIndex === di;
				});
				if(item) {
					item.setChecked(true, true);
				}
			}, this);
		}
		// }}}
		// {{{
		// disable items
		if(this.readonlyIndexes instanceof Array) {
			Ext.each(this.readonlyIndexes, function(di) {
				var item = menu.items.find(function(itm) {
					return itm.dataIndex === di;
				});
				if(item) {
					item.disable();
				}
			}, this);
		}
		// }}}

	} // eo function reconfigure
	// }}}

}); // eo extend

// eof
/*!
 * Ext JS Library 3.0.0
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ns('Ext.ux.grid');

/**
 * @class Ext.ux.grid.RowEditor
 * @extends Ext.Panel 
 * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
 * A validation mode may be enabled which uses AnchorTips to notify the user of all
 * validation errors at once.
 * 
 * @ptype roweditor
 */
Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
    floating: true,
    shadow: false,
    layout: 'hbox',
    cls: 'x-small-editor',
    buttonAlign: 'center',
    baseCls: 'x-row-editor',
    elements: 'header,footer,body',
    frameWidth: 5,
    buttonPad: 3,
    clicksToEdit: 'auto',
    monitorValid: true,
    focusDelay: 250,
    errorSummary: true,

    defaults: {
        normalWidth: true
    },

    initComponent: function(){
        Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
        this.addEvents(
            /**
             * @event beforeedit
             * Fired before the row editor is activated.
             * If the listener returns <tt>false</tt> the editor will not be activated.
             * @param {Ext.ux.grid.RowEditor} roweditor This object
             * @param {Number} rowIndex The rowIndex of the row just edited
             */
            'beforeedit',
            /**
             * @event validateedit
             * Fired after a row is edited and passes validation.
             * If the listener returns <tt>false</tt> changes to the record will not be set.
             * @param {Ext.ux.grid.RowEditor} roweditor This object
             * @param {Object} changes Object with changes made to the record.
             * @param {Ext.data.Record} r The Record that was edited.
             * @param {Number} rowIndex The rowIndex of the row just edited
             */
            'validateedit',
            /**
             * @event afteredit
             * Fired after a row is edited and passes validation.  This event is fired
             * after the store's update event is fired with this edit.
             * @param {Ext.ux.grid.RowEditor} roweditor This object
             * @param {Object} changes Object with changes made to the record.
             * @param {Ext.data.Record} r The Record that was edited.
             * @param {Number} rowIndex The rowIndex of the row just edited
             */
            'afteredit'
        );
    },

    init: function(grid){
        this.grid = grid;
        this.ownerCt = grid;
        if(this.clicksToEdit === 2){
            grid.on('rowdblclick', this.onRowDblClick, this);
        }else{
            grid.on('rowclick', this.onRowClick, this);
            if(Ext.isIE){
                grid.on('rowdblclick', this.onRowDblClick, this);
            }
        }

        // stopEditing without saving when a record is removed from Store.
        grid.getStore().on('remove', function() {
            this.stopEditing(false);
        },this);

        grid.on({
            scope: this,
            keydown: this.onGridKey,
            columnresize: this.verifyLayout,
            columnmove: this.refreshFields,
            reconfigure: this.refreshFields,
	    destroy : this.destroy,
            bodyscroll: {
                buffer: 250,
                fn: this.positionButtons
            }
        });
        grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
        grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
    },

    refreshFields: function(){
        this.initFields();
        this.verifyLayout();
    },

    isDirty: function(){
        var dirty;
        this.items.each(function(f){
            if(String(this.values[f.id]) !== String(f.getValue())){
                dirty = true;
                return false;
            }
        }, this);
        return dirty;
    },

    startEditing: function(rowIndex, doFocus){
        if(this.editing && this.isDirty()){
            this.showTooltip('You need to commit or cancel your changes');
            return;
        }
        this.editing = true;
        if(typeof rowIndex == 'object'){
            rowIndex = this.grid.getStore().indexOf(rowIndex);
        }
        if(this.fireEvent('beforeedit', this, rowIndex) !== false){
            var g = this.grid, view = g.getView();
            var row = view.getRow(rowIndex);
            var record = g.store.getAt(rowIndex);
            this.record = record;
            this.rowIndex = rowIndex;
            this.values = {};
            if(!this.rendered){
                this.render(view.getEditorParent());
            }
            var w = Ext.fly(row).getWidth();
            this.setSize(w);
            if(!this.initialized){
                this.initFields();
            }
            var cm = g.getColumnModel(), fields = this.items.items, f, val;
            for(var i = 0, len = cm.getColumnCount(); i < len; i++){
                val = this.preEditValue(record, cm.getDataIndex(i));
                f = fields[i];
                f.setValue(val);
                this.values[f.id] = val || '';
            }
            this.verifyLayout(true);
            if(!this.isVisible()){
                this.setPagePosition(Ext.fly(row).getXY());
            } else{
                this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
            }
            if(!this.isVisible()){
                this.show().doLayout();
            }
            if(doFocus !== false){
                this.doFocus.defer(this.focusDelay, this);
            }
        }
    },

    stopEditing : function(saveChanges){
        this.editing = false;
        if(!this.isVisible()){
            return;
        }
        if(saveChanges === false || !this.isValid()){
            this.hide();
            return;
        }
        var changes = {}, r = this.record, hasChange = false;
        var cm = this.grid.colModel, fields = this.items.items;
        for(var i = 0, len = cm.getColumnCount(); i < len; i++){
            if(!cm.isHidden(i)){
                var dindex = cm.getDataIndex(i);
                if(!Ext.isEmpty(dindex)){
                    var oldValue = r.data[dindex];
                    var value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
                    if(String(oldValue) !== String(value)){
                        changes[dindex] = value;
                        hasChange = true;
                    }
                }
            }
        }
        if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
            r.beginEdit();
            for(var k in changes){
                if(changes.hasOwnProperty(k)){
                    r.set(k, changes[k]);
                }
            }
            r.endEdit();
            this.fireEvent('afteredit', this, changes, r, this.rowIndex);
        }
        this.hide();
    },

    verifyLayout: function(force){
        if(this.el && (this.isVisible() || force === true)){
            var row = this.grid.getView().getRow(this.rowIndex);
            this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + (Ext.isBorderBox ? 9 : 0) : undefined);
            var cm = this.grid.colModel, fields = this.items.items;
            for(var i = 0, len = cm.getColumnCount(); i < len; i++){
                if(!cm.isHidden(i)){
                    var adjust = 0;
                    if(i === 0){
                        adjust += 0; // outer padding
                    }
                    if(i === (len - 1)){
                        adjust += 3; // outer padding
                    } else{
                        adjust += 1;
                    }
                    fields[i].show();
                    fields[i].setWidth(cm.getColumnWidth(i) - adjust);
                } else{
                    fields[i].hide();
                }
            }
            this.doLayout();
            this.positionButtons();
        }
    },

    slideHide : function(){
        this.hide();
    },

    initFields: function(){
        var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
        this.removeAll(false);
        for(var i = 0, len = cm.getColumnCount(); i < len; i++){
            var c = cm.getColumnAt(i);
            var ed = c.getEditor();
            if(!ed){
                ed = c.displayEditor || new Ext.form.DisplayField();
            }
            if(i == 0){
                ed.margins = pm('0 1 2 1');
            } else if(i == len - 1){
                ed.margins = pm('0 0 2 1');
            } else{
                ed.margins = pm('0 1 2');
            }
            ed.setWidth(cm.getColumnWidth(i));
            ed.column = c;
            if(ed.ownerCt !== this){
                ed.on('focus', this.ensureVisible, this);
                ed.on('specialkey', this.onKey, this);
            }
            this.insert(i, ed);
        }
        this.initialized = true;
    },

    onKey: function(f, e){
        if(e.getKey() === e.ENTER){
            this.stopEditing(true);
            e.stopPropagation();
        }
    },

    onGridKey: function(e){
        if(e.getKey() === e.ENTER && !this.isVisible()){
            var r = this.grid.getSelectionModel().getSelected();
            if(r){
                var index = this.grid.store.indexOf(r);
                this.startEditing(index);
                e.stopPropagation();
            }
        }
    },

    ensureVisible: function(editor){
        if(this.isVisible()){
             this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
        }
    },

    onRowClick: function(g, rowIndex, e){
        if(this.clicksToEdit == 'auto'){
            var li = this.lastClickIndex;
            this.lastClickIndex = rowIndex;
            if(li != rowIndex && !this.isVisible()){
                return;
            }
        }
        this.startEditing(rowIndex, false);
        this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
    },

    onRowDblClick: function(g, rowIndex, e){
        this.startEditing(rowIndex, false);
        this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
    },

    onRender: function(){
        Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
        this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
        this.btns = new Ext.Panel({
            baseCls: 'x-plain',
            cls: 'x-btns',
            elements:'body',
            layout: 'table',
            width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
            items: [{
                ref: 'saveBtn',
                itemId: 'saveBtn',
                xtype: 'button',
                text: this.saveText || 'Save',
                width: this.minButtonWidth,
                handler: this.stopEditing.createDelegate(this, [true])
            }, {
                xtype: 'button',
                text: this.cancelText || 'Cancel',
                width: this.minButtonWidth,
                handler: this.stopEditing.createDelegate(this, [false])
            }]
        });
        this.btns.render(this.bwrap);
    },

    afterRender: function(){
        Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
        this.positionButtons();
        if(this.monitorValid){
            this.startMonitoring();
        }
    },

    onShow: function(){
        if(this.monitorValid){
            this.startMonitoring();
        }
        Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
    },

    onHide: function(){
        Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
        this.stopMonitoring();
        this.grid.getView().focusRow(this.rowIndex);
    },

    positionButtons: function(){
        if(this.btns){
            var h = this.el.dom.clientHeight;
            var view = this.grid.getView();
            var scroll = view.scroller.dom.scrollLeft;
            var width =  view.mainBody.getWidth();
            var bw = this.btns.getWidth();
            this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
        }
    },

    // private
    preEditValue : function(r, field){
        var value = r.data[field];
        return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
    },

    // private
    postEditValue : function(value, originalValue, r, field){
        return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
    },

    doFocus: function(pt){
        if(this.isVisible()){
            var index = 0;
            if(pt){
                index = this.getTargetColumnIndex(pt);
            }
            var cm = this.grid.getColumnModel();
            for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
                var c = cm.getColumnAt(i);
                if(!c.hidden && c.getEditor()){
                    c.getEditor().focus();
                    break;
                }
            }
        }
    },

    getTargetColumnIndex: function(pt){
        var grid = this.grid, v = grid.view;
        var x = pt.left;
        var cms = grid.colModel.config;
        var i = 0, match = false;
        for(var len = cms.length, c; c = cms[i]; i++){
            if(!c.hidden){
                if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
                    match = i;
                    break;
                }
            }
        }
        return match;
    },

    startMonitoring : function(){
        if(!this.bound && this.monitorValid){
            this.bound = true;
            Ext.TaskMgr.start({
                run : this.bindHandler,
                interval : this.monitorPoll || 200,
                scope: this
            });
        }
    },

    stopMonitoring : function(){
        this.bound = false;
        if(this.tooltip){
            this.tooltip.hide();
        }
    },

    isValid: function(){
        var valid = true;
        this.items.each(function(f){
            if(!f.isValid(true)){
                valid = false;
                return false;
            }
        });
        return valid;
    },

    // private
    bindHandler : function(){
        if(!this.bound){
            return false; // stops binding
        }
        var valid = this.isValid();
        if(!valid && this.errorSummary){
            this.showTooltip(this.getErrorText().join(''));
        }
        this.btns.saveBtn.setDisabled(!valid);
        this.fireEvent('validation', this, valid);
    },

    showTooltip: function(msg){
        var t = this.tooltip;
        if(!t){
            t = this.tooltip = new Ext.ToolTip({
                maxWidth: 600,
                cls: 'errorTip',
                width: 300,
                title: 'Errors',
                autoHide: false,
                anchor: 'left',
                anchorToTarget: true,
                mouseOffset: [40,0]
            });
        }
        t.initTarget(this.items.last().getEl());
        if(!t.rendered){
            t.show();
            t.hide();
        }
        t.body.update(msg);
        t.doAutoWidth();
        t.show();
    },

    getErrorText: function(){
        var data = ['<ul>'];
        this.items.each(function(f){
            if(!f.isValid(true)){
                data.push('<li>', f.activeError, '</li>');
            }
        });
        data.push('</ul>');
        return data;
    }
});
Ext.preg('roweditor', Ext.ux.grid.RowEditor);

Ext.override(Ext.form.Field, {
    markInvalid : function(msg){
        if(!this.rendered || this.preventMark){ // not rendered
            return;
        }
        msg = msg || this.invalidText;

        var mt = this.getMessageHandler();
        if(mt){
            mt.mark(this, msg);
        }else if(this.msgTarget){
            this.el.addClass(this.invalidClass);
            var t = Ext.getDom(this.msgTarget);
            if(t){
                t.innerHTML = msg;
                t.style.display = this.msgDisplay;
            }
        }
        this.activeError = msg;
        this.fireEvent('invalid', this, msg);
    }
});

Ext.override(Ext.ToolTip, {
    doAutoWidth : function(){
        var bw = this.body.getTextWidth();
        if(this.title){
            bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
        }
        bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr") + 20;
        this.setWidth(bw.constrain(this.minWidth, this.maxWidth));

        // IE7 repaint bug on initial show
        if(Ext.isIE7 && !this.repainted){
            this.el.repaint();
            this.repainted = true;
        }
    }
});
/*!
 * Ext JS Library 3.0+
 * Copyright(c) 2006-2009 Ext JS, LLC
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ns('Ext.ux.form');

/**
 * @class Ext.ux.form.FileUploadField
 * @extends Ext.form.TextField
 * Creates a file upload field.
 * @xtype fileuploadfield
 */
Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField,  {
    /**
     * @cfg {String} buttonText The button text to display on the upload button (defaults to
     * 'Browse...').  Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
     * value will be used instead if available.
     */
    buttonText: 'Browse...',
    /**
     * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
     * text field (defaults to false).  If true, all inherited TextField members will still be available.
     */
    buttonOnly: false,
    /**
     * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
     * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
     */
    buttonOffset: 3,
    /**
     * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
     */

    // private
    readOnly: true,

    /**
     * @hide
     * @method autoSize
     */
    autoSize: Ext.emptyFn,

    // private
    initComponent: function(){
        Ext.ux.form.FileUploadField.superclass.initComponent.call(this);

        this.addEvents(
            /**
             * @event fileselected
             * Fires when the underlying file input field's value has changed from the user
             * selecting a new file from the system file selection dialog.
             * @param {Ext.ux.form.FileUploadField} this
             * @param {String} value The file value returned by the underlying file input field
             */
            'fileselected'
        );
    },

    // private
    onRender : function(ct, position){
        Ext.ux.form.FileUploadField.superclass.onRender.call(this, ct, position);

        this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'});
        this.el.addClass('x-form-file-text');
        this.el.dom.removeAttribute('name');

        this.fileInput = this.wrap.createChild({
            id: this.getFileInputId(),
            name: this.name||this.getId(),
            cls: 'x-form-file',
            tag: 'input',
            type: 'file',
            size: 1
        });

        var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
            text: this.buttonText
        });
        this.button = new Ext.Button(Ext.apply(btnCfg, {
            renderTo: this.wrap,
            cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '')
        }));

        if(this.buttonOnly){
            this.el.hide();
            this.wrap.setWidth(this.button.getEl().getWidth());
        }

        this.fileInput.on('change', function(){
            var v = this.fileInput.dom.value;
            this.setValue(v);
            this.fireEvent('fileselected', this, v);
        }, this);
    },

    // private
    getFileInputId: function(){
        return this.id + '-file';
    },

    // private
    onResize : function(w, h){
        Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h);

        this.wrap.setWidth(w);

        if(!this.buttonOnly){
            var w = this.wrap.getWidth() - this.button.getEl().getWidth() - this.buttonOffset;
            this.el.setWidth(w);
        }
    },

    // private
    onDestroy: function(){
        Ext.ux.form.FileUploadField.superclass.onDestroy.call(this);
        Ext.destroy(this.fileInput, this.button, this.wrap);
    },


    // private
    preFocus : Ext.emptyFn,

    // private
    getResizeEl : function(){
        return this.wrap;
    },

    // private
    getPositionEl : function(){
        return this.wrap;
    },

    // private
    alignErrorIcon : function(){
        this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
    }

});

Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);

// backwards compat
Ext.form.FileUploadField = Ext.ux.form.FileUploadField;
Ext.onReady(function(){	
	Ext.form.DateField.prototype.format = 'Y-m-d';
	Ext.Panel.prototype.animCollapse = false;
	Ext.QuickTips.init();
	
	Ext.namespace('Monoql');

Monoql.dataconnections = new Ext.util.MixedCollection();

Monoql.showAboutWindow = function(){
	Ext.Msg.show({
		icon:Ext.Msg.INFO,
		title:'About MonoQL',
		minWidth:400,
		msg:'MonoQL 0.1a (Community Version: GPLv3)' + '<br /><br />' +
		'MonoQL is a unified web front-end to manage a variety of data sources including MySQL, MSSQL, Oracle, and others.' + '<br /><br />' +
		'Built on the <a href="http://sourceforge.net/projects/helix" target="_blank">Helix PHP Framework</a> and <a href="http://www.extjs.com" target="_blank">ExtJS Javascript Framework</a>.' + '<br /><br />' +
		'Visit <a href="http://www.monoql.com" target="_blank">www.MonoQL.com</a> for the latest news, information and releases.' + '<br /><br />' +
		'Copyright 2009 - <a href="http://www.jdcommerce.com" target="_blank">JDCommerce Inc</a>'
	});
};

Ext.namespace('Monoql.Bar');
Monoql.Bar.MenuBar = new Ext.Panel({
	id:'Monoql-MenuBar',
	layout:'fit',
	border:false,
	tbar:[{
			text:'File',
			cls:'monoql-menu-item',
			menu:[{
				text:'New Query',
				icon:'/helix/images/silk/gifs/tab_add.gif',
				handler:function(item,e){
					Ext.getCmp('main-tabpanel').showQueryConnectionWindow();
				}
			},{
				text:'New Connection',
				icon:'/helix/images/silk/gifs/server_add.gif',
				handler:function(item,e){
					Ext.getCmp('main-treepanel').showConnectionWindow();
				}
			}]
		},{
			text:'Edit',
			cls:'monoql-menu-item',
			menu:[{
				text:'Find',
				cls:'monoql-menu-item',
				icon:'/helix/images/silk/gifs/find.gif',
				disabled:true
			}]
		},{
			text:'Tools',
			cls:'monoql-menu-item',
			menu:[{
				text:'Import',
				cls:'monoql-menu-item',
				icon:'/helix/images/silk/gifs/database_go.gif',
				disabled:true
			}]
		},{
			text:'Data',
			cls:'monoql-menu-item',
			menu:[{
				text:'Filter',
				cls:'monoql-menu-item',
				icon:'/helix/images/silk/gifs/page_white_text.gif',
				disabled:true
			}]
		},{
			text:'Help',
			cls:'monoql-menu-item',
			menu:[{
				text:'About MonoQL',
				cls:'monoql-menu-item',
				icon:'/helix/images/silk/gifs/information.gif',
				handler:function(item,e){
					Monoql.showAboutWindow();
				}
			}]
		},'-',{
			id:'Monoql-MenuBar-OpenButton',
			text:'',
			icon:'/helix/images/silk/gifs/folder.gif',
			tooltip:'Open a file into a new query tab (Ctrl+O)',
			cls:'monoql-toolbar-item',
			handler:function(){
				Ext.getCmp('main-tabpanel').showOpenQueryWindow();
			}
		},{
			id:'Monoql-MenuBar-SaveButton',
			text:'',
			icon:'/helix/images/silk/gifs/disk.gif',
			tooltip:'Save this query to a file (Ctrl+S)',
			cls:'monoql-toolbar-item',
			handler:function(){
				Ext.getCmp('main-tabpanel').saveActiveQuery();
			}
		},'-',{
			id:'Monoql-MenuBar-ExecuteQueryButton',
			text:'',
			icon:'/helix/images/silk/gifs/exclamation.gif',
			tooltip:'Execute this query (F9)',
			cls:'monoql-toolbar-item',
			handler:function(){
				Ext.getCmp('main-tabpanel').executeActiveQuery();
			}
		},{
			id:'Monoql-MenuBar-CancelQueryButton',
			text:'',
			icon:'/helix/images/silk/gifs/cross.gif',
			tooltip:'Cancel the current query',
			cls:'monoql-toolbar-item',
			disabled:true,
			handler:function(){
				Ext.getCmp('main-tabpanel').cancelActiveQuery();
			}
		},{
			text:'',
			icon:'/helix/images/silk/gifs/tab_add.gif',
			tooltip:'Open a new query tab (Ctrl+T)',
			cls:'monoql-toolbar-item',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').showQueryConnectionWindow();
			}
		},'-',{
			text:'',
			icon:'/helix/images/silk/gifs/table.gif',
			tooltip:'Output query results to grid',
			cls:'monoql-toolbar-item',
			toggleGroup:'result-display-type',
			pressed:true,
			handler:function(){
			}
		},{
			text:'',
			icon:'/helix/images/silk/gifs/text_columns.gif',
			tooltip:'Output query results to text',
			cls:'monoql-toolbar-item',
			toggleGroup:'result-display-type',
			handler:function(){
			}
		},{
			text:'',
			icon:'/helix/images/silk/gifs/page_white_put.gif',
			tooltip:'Save query results as a file',
			cls:'monoql-toolbar-item',
			toggleGroup:'result-display-type',
			handler:function(){
			}
		},'-',{
			xtype:'combo',
			hiddenName:'main-menubar-connection',
			id:'main-menubar-connection-id',
			emptyText:'Set Active Connection...',
			store:new Ext.data.JsonStore({
		        idPropery:'id',
		        fields:['id','name'],
		        root:'data',
		        data:{data:[]}
		    }),
			forceSelection:true,
			mode:'local',
			valueField:'id',
			displayField:'name',
			triggerAction:'all',
			editable:false,
			listeners:{
				'select':function(combo,record,index){
					if (Ext.getCmp('main-tabpanel').queryTabActive()){
						var activeTab = Ext.getCmp('main-tabpanel').getActiveTab();
						activeTab.dataconnection = Monoql.dataconnections.get(combo.getValue());
						activeTab.queryField.database = null;
						activeTab.queryField.table = null;
						activeTab.resultTabSet.baseParams = Ext.apply({},activeTab.dataconnection);
						activeTab.resultTabSet.getResultTab().baseParams = Ext.apply({},activeTab.dataconnection);
						activeTab.setTitle(activeTab.dataconnection.name + '-' + String.leftPad(activeTab.num,2,'0'));
						Ext.getCmp('main-tabpanel').updateDatabaseStore();
						Ext.getCmp('main-tabpanel').updateActiveDatabase();
						Ext.getCmp('main-menubar-database-id').clearValue();
						Ext.getCmp('main-menubar-database-id').focus();
					}
				}
			}
		},'-',{
			xtype:'combo',
			hiddenName:'main-menubar-database',
			id:'main-menubar-database-id',
			emptyText:'Set Active Database...',
			store:new Ext.data.JsonStore({
		        idPropery:'id',
		        fields:['name'],
		        root:'data',
		        data:{data:[]}
		    }),
			forceSelection:true,
			mode:'local',
			valueField:'name',
			displayField:'name',
			triggerAction:'all',
			editable:false,
			listeners:{
				'select':function(combo,record,index){
					if (Ext.getCmp('main-tabpanel').queryTabActive()){
						var activeTab = Ext.getCmp('main-tabpanel').getActiveTab();
						var database = combo.getValue();
						activeTab.queryField.database = database;
						activeTab.queryField.table = null;
						activeTab.resultTabSet.baseParams.database = database;
						activeTab.resultTabSet.getResultTab().baseParams.database = database;
					}
				}
			}
		},'->',{
			xtype:'tbtext',
			text:' [ <a href="/index?f=logout">Logout</a> ]'
		}
	]
});Ext.namespace('Monoql.Grid');

Monoql.Grid = Ext.extend(Ext.ux.grid.livegrid.EditorGridPanel,{
	loadMask:true,
	border:false,
	stripeRows:true,
	scrollDelay:200000,
	
	initComponent:function(){
		Monoql.Grid.superclass.initComponent.call(this);
	},
	
	showExportToCsvWindow:function(){
		var window = new Monoql.Window.ExportToCsvWindow({
			grid:this
		});
		window.show();
	},
	
	showExportToXmlWindow:function(){
		var window = new Monoql.Window.ExportToXmlWindow({
			grid:this
		});
		window.show();
	},
	
	showCreateInsertsWindow:function(){
		var window = new Monoql.Window.CreateInsertsWindow({
			grid:this
		});
		window.show();
	}
});

Ext.reg('monoql-grid',Monoql.Grid);

Ext.namespace('Monoql.Grid.DataGrid');
Monoql.Grid.DataGrid = Ext.extend(Monoql.Grid,{
	loadMask:true,
	border:false,
	stripeRows:true,
	scrollDelay:200000,
	
	initComponent:function(){
		var search = new Ext.ux.grid.Search({
			iconCls:'monoql-grid-search',
			readonlyIndexes:[],
			disableIndexes:[],
			minChars:2,
			autoFocus:true,
			menuStyle:'checkbox',
			paramNames:{
				fields:'search-fields',
				query:'search-keyword'
			}
		});
		var config = {
		};
		Ext.apply(this,config);
		Ext.apply(this.initialConfig,config);
		Monoql.Grid.DataGrid.superclass.initComponent.call(this);
		this.addListener('cellcontextmenu',this.onCellContextMenu,this);
	},
	
	onCellContextMenu:function(grid,rowIndex,cellIndex,e){
		var sm = grid.getSelectionModel(); 
		if (sm.getCount()<=1){
			sm.selectRow(rowIndex,false);
		}
		var menu = new Monoql.Menu.DataGridMenu({
			grid:this
		});
		menu.show(grid.getView().getRow(rowIndex));
		menu.getEl().moveTo(e.getPageX()-30,e.getPageY());
		e.stopEvent();
	},
	
	deleteSelectedRows:function(){
		var store = this.getStore();
		var sm = this.getSelectionModel();
		var selections = sm.getSelections();
		Ext.each(selections,function(item,index,allItems){
			store.remove(item);
		});
	}
});

Ext.reg('monoql-datagrid',Monoql.Grid.DataGrid);Ext.namespace('Monoql.Grid.ResultGrid');
Monoql.Grid.ResultGrid = Ext.extend(Monoql.Grid,{
	loadMask:true,
	border:false,
	stripeRows:true,
	scrollDelay:200000,
	
	initComponent:function(){
		Monoql.Grid.ResultGrid.superclass.initComponent.call(this);
		this.addListener('cellcontextmenu',this.onCellContextMenu,this);
	},
	
	onCellContextMenu:function(grid,rowIndex,cellIndex,e){
		var sm = grid.getSelectionModel(); 
		if (sm.getCount()<=1){
			sm.selectRow(rowIndex,false);
		}
		var menu = new Monoql.Menu.ResultGridMenu({
			grid:this
		});
		menu.show(grid.getView().getRow(rowIndex));
		menu.getEl().moveTo(e.getPageX()-30,e.getPageY());
		e.stopEvent();
	}
});

Ext.reg('monoql-resultgrid',Monoql.Grid.ResultGrid);Ext.namespace('Monoql.Tab');

Monoql.Tab = Ext.extend(Ext.Panel,{
	initComponent:function(){
		Monoql.Tab.superclass.initComponent.call(this);
	}
});

Ext.namespace('Monoql.Tab.DataTab');

Monoql.Tab.DataTab = Ext.extend(Monoql.Tab,{
	border:false,
	bodyStyle:'border-left-width:1px;border-bottom-width:1px;',
	
	initComponent: function(){
		Monoql.Tab.DataTab.superclass.initComponent.call(this);
		this.addEvents(
			'beforequery',
			'afterquery',
			'queryresponse'
		);
	},
	
	onActivate:function(panel){
		this.addListener('beforequery',this.prepareGrid,this);
		this.addListener('queryresponse',this.renderGrid,this);
		this.addListener('afterquery',this.finalizeGrid,this);
		this.executeQuery();
	},
	
	executeQuery:function(){
		this.onBeforeQuery();
		Ext.Ajax.request({
			url:'/index?ajax&f=get_grid_data',
			params:Ext.apply({
				gridType:'DataGrid'
			},this.dataconnection),
			scope:this,
			callback:function(options,success,response){
				var responseObject = Ext.decode(response.responseText);
				var query = responseObject.query;
				this.onQueryResponse(query,responseObject);
			}
		});
	},
	
	onBeforeQuery:function(){
		this.getEl().mask('Loading Data...');
		this.fireEvent('beforequery',this);
	},
	
	onAfterQuery:function(){
		this.fireEvent('afterquery',this);
	},
	
	onQueryResponse:function(query,response){
		this.fireEvent('queryresponse',query,response);
		this.onAfterQuery();
	},
	
	prepareGrid:function(queryfield){
	},
	
	finalizeGrid:function(queryfield){
	},

	renderGrid:function(query,response){
		this.baseParams.query = query;
		this.baseParams.gridType = 'DataGrid';
		var record = Ext.data.Record.create(response.fields);
		var view = new Ext.ux.grid.livegrid.GridView({
			nearLimit:20,
			loadMask:{
				msg:'Buffering.  Please wait...'
			}
		});
		var store = new Ext.ux.grid.livegrid.Store({
			autoLoad:true,
			autoDestroy:true,
			baseParams:this.baseParams,
			bufferSize:100,
			remortSort:true,
			proxy:new Ext.data.HttpProxy({
				api:{
					create:'/index?ajax&f=insert_grid_data',
					read:'/index?ajax&f=get_grid_data',
					update:'/index?ajax&f=update_grid_data',
					destroy:'/index?ajax&f=delete_grid_data'
				}
			}),
			reader:new Ext.ux.grid.livegrid.JsonReader({
				root:'data',
				versionProperty:'version',
				totalProperty:'totalCount',
				id:'id'
			},record),
			writer:new Ext.data.JsonWriter({
				writeAllFields:true
			})
		});
		store.addListener('load',function(store,records,options){
			this.getEl().unmask();
		},this);
		var dataGrid = new Monoql.Grid.DataGrid({
			id:'DataGrid-'+this.id,
			enableDragDrop:false,
			cm:new Ext.grid.ColumnModel(response.columns),
			store:store,
			sm:new Ext.ux.grid.livegrid.RowSelectionModel(),
			view:view,
			bbar:new Ext.ux.grid.livegrid.Toolbar({
				view:view,
				displayInfo:true
			})
		});
//		dataGrid.addListener('render',function(){
//			dataGrid.getBottomToolbar().add({
//				text:'Insert'
//			});
//		},dataGrid,{single:true});
		this.dataGridContainer.add(dataGrid);
		this.dataGridContainer.doLayout();
		
		var newData = {};
		Ext.each(response.columns,function(item,index,allItems){
			newData[item.dataIndex] = null;
		});
		var insertStore = new Ext.data.JsonStore({
			autoLoad:false,
			autoDestroy:true,
			baseParams:{},
			data:[newData],
			proxy:new Ext.data.HttpProxy({
				api:{
					create:'/index?ajax&f=insert_grid_data',
					read:'/index?ajax&f=get_grid_data',
					update:'/index?ajax&f=update_grid_data',
					destroy:'/index?ajax&f=delete_grid_data'
				}
			}),
			fields:response.fields,
			reader:new Ext.data.JsonReader({
				root:'data',
				versionProperty:'version',
				totalProperty:'totalCount',
				id:'id'
			}),
			writer:new Ext.data.JsonWriter({
				writeAllFields:true
			})
		});
		var insertGrid = new Monoql.Grid.DataGrid({
			id:'InsertGrid-'+this.id,
			cm:new Ext.grid.ColumnModel(response.columns),
			store:insertStore
		});
		this.insertGridContainer.add(insertGrid);
		this.insertGridContainer.doLayout();
	}
});

Ext.reg('monoql-datatab',Monoql.Tab.DataTab);Ext.namespace('Monoql.Tab.MessageTab');

Monoql.Tab.MessageTab = Ext.extend(Monoql.Tab,{
	title:'Messages',
	border:false,
	bodyStyle:'border-left-width:1px;border-bottom-width:1px;',
	
	initComponent: function(){
		Monoql.Tab.MessageTab.superclass.initComponent.call(this);
	},
	
	clearMessages:function(queryfield){
		this.removeAll(true);
	},

	displayMessages:function(queryfield,query,response){
		if (queryfield.cancelled){
			this.removeAll(true);
			return;
		}
		var messages = response.messages.error ? 'Error Message : ' + response.messages.error + '<br /><br />' : '';
		messages = messages + (Ext.isNumber(response.messages.execution_time) ? 'Execution Time: ' + response.messages.execution_time + ' ms<br /><br />' : '');
		messages = messages + (Ext.isNumber(response.messages.affected_rows) ? 'Rows Affected : ' + response.messages.affected_rows + '<br /><br />' : '');
		messages = messages + (Ext.isNumber(response.messages.found_rows) ? 'Rows Returned : ' + response.messages.found_rows + '<br /><br />' : '');
		this.removeAll(true);
		this.add(new Ext.Panel({
			border:false,
			cls:'monoql-message-tab',
			html:messages
		}));
		if (response.messages.error){
			this.ownerCt.activate(1);
			this.doLayout();
		}
		else{
			this.ownerCt.activate(0);
		}
	}
});

Ext.reg('monoql-messagetab',Monoql.Tab.MessageTab);Ext.namespace('Monoql.Tab.QueryTab');

Monoql.Tab.QueryTab = Ext.extend(Monoql.Tab,{
	initComponent:function(){
		Monoql.Tab.QueryTab.superclass.initComponent.call(this);
		this.queryField = this.getQueryField();
	},
	
	getQueryField:function(){
		return this.getComponent(0).getComponent(0);
	}
});

Ext.reg('monoql-querytab',Monoql.Tab.QueryTab);Ext.namespace('Monoql.Tab.ResultTab');

Monoql.Tab.ResultTab = Ext.extend(Monoql.Tab,{
	title:'Results',
	border:false,
	layout:'fit',
	bodyStyle:'border-left-width:1px;border-bottom-width:1px;',
	
	initComponent:function(){
		Monoql.Tab.ResultTab.superclass.initComponent.call(this);
	},
	
	prepareGrid:function(queryfield){
		this.removeAll(true);
	},
	
	finalizeGrid:function(queryfield){
		this.getEl().unmask();
	},

	renderGrid:function(queryfield,query,response){
		if (queryfield.cancelled){
			this.removeAll(true);
			return;
		}
		this.getEl().mask('Loading Data...');
		this.baseParams.query = query;
		this.baseParams.gridType = 'ResultGrid';
		var record = Ext.data.Record.create(response.fields);
		var view = new Ext.ux.grid.livegrid.GridView({
			nearLimit:10,
			loadMask:{
				msg:'Buffering.  Please wait...'
			}
		});
		var store = new Ext.ux.grid.livegrid.Store({
			autoLoad:true,
			autoDestroy:true,
			url:'/index?ajax&f=get_grid_data',
			baseParams:this.baseParams,
			bufferSize:60,
			remortSort:true,
			reader:new Ext.ux.grid.livegrid.JsonReader({
				root:'data',
				versionProperty:'version',
				totalProperty:'totalCount',
				id:'id'
			},record)
		});
		this.add({
			xtype:'monoql-resultgrid',
			id:'ResultGrid-'+this.id,
			enableDragDrop:false,
			loadMask:{
				msg:'Loading...'
			},
			cm:new Ext.grid.ColumnModel(response.columns),
			store:store,
			sm:new Ext.ux.grid.livegrid.RowSelectionModel(),
			view:view,
			bbar:new Ext.ux.grid.livegrid.Toolbar({
				view:view,
				displayInfo:true
			})
		});
		this.doLayout();
	}
});

Ext.reg('monoql-resulttab',Monoql.Tab.ResultTab);Ext.namespace('Monoql.Tab.TableDesignTab');

Monoql.Tab.TableDesignTab = Ext.extend(Monoql.Tab,{
	border:false,
	layout:'fit',
	bodyStyle:'border-left-width:1px;border-bottom-width:1px;',
	
	initComponent: function(config){
		Monoql.Tab.TableDesignTab.superclass.initComponent.call(this);
	}
});

Ext.reg('monoql-tabledesigntab',Monoql.Tab.TableDesignTab);Ext.namespace('Monoql.TabSet');

Monoql.TabSet = Ext.extend(Ext.TabPanel,{
	initComponent:function(){
		Monoql.TabSet.superclass.initComponent.call(this);
	}
});

Ext.reg('monoql-tabset',Monoql.TabSet);

Ext.namespace('Monoql.TabSet.ResultTabSet');

Monoql.TabSet.ResultTabSet = Ext.extend(Monoql.TabSet,{
	border:false,
	
	initComponent:function(){
		Monoql.TabSet.ResultTabSet.superclass.initComponent.call(this);
		this.add(
			new Monoql.Tab.ResultTab({
				id:'ResultTab-'+this.id,
				baseParams:this.baseParams
			}),
			new Monoql.Tab.MessageTab({
				id:'MessageTab-'+this.id
			})
		);
		this.activate(0);
	},
	
	getResultTab:function(){
		return this.getItem('ResultTab-'+this.id);
	},
	
	getMessageTab:function(){
		return this.getItem('MessageTab-'+this.id);
	}
});

Ext.reg('monoql-resulttabset',Monoql.TabSet.ResultTabSet);Ext.namespace('Monoql.Menu');

Monoql.Menu = Ext.extend(Ext.menu.Menu,{
	initComponent:function(){
		Monoql.Menu.superclass.initComponent.call(this);
	}
});

Ext.namespace('Monoql.Menu.GroupNodeMenu');

Monoql.Menu.GroupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.GroupNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.addItem(new Ext.menu.Item({
			text:'New Connection',
			icon:'/helix/images/silk/gifs/server_add.gif',
			handler:function(item,e){
				var window = new Monoql.Window.ConnectionWindow({
					node:node
				});
				window.show();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Remove',
			icon:'/helix/images/silk/gifs/cross.gif'
		}));
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.ConnectionNodeMenu');

Monoql.Menu.ConnectionNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.ConnectionNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.dataconnection = this.node.dataconnection;
		var dataconnection = this.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'New Query',
			icon:'/helix/images/silk/gifs/tab_add.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').addQueryTab(dataconnection);
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Run SQL File...',
			icon:'/helix/images/silk/gifs/database_go.gif',
			handler:function(item,e){
				this.ownerCt.node.showRunSqlFileWindow();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Edit Connection',
			icon:'/helix/images/silk/gifs/pencil.gif',
			handler:function(item,e){
				var window = new Monoql.Window.ConnectionWindow({
					node:node
				});
				window.show();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Close All Tabs',
			icon:'/helix/images/silk/gifs/tab_delete.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').removeConnectionTabs(dataconnection);
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Remove',
			icon:'/helix/images/silk/gifs/cross.gif',
			handler:this.showRemoveConnectionWindow
		}));
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	},
	
	showRemoveConnectionWindow:function(item,e){
		var dataconnection = this.ownerCt.dataconnection;
		Ext.Msg.confirm(
			'Remove \'' + dataconnection.name + '\' Connection',
			'About to remove connection: \'' + dataconnection.name + '\'. This will close any tabs associated with this connection.<br /><br />Are you sure you want to remove this connection?',
			this.ownerCt.removeConnection,
			this.ownerCt
		);
	},
	
	removeConnection:function(buttonid){
		if (buttonid=='yes'){
			this.node.removeConnection();
		}
	}
});Ext.namespace('Monoql.Menu.DatabaseNodeMenu');

Monoql.Menu.DatabaseNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.DatabaseNodeMenu.superclass.initComponent.call(this);
		var dataconnection = this.node.attributes.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'New Query',
			icon:'/helix/images/silk/gifs/tab_add.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').addQueryTab(dataconnection);
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Run SQL File...',
			icon:'/helix/images/silk/gifs/database_go.gif',
			handler:function(item,e){
				this.ownerCt.node.showRunSqlFileWindow();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Empty All Tables',
			icon:'/helix/images/silk/gifs/cut_red.gif',
			handler:function(item,e){
				this.ownerCt.node.showTruncateDatabaseConfirmation();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Remove All Tables',
			icon:'/helix/images/silk/gifs/table_delete.gif',
			handler:function(item,e){
				this.ownerCt.node.showEmptyDatabaseConfirmation();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Delete Database',
			icon:'/helix/images/silk/gifs/database_delete.gif',
			handler:function(item,e){
				this.ownerCt.node.showDeleteDatabaseConfirmation();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.TableNodeMenu');

Monoql.Menu.TableNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.TableNodeMenu.superclass.initComponent.call(this);
		var dataconnection = this.node.attributes.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'New Query',
			icon:'/helix/images/silk/gifs/tab_add.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').addQueryTab(dataconnection);
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Open Table',
			icon:'/helix/images/silk/gifs/table.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').addDataTab(dataconnection);
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Import From CSV...',
			icon:'/helix/images/silk/gifs/page_white_get.gif',
			handler:function(item,e){
				this.ownerCt.node.showImportFromCsvWindow({
					node:this.ownerCt.node
				});
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Empty Table',
			icon:'/helix/images/silk/gifs/cut_red.gif',
			handler:function(item,e){
				this.ownerCt.node.showEmptyTableConfirmation();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Delete Table',
			icon:'/helix/images/silk/gifs/delete.gif',
			handler:function(item,e){
				this.ownerCt.node.showDeleteTableConfirmation();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.ColumnNodeMenu');

Monoql.Menu.ColumnNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.ColumnNodeMenu.superclass.initComponent.call(this);
	}
});Ext.namespace('Monoql.Menu.TableGroupNodeMenu');

Monoql.Menu.TableGroupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.TableGroupNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.ScriptGroupNodeMenu');

Monoql.Menu.ScriptGroupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.ScriptGroupNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.ViewGroupNodeMenu');

Monoql.Menu.ViewGroupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.ViewGroupNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.StoredProcedureGroupNodeMenu');

Monoql.Menu.StoredProcedureGroupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.StoredProcedureGroupNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.FunctionGroupNodeMenu');

Monoql.Menu.FunctionGroupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.FunctionGroupNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.TriggerGroupNodeMenu');

Monoql.Menu.TriggerGroupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.TriggerGroupNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.UserGroupNodeMenu');

Monoql.Menu.UserGroupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.UserGroupNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.BackupGroupNodeMenu');

Monoql.Menu.BackupGroupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.BackupGroupNodeMenu.superclass.initComponent.call(this);
		var node = this.node;
		this.addItem(new Ext.menu.Item({
			text:'Refresh',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:function(item,e){
				this.ownerCt.node.reload();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.ScriptNodeMenu');

Monoql.Menu.ScriptNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.ScriptNodeMenu.superclass.initComponent.call(this);
		var dataconnection = this.node.attributes.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'Open Script',
			icon:'/helix/images/silk/gifs/tab_add.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').addQueryTab(dataconnection);
			}
		}));
	}
});Ext.namespace('Monoql.Menu.ViewNodeMenu');

Monoql.Menu.ViewNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.ViewNodeMenu.superclass.initComponent.call(this);
		var dataconnection = this.node.attributes.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'Open View',
			icon:'/helix/images/silk/gifs/tab_add.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').addDataTab(dataconnection);
			}
		}));
	}
});Ext.namespace('Monoql.Menu.StoredProcedureNodeMenu');

Monoql.Menu.StoredProcedureNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.StoredProcedureNodeMenu.superclass.initComponent.call(this);
		var dataconnection = this.node.attributes.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'Open Stored Procedure',
			icon:'/helix/images/silk/gifs/tab_add.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').addQueryTab(dataconnection);
			}
		}));
	}
});Ext.namespace('Monoql.Menu.FunctionNodeMenu');

Monoql.Menu.FunctionNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.FunctionNodeMenu.superclass.initComponent.call(this);
		var dataconnection = this.node.attributes.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'Open Function',
			icon:'/helix/images/silk/gifs/tab_add.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').addQueryTab(dataconnection);
			}
		}));
	}
});Ext.namespace('Monoql.Menu.TriggerNodeMenu');

Monoql.Menu.TriggerNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.TriggerNodeMenu.superclass.initComponent.call(this);
		var dataconnection = this.node.attributes.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'Open Trigger',
			icon:'/helix/images/silk/gifs/tab_add.gif',
			handler:function(item,e){
				Ext.getCmp('main-tabpanel').addQueryTab(dataconnection);
			}
		}));
	}
});Ext.namespace('Monoql.Menu.UserNodeMenu');

Monoql.Menu.UserNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.UserNodeMenu.superclass.initComponent.call(this);
		var dataconnection = this.node.attributes.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'Edit User',
			icon:'/helix/images/silk/gifs/user_edit.gif',
			handler:function(item,e){
			}
		}));
	}
});Ext.namespace('Monoql.Menu.BackupNodeMenu');

Monoql.Menu.BackupNodeMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.BackupNodeMenu.superclass.initComponent.call(this);
		var dataconnection = this.node.attributes.dataconnection;
		this.addItem(new Ext.menu.Item({
			text:'Open Backup',
			icon:'/helix/images/silk/gifs/paper.gif',
			handler:function(item,e){
			}
		}));
	}
});Ext.namespace('Monoql.Menu.DataGridMenu');

Monoql.Menu.DataGridMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.DataGridMenu.superclass.initComponent.call(this);
		var grid = this.grid;

		this.addItem(new Ext.menu.Item({
			text:'Create insert statements...',
			icon:'/helix/images/silk/gifs/table_go.gif',
			handler:function(item,e){
				grid.showCreateInsertsWindow();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Export grid to CSV...',
			icon:'/helix/images/silk/gifs/page_white_put.gif',
			handler:function(item,e){
				grid.showExportToCsvWindow();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Export grid to XML...',
			icon:'/helix/images/silk/gifs/page_white_code.gif',
			handler:function(item,e){
				grid.showExportToXmlWindow();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Delete Selected Row(s)',
			icon:'/helix/images/silk/gifs/delete.gif',
			handler:function(item,e){
				//grid.deleteSelectedRows();
			}
		}));
	}
});Ext.namespace('Monoql.Menu.ResultGridMenu');

Monoql.Menu.ResultGridMenu = Ext.extend(Monoql.Menu,{
	initComponent:function(){
		Monoql.Menu.ResultGridMenu.superclass.initComponent.call(this);
		var grid = this.grid;

		this.addItem(new Ext.menu.Item({
			text:'Create insert statements...',
			icon:'/helix/images/silk/gifs/table_go.gif',
			handler:function(item,e){
				grid.showCreateInsertsWindow();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Export grid to CSV...',
			icon:'/helix/images/silk/gifs/page_white_put.gif',
			handler:function(item,e){
				grid.showExportToCsvWindow();
			}
		}));
		this.addItem(new Ext.menu.Item({
			text:'Export grid to XML...',
			icon:'/helix/images/silk/gifs/page_white_code.gif',
			handler:function(item,e){
				grid.showExportToXmlWindow();
			}
		}));
	}
});Ext.namespace('Monoql.TreeNode');

Monoql.TreeNode = Ext.extend(Ext.tree.AsyncTreeNode,{
	constructor:function(attributes){
		Monoql.TreeNode.superclass.constructor.call(this,attributes);
		this.addListener("beforeload",function(node){
			var baseParams = Ext.apply({},node.dataconnection || {});
			baseParams.nodeType = node.nodeType;
			node.getOwnerTree().getLoader().baseParams = baseParams;
		});
		this.addListener("beforecollapse",function(node,deep,anim){
			while(node.firstChild){
	        	node.removeChild(node.firstChild).destroy();
	        }
	        node.childrenRendered = false;
	        node.loaded = false;
		});
		this.addListener("contextmenu",function(node,e){
			node.select();
		});
	}
});

Ext.reg('monoql-treenode',Monoql.TreeNode);

Ext.namespace('Monoql.TreeNode.ColumnNode');

Monoql.TreeNode.ColumnNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.cls = 'monoql-not-expandable';
			attributes.iconCls = 'monoql-column-node';
			Monoql.TreeNode.ColumnNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'ColumnNode';
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.superclass.nodeType = this.nodeType;
		}
});

Ext.reg('monoql-columnnode',Monoql.TreeNode.ColumnNode);Ext.namespace('Monoql.TreeNode.DatabaseNode');

Monoql.TreeNode.DatabaseNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-database-node';
			Monoql.TreeNode.DatabaseNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'DatabaseNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.DatabaseNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		showTruncateDatabaseConfirmation:function(){
			Ext.Msg.confirm(
				'Empty All Tables',
				'This will empty all tables in this database.<br /><br />Are you sure you want to continue?',
				this.truncateDatabase,
				this
			);
		},
		
		truncateDatabase:function(buttonid){
			if (buttonid=='yes'){
				Ext.fly(this.ui.getIconEl()).addClass('loading-indicator');
				Ext.Ajax.request({
					url:'/index?ajax&f=truncate_database',
					params:Ext.apply({},this.dataconnection),
					callback:function(options,success,response){
						Ext.fly(this.ui.getIconEl()).removeClass('loading-indicator');
					},
					scope:this
				});
			}
		},
		
		showEmptyDatabaseConfirmation:function(){
			Ext.Msg.confirm(
				'Remove All Tables',
				'This will remove all tables from this database.<br /><br />Are you sure you want to continue?',
				this.emptyDatabase,
				this
			);
		},
		
		emptyDatabase:function(buttonid){
			if (buttonid=='yes'){
				Ext.fly(this.ui.getIconEl()).addClass('loading-indicator');
				Ext.Ajax.request({
					url:'/index?ajax&f=empty_database',
					params:Ext.apply({},this.dataconnection),
					callback:function(options,success,response){
						Ext.fly(this.ui.getIconEl()).removeClass('loading-indicator');
						this.reload();
					},
					scope:this
				});
			}
		},
		
		showDeleteDatabaseConfirmation:function(){
			Ext.Msg.confirm(
				'Delete Database',
				'This will delete this database and all records.<br /><br />Are you sure you want to continue?',
				this.deleteDatabase,
				this
			);
		},
		
		deleteDatabase:function(buttonid){
			if (buttonid=='yes'){
				Ext.fly(this.ui.getIconEl()).addClass('loading-indicator');
				Ext.Ajax.request({
					url:'/index?ajax&f=delete_database',
					params:Ext.apply({},this.dataconnection),
					callback:function(options,success,response){
						Ext.fly(this.ui.getIconEl()).removeClass('loading-indicator');
						this.remove();
					},
					scope:this
				});
			}
		},
		
		showRunSqlFileWindow:function(){
			var window = new Monoql.Window.RunSqlFileWindow({
				node:this
			});
			window.show();
		}
});

Ext.reg('monoql-databasenode',Monoql.TreeNode.DatabaseNode);Ext.namespace('Monoql.TreeNode.TableNode');

Monoql.TreeNode.TableNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-table-node';
			Monoql.TreeNode.TableNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'TableNode';
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.superclass.nodeType = this.nodeType;
			this.addListener('dblclick',this.onDblClick,this);
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.TableNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		onDblClick:function(node,e){
			Ext.getCmp('main-tabpanel').addDataTab(this.dataconnection);
		},
		
		showEmptyTableConfirmation:function(){
			Ext.Msg.confirm(
				'Empty Table',
				'This will remove all records from this table.<br /><br />Are you sure you want to continue?',
				this.emptyTable,
				this
			);
		},
		
		emptyTable:function(buttonid){
			if (buttonid=='yes'){
				Ext.fly(this.ui.getIconEl()).addClass('loading-indicator');
				Ext.Ajax.request({
					url:'/index?ajax&f=empty_table',
					params:Ext.apply({},this.dataconnection),
					callback:function(options,success,response){
						Ext.fly(this.ui.getIconEl()).removeClass('loading-indicator');
						this.reload();
					},
					scope:this
				});
			}
		},
		
		showDeleteTableConfirmation:function(){
			Ext.Msg.confirm(
				'Drop Table',
				'This will remove this table from this database.<br /><br />Are you sure you want to continue?',
				this.deleteTable,
				this
			);
		},
		
		deleteTable:function(buttonid){
			if (buttonid=='yes'){
				Ext.fly(this.ui.getIconEl()).addClass('loading-indicator');
				Ext.Ajax.request({
					url:'/index?ajax&f=drop_table',
					params:Ext.apply({},this.dataconnection),
					callback:function(options,success,response){
						Ext.fly(this.ui.getIconEl()).removeClass('loading-indicator');
						this.remove();
					},
					scope:this
				});
			}
		},
		
		showImportFromCsvWindow:function(){
			var window = new Monoql.Window.ImportFromCsvWindow({
				node:this
			});
			window.show();
		}
});

Ext.override(Ext.tree.TreeNodeUI,{
    onDblClick : function(e){
	    e.preventDefault();
	    if(this.disabled){
	        return;
	    }
	    if(this.checkbox){
	        this.toggleCheck();
	    }
	    if(!this.animating && this.node.isExpandable() && this.node.nodeType!='TableNode'){
	        this.node.toggle();
	    }
	    this.fireEvent("dblclick", this.node, e);
	}
});

Ext.reg('monoql-tablenode',Monoql.TreeNode.TableNode);Ext.namespace('Monoql.TreeNode.ConnectionNode');

Monoql.TreeNode.ConnectionNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-connection-node';
			Monoql.TreeNode.ConnectionNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'ConnectionNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addEvents(
				'beforeconnectionremove',
				'connectionremove'
			);
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.ConnectionNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		removeConnection:function(){
			this.removeRemoteConnection();
		},
		
		removeRemoteConnection:function(){
			Ext.Ajax.request({
				url:'/index?ajax&f=remove_connection',
				params:Ext.apply({},this.dataconnection),
				callback:function(options,success,response){
					this.removeLocalConnection.call(this);
				},
				scope:this
			});
		},
		
		removeLocalConnection:function(){
			Ext.getCmp('main-tabpanel').removeConnectionTabs(this.dataconnection);
			this.remove();
			this.onConnectionRemove();
		},
		
		onBeforeConnectionRemove:function(){
			this.fireEvent('beforeconnectionremove',this,this.dataconnection);
		},
		
		onConnectionRemove:function(){
			this.fireEvent('connectionremove',this,this.dataconnection);
		},
		
		showRunSqlFileWindow:function(){
			var window = new Monoql.Window.RunSqlFileWindow({
				node:this
			});
			window.show();
		}
});

Ext.reg('monoql-connectionnode',Monoql.TreeNode.ConnectionNode);Ext.namespace('Monoql.TreeNode.GroupNode');

Monoql.TreeNode.GroupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-group-node';
			Monoql.TreeNode.GroupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'GroupNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
			this.addListener('append',this.onAppend,this);
			this.addListener('remove',this.onRemove,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.GroupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		onAppend:function(tree,parent,child,index){
			Monoql.dataconnections.add(child.dataconnection);
			Ext.getCmp('main-tabpanel').updateConnectionStore();
		},
		
		onRemove:function(tree,parent,child){
			Monoql.dataconnections.remove(child.dataconnection);
		}
});

Ext.reg('monoql-groupnode',Monoql.TreeNode.GroupNode);Ext.namespace('Monoql.TreeNode.TableGroupNode');

Monoql.TreeNode.TableGroupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-tablegroup-node';
			Monoql.TreeNode.TableGroupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'TableGroupNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.TableGroupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		}
});

Ext.reg('monoql-tablegroupnode',Monoql.TreeNode.TableGroupNode);Ext.namespace('Monoql.TreeNode.ScriptGroupNode');

Monoql.TreeNode.ScriptGroupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-scriptgroup-node';
			Monoql.TreeNode.ScriptGroupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'ScriptGroupNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.ScriptGroupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		}
});

Ext.reg('monoql-scriptgroupnode',Monoql.TreeNode.ScriptGroupNode);Ext.namespace('Monoql.TreeNode.ViewGroupNode');

Monoql.TreeNode.ViewGroupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-scriptgroup-node';
			Monoql.TreeNode.ViewGroupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'ViewGroupNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.ViewGroupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		}
});

Ext.reg('monoql-viewgroupnode',Monoql.TreeNode.ViewGroupNode);Ext.namespace('Monoql.TreeNode.StoredProcedureGroupNode');

Monoql.TreeNode.StoredProcedureGroupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-scriptgroup-node';
			Monoql.TreeNode.StoredProcedureGroupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'StoredProcedureGroupNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.StoredProcedureGroupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		}
});

Ext.reg('monoql-storedproceduregroupnode',Monoql.TreeNode.StoredProcedureGroupNode);Ext.namespace('Monoql.TreeNode.FunctionGroupNode');

Monoql.TreeNode.FunctionGroupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-scriptgroup-node';
			Monoql.TreeNode.FunctionGroupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'FunctionGroupNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.FunctionGroupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		}
});

Ext.reg('monoql-functiongroupnode',Monoql.TreeNode.FunctionGroupNode);Ext.namespace('Monoql.TreeNode.TriggerGroupNode');

Monoql.TreeNode.TriggerGroupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-scriptgroup-node';
			Monoql.TreeNode.TriggerGroupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'TriggerGroupNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.TriggerGroupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		}
});

Ext.reg('monoql-triggergroupnode',Monoql.TreeNode.TriggerGroupNode);Ext.namespace('Monoql.TreeNode.UserGroupNode');

Monoql.TreeNode.UserGroupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-scriptgroup-node';
			Monoql.TreeNode.UserGroupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'UserGroupNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.UserGroupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		}
});

Ext.reg('monoql-usergroupnode',Monoql.TreeNode.UserGroupNode);Ext.namespace('Monoql.TreeNode.BackupGroupNode');

Monoql.TreeNode.BackupGroupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-scriptgroup-node';
			Monoql.TreeNode.BackupGroupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'BackupGroupNode';
			this.superclass.nodeType = this.nodeType;
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.BackupGroupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		}
});

Ext.reg('monoql-backupgroupnode',Monoql.TreeNode.BackupGroupNode);Ext.namespace('Monoql.TreeNode.ScriptNode');

Monoql.TreeNode.ScriptNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-script-node';
			Monoql.TreeNode.ScriptNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'ScriptNode';
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.superclass.nodeType = this.nodeType;
			this.addListener('dblclick',this.onDblClick,this);
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.ScriptNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		onDblClick:function(){
			Ext.getCmp('main-tabpanel').addDataTab(this.dataconnection);
		}
});

Ext.reg('monoql-scriptnode',Monoql.TreeNode.ScriptNode);Ext.namespace('Monoql.TreeNode.ViewNode');

Monoql.TreeNode.ViewNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-view-node';
			Monoql.TreeNode.ViewNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'ViewNode';
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.superclass.nodeType = this.nodeType;
			this.addListener('dblclick',this.onDblClick,this);
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.ViewNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		onDblClick:function(){
			Ext.getCmp('main-tabpanel').addDataTab(this.dataconnection);
		}
});

Ext.reg('monoql-viewnode',Monoql.TreeNode.ViewNode);Ext.namespace('Monoql.TreeNode.StoredProcedureNode');

Monoql.TreeNode.StoredProcedureNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-storedprocedure-node';
			Monoql.TreeNode.StoredProcedureNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'StoredProcedureNode';
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.superclass.nodeType = this.nodeType;
			this.addListener('dblclick',this.onDblClick,this);
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.StoredProcedureNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		onDblClick:function(){
		}
});

Ext.reg('monoql-storedprocedurenode',Monoql.TreeNode.StoredProcedureNode);Ext.namespace('Monoql.TreeNode.FunctionNode');

Monoql.TreeNode.FunctionNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-function-node';
			Monoql.TreeNode.FunctionNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'FunctionNode';
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.superclass.nodeType = this.nodeType;
			this.addListener('dblclick',this.onDblClick,this);
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.FunctionNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		onDblClick:function(){
		}
});

Ext.reg('monoql-functionnode',Monoql.TreeNode.FunctionNode);Ext.namespace('Monoql.TreeNode.TriggerNode');

Monoql.TreeNode.TriggerNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-trigger-node';
			Monoql.TreeNode.TriggerNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'TriggerNode';
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.superclass.nodeType = this.nodeType;
			this.addListener('dblclick',this.onDblClick,this);
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.TriggerNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		onDblClick:function(){
		}
});

Ext.reg('monoql-triggernode',Monoql.TreeNode.TriggerNode);Ext.namespace('Monoql.TreeNode.UserNode');

Monoql.TreeNode.UserNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-user-node';
			Monoql.TreeNode.UserNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'UserNode';
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.superclass.nodeType = this.nodeType;
			this.addListener('dblclick',this.onDblClick,this);
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.UserNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		onDblClick:function(){
		}
});

Ext.reg('monoql-usernode',Monoql.TreeNode.UserNode);Ext.namespace('Monoql.TreeNode.BackupNode');

Monoql.TreeNode.BackupNode = Ext.extend(Monoql.TreeNode,{
		constructor:function(attributes){
			attributes.iconCls = 'monoql-backup-node';
			Monoql.TreeNode.BackupNode.superclass.constructor.call(this,attributes);
			this.nodeType = 'BackupNode';
			this.dataconnection = this.attributes.dataconnection;
			this.superclass.dataconnection = this.dataconnection;
			this.superclass.nodeType = this.nodeType;
			this.addListener('dblclick',this.onDblClick,this);
			this.addListener('contextmenu',this.onContextMenu,this);
		},
		
		onContextMenu:function(node,e){
			var menu = new Monoql.Menu.BackupNodeMenu({
				node:this
			});
			menu.show(this.ui.getAnchor());
		},
		
		onDblClick:function(){
			Ext.getCmp('main-tabpanel').addDataTab(this.dataconnection);
		}
});

Ext.reg('monoql-backupnode',Monoql.TreeNode.BackupNode);Ext.namespace('Monoql.Tree');

Monoql.Tree = Ext.extend(Ext.tree.TreePanel,{
	initComponent:function(){
		Monoql.Tree.superclass.initComponent.call(this);
	}
});

Ext.reg('monoql-tree',Monoql.Tree);

Ext.namespace("Monoql.Tree.DatabaseTree");

Monoql.Tree.DatabaseTree = Ext.extend(Monoql.Tree,{
	autoScroll:true,
	rootVisible:true,
	animate:false,
	border:false,
	bodyStyle:'border-bottom-width:1px;',
	
	initComponent:function(){
		this.tbar = [{
			text:'New Connection',
			icon:'/helix/images/silk/gifs/server_add.gif',
			handler:this.showConnectionWindow
		},'-',{
			text:'',
			icon:'/helix/images/silk/gifs/arrow_refresh.gif',
			handler:this.refreshSelectedNode
		}];
		Monoql.Tree.DatabaseTree.superclass.initComponent.call(this);
	},
	
	showConnectionWindow:function(button,e){
		var window = new Monoql.Window.ConnectionWindow({
			node:Ext.getCmp('main-treepanel').getRootNode()
		});
		window.show();
	},
	
	refreshSelectedNode:function(button,e){
		var selectedNode, tree = this.ownerCt.ownerCt;
		if (tree.getSelectionModel().getSelectedNode()){
			selectedNode = tree.getSelectionModel().getSelectedNode();
		} else{
			selectedNode = tree.getRootNode();
		}
		selectedNode.ensureVisible();
		selectedNode.reload();
	}
});

Ext.tree.TreePanel.nodeTypes = {
	GroupNode:Monoql.TreeNode.GroupNode,
	ConnectionNode:Monoql.TreeNode.ConnectionNode,
	DatabaseNode:Monoql.TreeNode.DatabaseNode,
	TableNode:Monoql.TreeNode.TableNode,
	ColumnNode:Monoql.TreeNode.ColumnNode,
	TableGroupNode:Monoql.TreeNode.TableGroupNode,
	ScriptGroupNode:Monoql.TreeNode.ScriptGroupNode,
	ViewGroupNode:Monoql.TreeNode.ViewGroupNode,
	StoredProcedureGroupNode:Monoql.TreeNode.StoredProcedureGroupNode,
	FunctionGroupNode:Monoql.TreeNode.FunctionGroupNode,
	TriggerGroupNode:Monoql.TreeNode.TriggerGroupNode,
	UserGroupNode:Monoql.TreeNode.UserGroupNode,
	BackupGroupNode:Monoql.TreeNode.BackupGroupNode,
	ScriptNode:Monoql.TreeNode.ScriptNode,
	ViewNode:Monoql.TreeNode.ViewNode,
	StoredProcedureNode:Monoql.TreeNode.StoredProcedureNode,
	FunctionNode:Monoql.TreeNode.FunctionNode,
	TriggerNode:Monoql.TreeNode.TriggerNode,
	UserNode:Monoql.TreeNode.UserNode,
	BackupNode:Monoql.TreeNode.BackupNode
};

Ext.reg('monoql-databasetree',Monoql.Tree.DatabaseTree);Ext.namespace('Monoql.Form');
Ext.namespace('Monoql.Form.QueryField');

Monoql.Form.QueryField = Ext.extend(Ext.form.TextArea,{
	query:null,
	executing:false,
	cancelled:false,
	cls:'monoql-queryfield',
	hideLabel:true,
	anchor:'0 0',
	enableKeyEvents:true,
	
	initComponent:function(){
		Monoql.Form.QueryField.superclass.initComponent.call(this);
		this.database = null;
		this.table = null;
		this.addEvents(
			'beforequery',
			'afterquery',
			'queryresponse',
			'cancelquery'
		);
	},
	
	executeQuery:function(query){
		this.onBeforeQuery();
		var queryForm = this.ownerCt;
		Ext.Ajax.request({
			url:'/index?ajax&f=get_grid_data',
			params:Ext.apply({
				database:this.database,
				table:this.table,
				query:query
			},this.getQueryTab().dataconnection),
			scope:this,
			callback:function(options,success,response){
				var responseObject = Ext.decode(response.responseText);
				var query = responseObject.query;
				this.onQueryResponse(query,responseObject);
			}
		});
		this.transactionId = Ext.lib.Ajax.transactionId;
	},
	
	onBeforeQuery:function(){
		this.executing = true;
		this.cancelled = false;
		this.updateMenuBar();
		
		var resultTab = this.getQueryTab().resultTabSet.getResultTab();
		var messageTab = this.getQueryTab().resultTabSet.getMessageTab();
		this.addListener('beforequery',resultTab.prepareGrid,resultTab,{single:true});
		this.addListener('afterquery',resultTab.finalizeGrid,resultTab,{single:true});
		this.addListener('queryresponse',resultTab.renderGrid,resultTab,{single:true});
		this.addListener('beforequery',messageTab.clearMessages,messageTab,{single:true});
		this.addListener('queryresponse',messageTab.displayMessages,messageTab,{single:true});
		this.ownerCt.getEl().mask('Executing Query...');
		this.fireEvent('beforequery',this);
	},
	
	onAfterQuery:function(){
		this.executing = false;
		this.updateMenuBar();
		this.fireEvent('afterquery',this);
	},
	
	onQueryResponse:function(query,response){
		this.ownerCt.getEl().unmask();
		this.fireEvent('queryresponse',this,query,response);
		this.onAfterQuery();
	},
	
	highlightSyntax:function(){
		alert('Highlighting Syntax...');
	},
	
	displaySuggestions:function(){
		alert('Displaying Suggestions...');
	},
	
	getQueryTab:function(){
		return Ext.getCmp(this.getEl().up('div[id^=QueryTab]').id);
	},
	
	saveQuery:function(query,filename,mode){
		if (query.trim().length>0){
			this.ownerCt.getEl().mask('Saving...');
			Ext.Ajax.request({
				url:'/index?ajax&f=save_query',
				params:Ext.apply({
					query:query,
					mode:mode
				},this.getQueryTab().dataconnection),
				scope:this,
				callback:function(options,success,response){
					var responseObject = Ext.decode(response.responseText);
					window.location = responseObject.webpath + '?attachment&filename=' + filename + '.sql';
					this.ownerCt.getEl().unmask();
				}
			});
		}
	},
	
	cancelQuery:function(){
		if (this.executing){
			this.cancelled = true;
			this.executing = false;
			this.updateMenuBar();
			
			this.fireEvent('cancelquery',this);
			var resultTab = this.getQueryTab().resultTabSet.getResultTab();
			var messageTab = this.getQueryTab().resultTabSet.getMessageTab();
			this.removeListener('beforequery',resultTab.prepareGrid,resultTab);
			this.removeListener('afterquery',resultTab.finalizeGrid,resultTab);
			this.removeListener('queryresponse',resultTab.renderGrid,resultTab);
			this.removeListener('beforequery',messageTab.clearMessages,messageTab);
			this.removeListener('queryresponse',messageTab.displayMessages,messageTab);
			if (Ext.isNumber(this.transactionId)){
				Ext.Ajax.abort(this.transactionId);
			}
			this.getQueryTab().getEl().unmask();
		}
	},
	
	updateMenuBar:function(){
		Ext.getCmp('Monoql-MenuBar-ExecuteQueryButton').setDisabled(this.executing);
		Ext.getCmp('Monoql-MenuBar-CancelQueryButton').setDisabled(!this.executing);
	}
});

Ext.reg('monoql-queryfield',Monoql.Form.QueryField);Ext.namespace('Monoql.Form.QueryForm');

Monoql.Form.QueryForm = Ext.extend(Ext.form.FormPanel,{
	initComponent:function(){
		Monoql.Form.QueryForm.superclass.initComponent.call(this);
		this.add({
			xtype:'monoql-queryfield',
			name:'query',
			value:this.queryText,
			id:this.queryId
		});
	}
});

Ext.reg('monoql-queryform',Monoql.Form.QueryForm);Ext.namespace('Monoql.Form.ConnectionForm');

Monoql.Form.ConnectionForm = Ext.extend(Ext.form.FormPanel,{
	labelAlign:'top',
	frame:true,
	border:false,
	width:440,
	id:'ConnectionForm',
	
	initComponent:function(){
		Monoql.Form.ConnectionForm.superclass.initComponent.call(this);
		this.add({
			xtype:'hidden',
			name:'dataconnection:id',
			value:this.node.dataconnection.id
		});
		this.add({
			xtype:'panel',
			layout:'column',
			items:[{
				columnWidth:0.5,
				layout:'form',
				defaults:{
					width:200
				},
				items:[{
					xtype:'textfield',
					name:'dataconnection:name',
					value:this.node.dataconnection.name,
					fieldLabel:'Connection Name',
					allowBlank:false
				},{
					xtype:'textfield',
					name:'dataconnection:host',
					value:this.node.dataconnection.host,
					fieldLabel:'Hostname or IP Address',
					allowBlank:false
				},{
					xtype:'textfield',
					name:'dataconnection:username',
					value:this.node.dataconnection.username,
					fieldLabel:'Username',
					allowBlank:false
				},{
					xtype:'textfield',
					name:'dataconnection:password',
					value:'',
					inputType:'password',
					fieldLabel:'Password'
				}]
			},{
				columnWidth:0.5,
				layout:'form',
				defaults:{
					width:200
				},
				items:[{
					xtype:'combo',
					hiddenName:'dataconnection:group_id',
					value:'',
					fieldLabel:'Group',
					store:new Ext.data.ArrayStore({
				        id:0,
				        fields:['name','description'],
				        data:[[0,'Connections']]		
				    }),
					forceSelection:true,
					mode:'local',
					valueField:'name',
					displayField:'description',
					triggerAction:'all',
					editable:false
				},{
					xtype:'combo',
					hiddenName:'dataconnection:type',
					value:this.node.dataconnection.type,
					fieldLabel:'Database type',
					store:new Ext.data.ArrayStore({
				        id:0,
				        fields:['name','description'],
				        data:[['mysql','MySQL']]		
				    }),
					forceSelection:true,
					mode:'local',
					valueField:'name',
					displayField:'description',
					triggerAction:'all',
					editable:false
				},{
					xtype:'textfield',
					name:'dataconnection:port',
					value:this.node.dataconnection.port,
					fieldLabel:'Port (Optional)'
				},{
					xtype:'textfield',
					name:'dataconnection:database',
					value:this.node.dataconnection.database,
					fieldLabel:'Default database (Optional)'
				}]
			}]
		});
		this.addButton({
			text:'Save',
			handler:function(button,e){
				var form = Ext.getCmp('ConnectionForm');
				form.getEl().mask('Saving...');
				form.getForm().submit({
					url:'/index?ajax&f=edit_connection',
					success:function(form,action){
						this.getEl().unmask();
						if (form.window){
							form.window.close();
						}
						var node = new Monoql.TreeNode.ConnectionNode(action.result.nodeConfig);
						if (form.node.dataconnection.id==node.attributes.dataconnection.id){
							form.node.parentNode.replaceChild(node,form.node);
						} else{
							form.window.node.appendChild(node);	
						}
					},
					failure:function(form,action){
						this.getEl().unmask();
						Ext.Msg.alert(
							'Connection Error',
							'Connection failed.  Please correct connection settings.'
						);
					},
					scope:form
				});
			}
		});
		this.addButton({
			text:'Cancel',
			handler:function(button,e){
				var form = Ext.getCmp('ConnectionForm');	
				if (form.window){
					form.window.close();
				}
			}
		});
	}
});

Ext.reg('monoql-connectionform',Monoql.Form.ConnectionForm);Ext.namespace('Monoql.Form.QueryConnectionForm');

Monoql.Form.QueryConnectionForm = Ext.extend(Ext.form.FormPanel,{
	labelAlign:'top',
	frame:true,
	border:false,
	width:250,
	id:'QueryConnectionForm',

	initComponent:function(){
		Monoql.Form.QueryConnectionForm.superclass.initComponent.call(this);
		this.add({
			xtype:'combo',
			hiddenName:'dataconnection:id',
			value:'',
			width:230,
			fieldLabel:'Select a connection for the new query',
			store:new Ext.data.JsonStore({
		        idPropery:'id',
		        fields:['id','name'],
		        root:'data',
		        data:this.connectionStoreData()
		    }),
			forceSelection:true,
			mode:'local',
			valueField:'id',
			displayField:'name',
			triggerAction:'all',
			editable:false
		});
		this.addButton({
			text:'OK',
			handler:function(button,e){
				var form = Ext.getCmp('QueryConnectionForm');
				var id = form.getForm().findField('dataconnection:id').getValue();
				if (id>0){
					var dataconnection = Monoql.dataconnections.get(id);
					Ext.getCmp('main-tabpanel').addQueryTab(dataconnection);
					form.window.close();
				} else if (id=='new'){
					form.window.close();
					Ext.getCmp('main-treepanel').showConnectionWindow();
				}
			}
		});
		this.addButton({
			text:'Cancel',
			handler:function(button,e){
				var form = Ext.getCmp('QueryConnectionForm');	
				if (form.window){
					form.window.close();
				}
			}
		});
	},
	
	connectionStoreData:function(){
		var data = [{
			id:'new',
			name:'[Add New Connection]'
		}];
		Monoql.dataconnections.each(function(item,index,length){
			data[data.length] = item;
		});
		return {data:data};
	}
});

Ext.reg('monoql-queryconnectionform',Monoql.Form.QueryConnectionForm);Ext.namespace('Monoql.Form.OpenQueryForm');

Monoql.Form.OpenQueryForm = Ext.extend(Ext.form.FormPanel,{
	labelAlign:'top',
	frame:true,
	border:false,
	width:250,
	id:'OpenQueryForm',
	fileUpload:true,
	
	initComponent:function(){
		Monoql.Form.OpenQueryForm.superclass.initComponent.call(this);
		activeTab = Ext.getCmp('main-tabpanel').getActiveTab(); 
		this.add({
			xtype:'combo',
			hiddenName:'dataconnection:id',
			value:activeTab.xtype=='monoql-querytab' ? activeTab.dataconnection.id : '',
			width:230,
			fieldLabel:'Select a connection for this query',
			store:new Ext.data.JsonStore({
		        idPropery:'id',
		        fields:['id','name'],
		        root:'data',
		        data:this.connectionStoreData()
		    }),
			forceSelection:true,
			mode:'local',
			valueField:'id',
			displayField:'name',
			triggerAction:'all',
			editable:false
		});
		this.add({
			xtype:'fileuploadfield',
			name:'sqlscript',
			fieldLabel:'Select file to open',
			buttonText:'Browse...',
			width:230
		});
		this.addButton({
			text:'Open',
			handler:function(button,e){
				var form = Ext.getCmp('OpenQueryForm');
				var id = form.getForm().findField('dataconnection:id').getValue();
				if (id=='new'){
					form.window.close();
					Ext.getCmp('main-treepanel').showConnectionWindow();
				} else if (id>0) {
					var dataconnection = Monoql.dataconnections.get(id);
					form.getForm().submit({
						url:'/index?ajax&f=open_query',
						success:function(form,action){
							activeTab = Ext.getCmp('main-tabpanel').getActiveTab();
							if (activeTab.xtype=='monoql-querytab' && activeTab.dataconnection.id==action.result.dataconnection.id && activeTab.queryField.getValue().trim()=='') {
								activeTab.queryField.setValue(action.result.query);
							} else {
								Ext.getCmp('main-tabpanel').addQueryTab(dataconnection,action.result.query);
							}
							form.window.close();
						},
						failure:function(form,action){
							Ext.Msg.alert('File Required','You must select a file that is not empty');
						}
					});
				} else {
					Ext.Msg.alert('Missing Required Field','You must select a connection');
				}
			}
		});
		this.addButton({
			text:'Cancel',
			handler:function(button,e){
				var form = Ext.getCmp('OpenQueryForm');	
				if (form.window){
					form.window.close();
				}
			}
		});
	},
	
	connectionStoreData:function(){
		var data = [{
			id:'new',
			name:'[Add New Connection]'
		}];
		Monoql.dataconnections.each(function(item,index,length){
			data[data.length] = item;
		});
		return {data:data};
	}
});

Ext.reg('monoql-openqueryform',Monoql.Form.OpenQueryForm);Ext.namespace('Monoql.Form.ExportToCsvForm');

Monoql.Form.ExportToCsvForm = Ext.extend(Ext.form.FormPanel,{
	labelAlign:'left',
	frame:true,
	border:false,
	width:250,
	id:'ExportToCsvForm',
	defaults:{
		labelStyle:'width:180px;',
		width:50
	},
	
	initComponent:function(){
		var grid = this.grid;
		Monoql.Form.ExportToCsvForm.superclass.initComponent.call(this);
		this.add({
			xtype:'textfield',
			name:'delimiter',
			value:',',
			fieldLabel:'Fields separated by'
		});
		this.add({
			xtype:'textfield',
			name:'encapsulator',
			value:'',
			fieldLabel:'Fields enclosed by'
		});
		this.add({
			xtype:'textfield',
			name:'terminator',
			value:'\\r\\n',
			fieldLabel:'Lines terminated by'
		});
		this.add({
			xtype:'numberfield',
			name:'line-count',
			value:'10000',
			allowDecimals:false,
			allowNegative:false,
			fieldLabel:'Maximum records returned'
		});
		this.addButton({
			text:'Export',
			handler:function(button,e){
				var formpanel = Ext.getCmp('ExportToCsvForm');
				formpanel.getEl().mask('Exporting...');
				formpanel.getForm().baseParams = Ext.apply({},grid.getStore().baseParams);
				formpanel.getForm().submit({
					url:'/index?ajax&f=export_to_csv',
					success:function(form,action){
						activeTab = Ext.getCmp('main-tabpanel').getActiveTab();
						window.location = action.result.webpath + '?attachment&filename=grid-export.csv';
						formpanel.getEl().unmask();
						form.window.close();
					}
				});
			}
		});
		this.addButton({
			text:'Cancel',
			handler:function(button,e){
				var form = Ext.getCmp('ExportToCsvForm');	
				if (form.window){
					form.window.close();
				}
			}
		});
	}
});

Ext.reg('monoql-exporttocsvform',Monoql.Form.ExportToCsvForm);Ext.namespace('Monoql.Form.ExportToXmlForm');

Monoql.Form.ExportToXmlForm = Ext.extend(Ext.form.FormPanel,{
	labelAlign:'left',
	frame:true,
	border:false,
	width:250,
	id:'ExportToXmlForm',
	defaults:{
		labelStyle:'width:180px;',
		width:50
	},
	
	initComponent:function(){
		var grid = this.grid;
		Monoql.Form.ExportToXmlForm.superclass.initComponent.call(this);
		this.add({
			xtype:'numberfield',
			name:'line-count',
			value:'10000',
			allowDecimals:false,
			allowNegative:false,
			fieldLabel:'Maximum records returned'
		});
		this.addButton({
			text:'Export',
			handler:function(button,e){
				var formpanel = Ext.getCmp('ExportToXmlForm');
				formpanel.getEl().mask('Exporting...');
				formpanel.getForm().baseParams = Ext.apply({},grid.getStore().baseParams);
				formpanel.getForm().submit({
					url:'/index?ajax&f=export_to_xml',
					success:function(form,action){
						activeTab = Ext.getCmp('main-tabpanel').getActiveTab();
						window.location = action.result.webpath + '?attachment&filename=grid-export.xml';
						formpanel.getEl().unmask();
						form.window.close();
					}
				});
			}
		});
		this.addButton({
			text:'Cancel',
			handler:function(button,e){
				var form = Ext.getCmp('ExportToXmlForm');	
				if (form.window){
					form.window.close();
				}
			}
		});
	}
});

Ext.reg('monoql-exporttoxmlform',Monoql.Form.ExportToXmlForm);Ext.namespace('Monoql.Form.CreateInsertsForm');

Monoql.Form.CreateInsertsForm = Ext.extend(Ext.form.FormPanel,{
	labelAlign:'top',
	frame:true,
	border:false,
	width:250,
	id:'CreateInsertsForm',
	
	initComponent:function(){
		var grid = this.grid;
		Monoql.Form.CreateInsertsForm.superclass.initComponent.call(this);
		this.add({
			xtype:'textfield',
			name:'tablename',
			value:'tablename',
			fieldLabel:'Table name for insert statements'
		});
		this.add({
			xtype:'numberfield',
			name:'line-count',
			value:'10000',
			allowDecimals:false,
			allowNegative:false,
			fieldLabel:'Maximum records returned'
		});
		this.addButton({
			text:'Submit',
			handler:function(button,e){
				var formpanel = Ext.getCmp('CreateInsertsForm');
				formpanel.getEl().mask('Creating Insert Statements...');
				formpanel.getForm().baseParams = Ext.apply({},grid.getStore().baseParams);
				formpanel.getForm().submit({
					url:'/index?ajax&f=create_insert_statements',
					success:function(form,action){
						activeTab = Ext.getCmp('main-tabpanel').getActiveTab();
						window.location = action.result.webpath + '?attachment&filename=table-insert-statements.sql';
						formpanel.getEl().unmask();
						form.window.close();
					}
				});
			}
		});
		this.addButton({
			text:'Cancel',
			handler:function(button,e){
				var form = Ext.getCmp('CreateInsertsForm');	
				if (form.window){
					form.window.close();
				}
			}
		});
	}
});

Ext.reg('monoql-createinsertsform',Monoql.Form.CreateInsertsForm);Ext.namespace('Monoql.Form.ImportFromCsvForm');

Monoql.Form.ImportFromCsvForm = Ext.extend(Ext.form.FormPanel,{
	labelAlign:'left',
	frame:true,
	border:false,
	width:250,
	id:'ImportFromCsvForm',
	fileUpload:true,
	defaults:{
		labelStyle:'width:180px;',
		width:50
	},
	
	initComponent:function(){
		var node = this.node;
		Monoql.Form.ImportFromCsvForm.superclass.initComponent.call(this);
		this.add({
			xtype:'fileuploadfield',
			name:'csvfile',
			hideLabel:true,
			buttonText:'Browse...',
			width:233
		});
		this.add({
			xtype:'textfield',
			name:'delimiter',
			value:',',
			fieldLabel:'Fields separated by'
		});
		this.add({
			xtype:'textfield',
			name:'encapsulator',
			value:'',
			fieldLabel:'Fields enclosed by'
		});
		this.add({
			xtype:'textfield',
			name:'terminator',
			value:'\\r\\n',
			fieldLabel:'Lines terminated by'
		});
		this.add({
			xtype:'numberfield',
			name:'ignore-lines-num',
			value:'0',
			allowDecimals:false,
			allowNegative:false,
			fieldLabel:'Ignore this many lines'
		});
		this.addButton({
			text:'Import',
			handler:function(button,e){
				var formpanel = Ext.getCmp('ImportFromCsvForm');
				formpanel.getEl().mask('Importing...');
				formpanel.getForm().baseParams = Ext.apply({},node.dataconnection);
				formpanel.getForm().submit({
					url:'/index?ajax&f=import_from_csv',
					success:function(form,action){
						Ext.Msg.alert(
							'Import Successful',
							'Your CSV has been successfully imported into: ' + node.dataconnection.database + '.' + node.dataconnection.table,
							function(){
								form.window.close();
							}
						);
					},
					failure:function(form,action){
						Ext.Msg.alert(
							'Import Failed',
							'Your CSV has NOT been imported into: ' + node.dataconnection.database + '.' + node.dataconnection.table +
							'<br /><br />' + 
							'Error: ' + action.result.error,
							function(){
								formpanel.getEl().unmask();
							}
						);
					}
				});
			}
		});
		this.addButton({
			text:'Cancel',
			handler:function(button,e){
				var form = Ext.getCmp('ImportFromCsvForm');	
				if (form.window){
					form.window.close();
				}
			}
		});
	}
});

Ext.reg('monoql-importfromcsvform',Monoql.Form.ImportFromCsvForm);Ext.namespace('Monoql.Form.RunSqlFileForm');

Monoql.Form.RunSqlFileForm = Ext.extend(Ext.form.FormPanel,{
	labelAlign:'top',
	frame:true,
	border:false,
	width:250,
	id:'RunSqlFileForm',
	fileUpload:true,
	
	initComponent:function(){
		var node = this.node;
		Monoql.Form.RunSqlFileForm.superclass.initComponent.call(this);
		this.add({
			xtype:'fileuploadfield',
			name:'sqlfile',
			fieldLabel:'Choose SQL file to run',
			buttonText:'Browse...',
			width:230
		});
		this.addButton({
			text:'Run SQL',
			handler:function(button,e){
				var formpanel = Ext.getCmp('RunSqlFileForm');
				formpanel.getEl().mask('Running SQL File...');
				formpanel.getForm().baseParams = node.dataconnection;
				formpanel.getForm().submit({
					url:'/index?ajax&f=run_sql_file',
					success:function(form,action){
						Ext.Msg.alert(
							'SQL Executed',
							'Your SQL file has been successfully executed',
							function(){
								form.window.close();
							}
						);
					},
					failure:function(form,action){
						Ext.Msg.alert(
							'SQL Failed',
							'Your SQL file has failed to run.' +
							'<br /><br />' + 
							'Error: ' + action.result.error,
							function(){
								formpanel.getEl().unmask();
							}
						);
					}
				});
			}
		});
		this.addButton({
			text:'Cancel',
			handler:function(button,e){
				var form = Ext.getCmp('RunSqlFileForm');	
				if (form.window){
					form.window.close();
				}
			}
		});
	}
});

Ext.reg('monoql-runsqlfileform',Monoql.Form.RunSqlFileForm);Ext.namespace('Monoql.Window');

Monoql.Window = Ext.extend(Ext.Window,{
	constrainHeader:true,
	
	initComponent:function(){
		Monoql.Window.superclass.initComponent.call(this);
	}
});

Ext.reg('monoql-window',Monoql.Window);

Ext.namespace('Monoql.Window.ConnectionWindow');

Monoql.Window.ConnectionWindow = Ext.extend(Monoql.Window,{
	y:100,
	modal:true,
	title:'Edit Connection Details',
	resizable:false,
	width:450,
	
	initComponent:function(){
		Monoql.Window.ConnectionWindow.superclass.initComponent.call(this);
		this.add({
			xtype:'monoql-connectionform',
			window:this,
			node:this.node
		});
	}
});

Ext.reg('monoql-connectionwindow',Monoql.Window.ConnectionWindow);Ext.namespace('Monoql.Window.QueryConnectionWindow');

Monoql.Window.QueryConnectionWindow = Ext.extend(Monoql.Window,{
	y:100,
	modal:true,
	title:'Open Query Tab',
	resizable:false,
	width:260,
	
	initComponent:function(){
		Monoql.Window.QueryConnectionWindow.superclass.initComponent.call(this);
		this.add({
			xtype:'monoql-queryconnectionform',
			window:this
		});
	}
});

Ext.reg('monoql-queryconnectionwindow',Monoql.Window.QueryConnectionWindow);Ext.namespace('Monoql.Window.OpenQueryWindow');

Monoql.Window.OpenQueryWindow = Ext.extend(Monoql.Window,{
	y:100,
	modal:true,
	title:'Open SQL File',
	resizable:false,
	width:260,
	
	initComponent:function(){
		Monoql.Window.OpenQueryWindow.superclass.initComponent.call(this);
		this.add({
			xtype:'monoql-openqueryform',
			window:this
		});
	}
});

Ext.reg('monoql-openquerywindow',Monoql.Window.OpenQueryWindow);Ext.namespace('Monoql.Window.ExportToCsvWindow');

Monoql.Window.ExportToCsvWindow = Ext.extend(Monoql.Window,{
	y:100,
	modal:true,
	title:'Export To CSV',
	resizable:false,
	width:260,
	
	initComponent:function(){
		Monoql.Window.ExportToCsvWindow.superclass.initComponent.call(this);
		this.add({
			xtype:'monoql-exporttocsvform',
			grid:this.grid,
			window:this
		});
	}
});

Ext.reg('monoql-exporttocsvwindow',Monoql.Window.ExportToCsvWindow);Ext.namespace('Monoql.Window.ExportToXmlWindow');

Monoql.Window.ExportToXmlWindow = Ext.extend(Monoql.Window,{
	y:100,
	modal:true,
	title:'Export To XML',
	resizable:false,
	width:260,
	
	initComponent:function(){
		Monoql.Window.ExportToXmlWindow.superclass.initComponent.call(this);
		this.add({
			xtype:'monoql-exporttoxmlform',
			grid:this.grid,
			window:this
		});
	}
});

Ext.reg('monoql-exporttoxmlwindow',Monoql.Window.ExportToXmlWindow);Ext.namespace('Monoql.Window.CreateInsertsWindow');

Monoql.Window.CreateInsertsWindow = Ext.extend(Monoql.Window,{
	y:100,
	modal:true,
	title:'Create Insert Statements',
	resizable:false,
	width:260,
	
	initComponent:function(){
		Monoql.Window.CreateInsertsWindow.superclass.initComponent.call(this);
		this.add({
			xtype:'monoql-createinsertsform',
			grid:this.grid,
			window:this
		});
	}
});

Ext.reg('monoql-createinsertswindow',Monoql.Window.CreateInsertsWindow);Ext.namespace('Monoql.Window.ImportFromCsvWindow');

Monoql.Window.ImportFromCsvWindow = Ext.extend(Monoql.Window,{
	y:100,
	modal:true,
	title:'Import From CSV',
	resizable:false,
	width:260,
	
	initComponent:function(){
		Monoql.Window.ImportFromCsvWindow.superclass.initComponent.call(this);
		this.add({
			xtype:'monoql-importfromcsvform',
			node:this.node,
			window:this
		});
	}
});

Ext.reg('monoql-importfromcsvwindow',Monoql.Window.ImportFromCsvWindow);Ext.namespace('Monoql.Window.RunSqlFileWindow');

Monoql.Window.RunSqlFileWindow = Ext.extend(Monoql.Window,{
	y:100,
	modal:true,
	title:'Run SQL File',
	resizable:false,
	width:260,
	
	initComponent:function(){
		Monoql.Window.RunSqlFileWindow.superclass.initComponent.call(this);
		this.add({
			xtype:'monoql-runsqlfileform',
			node:this.node,
			window:this
		});
	}
});

Ext.reg('monoql-runsqlfilewindow',Monoql.Window.RunSqlFileWindow);Ext.namespace('Monoql.Layout');
Ext.namespace('Monoql.Layout.North');
Monoql.Layout.North = new Ext.Panel({
	region:'north',
	layout:'fit',
	height:26,
	border:false,
	bodyStyle:'border-bottom-width:1px;',
	items:[Monoql.Bar.MenuBar]
});Ext.namespace('Monoql.Layout.Center.Center');
Monoql.Layout.Center.Center = new Ext.TabPanel({
	id:'main-tabpanel',
	region:'center',
	activeTab:0,
	border:false,
	enableTabScroll:true,
	listeners:{
		'render':{
			fn:function(tabpanel){
				tabpanel.header.addListener(
					'dblclick',
					tabpanel.onHeaderDblClick,
					tabpanel,
					{headerid:tabpanel.header.id}
				);
			},
			single:true
		},
		'tabchange':function(tabpanel,tab){
			tabpanel.updateMenuBar();
		}
	}
});

Monoql.Layout.Center.Center.onHeaderDblClick = function(e,target,options){
	if (target.id){
		Ext.getCmp('main-tabpanel').showQueryConnectionWindow();
	}
};

Monoql.Layout.Center.Center.tabs = {
	queryTabs:new Ext.util.MixedCollection(),
	dataTabs:new Ext.util.MixedCollection(),
	tableDesignTabs:new Ext.util.MixedCollection()
};

Monoql.Layout.Center.Center.queryTabActive = function(){
	return (this.getActiveTab().xtype=='monoql-querytab');
};

Monoql.Layout.Center.Center.addQueryTab = function(dataconnection,queryText){
	var lastQueryTabNum = this.tabs.queryTabs.last() ? (this.tabs.queryTabs.last().num || 0) : 0;
	var queryTabNum = lastQueryTabNum + 1;
	var queryTabCount = this.tabs.queryTabs.getCount();
	var queryTabId = 'QueryTab-' + queryTabNum;
	var queryFormId = 'QueryForm-' + queryTabNum;
	var queryFieldId = 'QueryField-' + queryTabNum;
	var resultTabSetId = 'ResultTabSet-' + queryTabNum;
	var databaseTitle = dataconnection.database ? dataconnection.database + '-': '';
	var queryText = queryText || '';
	
	var resultTabSet = new Monoql.TabSet.ResultTabSet({
		id:resultTabSetId,
		baseParams:Ext.apply({},dataconnection)
	});
	var resultTab = resultTabSet.getResultTab();
	var messageTab = resultTabSet.getMessageTab();

	var queryTabConfig = {
		xtype:'monoql-querytab',
		layout:'border',
		id:queryTabId,
		title:dataconnection.name + '-' + String.leftPad(queryTabNum,2,'0'),
		closable:true,
		num:queryTabNum,
		items:[{
			region:'north',
			xtype:'monoql-queryform',
			id:queryFormId,
			baseParams:Ext.apply({},dataconnection),
			queryFieldId:queryFieldId,
			queryText:queryText,
			height:200,
			split:true,
			border:false
		},{
			region:'center',
			layout:'fit',
			border:false,
			items:[resultTabSet]
		}]
	};
	var queryTab = new Monoql.Tab.QueryTab(queryTabConfig);
	queryTab.resultTabSet = resultTabSet;
	queryTab.dataconnection = dataconnection;
	
	this.add(queryTab);
	this.activate(queryTab);
	
	this.tabs.queryTabs.add({
		id:queryTabId,
		num:queryTabNum
	});
};

Monoql.Layout.Center.Center.addDataTab= function(dataconnection){
	var lastDataTabNum = this.tabs.dataTabs.last() ? (this.tabs.dataTabs.last().num || 0) : 0;
	var dataTabNum = lastDataTabNum + 1;
	var dataTabCount = this.tabs.dataTabs.getCount();
	var dataTabId = 'DataTab-' + dataconnection.id + '-' + dataconnection.table;
	if (this.findById(dataTabId)){
		this.activate(dataTabId);
		return;
	}
	var dataGridContainer = new Ext.Container({
		region:'center',
		layout:'fit',
		border:false,
		bodyStyle:'border-top-width:1px;',
		id:dataTabId + '-DataGridContainer'
	});
	var insertGridContainer = new Ext.Container({
		region:'north',
		layout:'fit',
		border:false,
		height:46,
		id:dataTabId + '-InsertGridContainer'
	});
	var dataTabConfig = {
		xtype:'monoql-datatab',
		layout:'border',
		id:dataTabId,
		baseParams:Ext.apply({},dataconnection),
		title:dataconnection.table,
		closable:true,
		items:[dataGridContainer]
	};
	var dataTab = new Monoql.Tab.DataTab(dataTabConfig);
	dataTab.insertGridContainer = insertGridContainer;
	dataTab.dataGridContainer = dataGridContainer;
	dataTab.dataconnection = dataconnection;
	
	dataTab.addListener('activate',dataTab.onActivate,dataTab,{single:true});
	
	this.add(dataTab);
	this.activate(dataTab);
	
	this.tabs.dataTabs.add({
		id:dataTabId,
		num:dataTabNum
	});
};

Monoql.Layout.Center.Center.executeActiveQuery = function(){
	var activeTab = this.getActiveTab();
	if (this.queryTabActive()){
		var queryField = activeTab.queryField;
		var queryText = queryField.getValue();
		if (queryText.trim().length>0)
		{
			queryField.executeQuery.call(queryField,queryText);
		}
	}
};

Monoql.Layout.Center.Center.saveActiveQuery = function(){
	var activeTab = this.getActiveTab();
	if (this.queryTabActive()){
		var queryField = activeTab.queryField;
		var queryText = queryField.getValue();
		if (queryText.trim().length>0)
		{
			queryField.saveQuery.call(queryField,queryText,activeTab.title,'local');
		}
	}
};

Monoql.Layout.Center.Center.showOpenQueryWindow = function(){
	var window = new Monoql.Window.OpenQueryWindow({
	});
	window.show();
};

Monoql.Layout.Center.Center.cancelActiveQuery = function(){
	var activeTab = this.getActiveTab();
	if (this.queryTabActive()){
		var queryField = activeTab.queryField;
		queryField.cancelQuery.call(queryField);
	}
};

Monoql.Layout.Center.Center.removeConnectionTabs = function(dataconnection){
	this.items.each(function(item,index,length){
		if (item.dataconnection && item.dataconnection.id==dataconnection.id){
			this.remove(item);
		}
	},this);
};

Monoql.Layout.Center.Center.updateMenuBar = function(){
	var queryTabActive = this.queryTabActive();
	Ext.getCmp('Monoql-MenuBar-ExecuteQueryButton').setDisabled(!queryTabActive);
	Ext.getCmp('Monoql-MenuBar-SaveButton').setDisabled(!queryTabActive);
	Ext.getCmp('main-menubar-connection-id').setDisabled(!queryTabActive);
	Ext.getCmp('main-menubar-database-id').setDisabled(!queryTabActive);
	this.updateActiveConnection();
	this.updateActiveDatabase();
};

Monoql.Layout.Center.Center.updateConnectionStore = function(){
	var data = [];
	var dataconnection = this.getActiveTab().dataconnection;
	for (i=0; i<Monoql.dataconnections.getCount(); i++){
		data[data.length] = Monoql.dataconnections.get(i);
	}
	var combo = Ext.getCmp('main-menubar-connection-id');
	var store = combo.getStore();
	store.loadData({data:data});
};

Monoql.Layout.Center.Center.updateActiveConnection = function(){
	this.updateConnectionStore();
	var combo = Ext.getCmp('main-menubar-connection-id');
	var dataconnection = this.getActiveTab().dataconnection;
	combo.clearValue();
	if (this.queryTabActive()){
		combo.setValue(dataconnection.id);
	}
};

Monoql.Layout.Center.Center.updateDatabaseStore = function(){
	var data = [];
	var dataconnection = this.getActiveTab().dataconnection;
	if (dataconnection){
		for (i=0; i<dataconnection.databases.length; i++){
			data[data.length] = {name:dataconnection.databases[i]};
		}
	}
	var combo = Ext.getCmp('main-menubar-database-id');
	var store = combo.getStore();
	store.loadData({data:data});
};

Monoql.Layout.Center.Center.updateActiveDatabase = function(){
	this.updateDatabaseStore();
	var combo = Ext.getCmp('main-menubar-database-id');
	var dataconnection = this.getActiveTab().dataconnection;
	combo.clearValue();
	if (this.queryTabActive()){
		combo.setValue(dataconnection.database);
	}
};

Monoql.Layout.Center.Center.showQueryConnectionWindow = function(){
	var window = new Monoql.Window.QueryConnectionWindow({
	});
	window.show();
};

Ext.getCmp('main-tabpanel').add({
	title:'MonoQL',
	bodyStyle:'border-left-width:1px;border-bottom-width:1px;'
});Ext.namespace('Monoql.Layout.Center.West');
Monoql.Layout.Center.West = new Ext.Panel({
	region:'west',
	collapsible:true,
	layout:'fit',
	split:true,
	width:250,
	border:true,
	bodyStyle:'border-bottom:none;border-left:none;',
	headerStyle:'border-top:none;border-left:none;',
	title:'Data Connections',
	items:[new Monoql.Tree.DatabaseTree({
		id:'main-treepanel',
		headerStyle:'border-top:none;',
		root:new Monoql.TreeNode.GroupNode({
			text:'Connections',
			id:'GroupNode-Connections',
			nodeType:'GroupNode',
			dataconnection:{},
			expanded:true
		}),
		loader:new Ext.tree.TreeLoader({
			url:'/index?ajax&f=get_child_nodes'
		})
	})]
});Ext.namespace('Monoql.Layout.Center');
Monoql.Layout.Center = new Ext.Panel({
	region:'center',
	id:'region-application',
	xtype:'panel',
	layout:'border',
	border:false,
	items:[
	   Monoql.Layout.Center.West,
	   Monoql.Layout.Center.Center
	]
});Ext.namespace('Monoql.Layout.South');
Monoql.Layout.South = new Ext.Panel({
	region:'south',
	layout:'fit',
	height:100,
	border:true,
	split:true,
	collapsible:true,
	collapsed:false,
	title:'Query Log',
	items:[{
		xtype:'panel',
		border:false
	}]
});	
	var map = new Ext.KeyMap(document,[{
	    key:[Ext.EventObject.F9],
	    fn:function(keycode,e){
			Ext.getCmp('main-tabpanel').executeActiveQuery();
		},
		shift:false,
		ctrl:false,
		alt:false,
	    scope:Ext.getCmp('main-tabpanel'),
	    stopEvent:true
	},{
	    key:[Ext.EventObject.T],
	    fn:function(keycode,e){
			Ext.getCmp('main-tabpanel').showQueryConnectionWindow();
		},
		shift:false,
		ctrl:true,
		alt:false,
	    scope:Ext.getCmp('main-tabpanel'),
	    stopEvent:true
	},{
	    key:[Ext.EventObject.O],
	    fn:function(keycode,e){
			Ext.getCmp('main-tabpanel').showOpenQueryWindow();
		},
		shift:false,
		ctrl:true,
		alt:false,
	    scope:Ext.getCmp('main-tabpanel'),
	    stopEvent:true
	},{
	    key:[Ext.EventObject.S],
	    fn:function(keycode,e){
			Ext.getCmp('main-tabpanel').saveActiveQuery();
		},
		shift:false,
		ctrl:true,
		alt:false,
	    scope:Ext.getCmp('main-tabpanel'),
	    stopEvent:true
	}]);
});