    /*!
     * Ext JS Library 3.3.1
     * Copyright(c) 2006-2010 Sencha Inc.
     * licensing@sencha.com
     * http://www.sencha.com/license
     */
    /**
     * @class Ext.ux.ToolbarReorderer
     * @extends Ext.ux.Reorderer
     * Plugin which can be attached to any Ext.Toolbar instance. Provides ability to reorder toolbar items
     * with drag and drop. Example:
     *
     * new Ext.Toolbar({
     *     plugins: [
     *         new Ext.ux.ToolbarReorderer({
     *             defaultReorderable: true
     *         })
     *     ],
     *     items: [
     *       {text: 'Button 1', reorderable: false},
     *       {text: 'Button 2'},
     *       {text: 'Button 3'}
     *     ]
     * });
     *
     * In the example above, buttons 2 and 3 will be reorderable via drag and drop. An event named 'reordered'
     * is added to the Toolbar, and is fired whenever a reorder has been completed.
     */
    Ext.ux.ToolbarReorderer = Ext.extend(Ext.ux.Reorderer, {
        /**
         * Initializes the plugin, decorates the toolbar with additional functionality
         */
        init: function(toolbar) {
            /**
             * This is used to store the correct x value of each button in the array. We need to use this
             * instead of the button's reported x co-ordinate because the buttons are animated when they move -
             * if another onDrag is fired while the button is still moving, the comparison x value will be incorrect
             */
            this.buttonXCache = {};

            toolbar.on({
                scope: this,
                add  : function(toolbar, item) {
                    this.createIfReorderable(item);
                }
            });

            //super sets a reference to the toolbar in this.target
            Ext.ux.ToolbarReorderer.superclass.init.apply(this, arguments);
        },

        /**
         * Sets up the given Toolbar item as a draggable
         * @param {Mixed} button The item to make draggable (usually an Ext.Button instance)
         */
        createItemDD: function(button) {
            if (button.dd != undefined) {
                return;
            }

            var el   = button.getEl(),
                id   = el.id,
                tbar = this.target,
                me   = this;

            button.dd = new Ext.dd.DD(el, undefined, {
                isTarget: false
            });

            //if a button has a menu, it is disabled while dragging with this function
            var menuDisabler = function() {
                return false;
            };

            Ext.apply(button.dd, {
                b4StartDrag: function() {
                    this.startPosition = el.getXY();

                    //bump up the z index of the button being dragged but keep a reference to the original
                    this.startZIndex = el.getStyle('zIndex');
                    el.setStyle('zIndex', 10000);

                    button.suspendEvents();
                    if (button.menu) {
                        button.menu.on('beforeshow', menuDisabler, me);
                    }
                },

                startDrag: function() {
                    this.constrainTo(tbar.getEl());
                    this.setYConstraint(0, 0, 0);
                },

                onDrag: function(e) {
                    //calculate the button's index within the toolbar and its current midpoint
                    var buttonX  = el.getXY()[0],
                        deltaX   = buttonX - this.startPosition[0],
                        items    = tbar.items.items,
                        oldIndex = items.indexOf(button),
                        newIndex;

                    //find which item in the toolbar the midpoint is currently over
                    for (var index = 0; index < items.length; index++) {
                        var item = items[index];

                        if (item.reorderable && item.id != button.id) {
                            //find the midpoint of the button
                            var box        = item.getEl().getBox(),
                                midpoint   = (me.buttonXCache[item.id] || box.x) + (box.width / 2),
                                movedLeft  = oldIndex > index && deltaX < 0 && buttonX < midpoint,
                                movedRight = oldIndex < index && deltaX > 0 && (buttonX + el.getWidth()) > midpoint;

                            if (movedLeft || movedRight) {
                                me[movedLeft ? 'onMovedLeft' : 'onMovedRight'](button, index, oldIndex);
                                break;
                            }
                        }
                    }
                },

                /**
                 * After the drag has been completed, make sure the button being dragged makes it back to
                 * the correct location and resets its z index
                 */
                endDrag: function() {
                    //we need to update the cache here for cases where the button was dragged but its
                    //position in the toolbar did not change
                    me.updateButtonXCache();

                    el.moveTo(me.buttonXCache[button.id], el.getY(), {
                        duration: me.animationDuration,
                        scope   : this,
                        callback: function() {
                            button.resumeEvents();
                            if (button.menu) {
                                button.menu.un('beforeshow', menuDisabler, me);
                            }

                            tbar.fireEvent('reordered', button, tbar);
                        }
                    });

                    el.setStyle('zIndex', this.startZIndex);
                }
            });
        },

        onMovedLeft: function(item, newIndex, oldIndex) {
            var tbar  = this.target,
                items = tbar.items.items;

            if (newIndex != undefined && newIndex != oldIndex) {
                //move the button currently under drag to its new location
                tbar.remove(item, false);
                tbar.insert(newIndex, item);

                //set the correct x location of each item in the toolbar
                this.updateButtonXCache();
                for (var index = 0; index < items.length; index++) {
                    var obj  = items[index],
                        newX = this.buttonXCache[obj.id];

                    if (item == obj) {
                        item.dd.startPosition[0] = newX;
                    } else {
                        var el = obj.getEl();

                        el.moveTo(newX, el.getY(), {duration: this.animationDuration});
                    }
                }
            }
        },

        onMovedRight: function(item, newIndex, oldIndex) {
            this.onMovedLeft.apply(this, arguments);
        },

        /**
         * @private
         * Updates the internal cache of button X locations.
         */
        updateButtonXCache: function() {
            var tbar   = this.target,
                items  = tbar.items,
                totalX = tbar.getEl().getBox(true).x;

            items.each(function(item) {
                this.buttonXCache[item.id] = totalX;

                totalX += item.getEl().getWidth();
            }, this);
        }
    });