Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

if(!dojo._hasResource["dijit.layout.SplitContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.layout.SplitContainer"] = true;
dojo.provide("dijit.layout.SplitContainer");

//
// FIXME: make it prettier
// FIXME: active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case)
//

dojo.require("dojo.cookie");
dojo.require("dijit.layout._LayoutWidget");

dojo.declare("dijit.layout.SplitContainer",
        dijit.layout._LayoutWidget,
        {
        // summary: 
        //      A Container widget with sizing handles in-between each child
        // description:
        //              Contains multiple children widgets, all of which are displayed side by side
        //              (either horizontally or vertically); there's a bar between each of the children,
        //              and you can adjust the relative size of each child by dragging the bars.
        //
        //              You must specify a size (width and height) for the SplitContainer.
        //
        // activeSizing: Boolean
        //              If true, the children's size changes as you drag the bar;
        //              otherwise, the sizes don't change until you drop the bar (by mouse-up)
        activeSizing: false,

        // sizerWidth: Integer
        //              Size in pixels of the bar between each child
        sizerWidth: 7, // FIXME: this should be a CSS attribute (at 7 because css wants it to be 7 until we fix to css)

        // orientation: String
        //              either 'horizontal' or vertical; indicates whether the children are
        //              arranged side-by-side or up/down.
        orientation: 'horizontal',

        // persist: Boolean
        //              Save splitter positions in a cookie
        persist: true,

        postMixInProperties: function(){
                this.inherited("postMixInProperties",arguments);
                this.isHorizontal = (this.orientation == 'horizontal');
        },

        postCreate: function(){
                this.inherited("postCreate",arguments);
                this.sizers = [];
                dojo.addClass(this.domNode, "dijitSplitContainer");
                // overflow has to be explicitly hidden for splitContainers using gekko (trac #1435)
                // to keep other combined css classes from inadvertantly making the overflow visible
                if(dojo.isMozilla){
                        this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work
                }

                // create the fake dragger
                if(typeof this.sizerWidth == "object"){
                        try{ //FIXME: do this without a try/catch
                                this.sizerWidth = parseInt(this.sizerWidth.toString());
                        }catch(e){ this.sizerWidth = 7; }
                }
                var sizer = this.virtualSizer = document.createElement('div');
                sizer.style.position = 'relative';

                // #1681: work around the dreaded 'quirky percentages in IE' layout bug
                // If the splitcontainer's dimensions are specified in percentages, it
                // will be resized when the virtualsizer is displayed in _showSizingLine
                // (typically expanding its bounds unnecessarily). This happens because
                // we use position: relative for .dijitSplitContainer.
                // The workaround: instead of changing the display style attribute,
                // switch to changing the zIndex (bring to front/move to back)

                sizer.style.zIndex = 10;
                sizer.className = this.isHorizontal ? 'dijitSplitContainerVirtualSizerH' : 'dijitSplitContainerVirtualSizerV';
                this.domNode.appendChild(sizer);
                dojo.setSelectable(sizer, false);
        },

        startup: function(){
                if(this._started){ return; }
                dojo.forEach(this.getChildren(), function(child, i, children){
                        // attach the children and create the draggers
                        this._injectChild(child);

                        if(i < children.length-1){
                                this._addSizer();
                        }
                }, this);

                if(this.persist){
                        this._restoreState();
                }
                this.inherited("startup",arguments); 
                this._started = true;
        },

        _injectChild: function(child){
                child.domNode.style.position = "absolute";
                dojo.addClass(child.domNode, "dijitSplitPane");
        },

        _addSizer: function(){
                var i = this.sizers.length;

                // TODO: use a template for this!!!
                var sizer = this.sizers[i] = document.createElement('div');
                sizer.className = this.isHorizontal ? 'dijitSplitContainerSizerH' : 'dijitSplitContainerSizerV';

                // add the thumb div
                var thumb = document.createElement('div');
                thumb.className = 'thumb';
                sizer.appendChild(thumb);

                // FIXME: are you serious? why aren't we using mover start/stop combo?
                var self = this;
                var handler = (function(){ var sizer_i = i; return function(e){ self.beginSizing(e, sizer_i); } })();
                dojo.connect(sizer, "onmousedown", handler);

                this.domNode.appendChild(sizer);
                dojo.setSelectable(sizer, false);
        },

        removeChild: function(widget){
                // summary: Remove sizer, but only if widget is really our child and
                // we have at least one sizer to throw away
                if(this.sizers.length && dojo.indexOf(this.getChildren(), widget) != -1){
                        var i = this.sizers.length - 1;
                        dojo._destroyElement(this.sizers[i]);
                        this.sizers.length--;
                }

                // Remove widget and repaint
                this.inherited("removeChild",arguments); 
                if(this._started){
                        this.layout();
                }
        },

        addChild: function(/*Widget*/ child, /*Integer?*/ insertIndex){
                // summary: Add a child widget to the container
                // child: a widget to add
                // insertIndex: postion in the "stack" to add the child widget
                
                this.inherited("addChild",arguments); 

                if(this._started){
                        // Do the stuff that startup() does for each widget
                        this._injectChild(child);
                        var children = this.getChildren();
                        if(children.length > 1){
                                this._addSizer();
                        }

                        // and then reposition (ie, shrink) every pane to make room for the new guy
                        this.layout();
                }
        },

        layout: function(){
                // summary:
                //              Do layout of panels

                // base class defines this._contentBox on initial creation and also
                // on resize
                this.paneWidth = this._contentBox.w;
                this.paneHeight = this._contentBox.h;

                var children = this.getChildren();
                if(!children.length){ return; }

                //
                // calculate space
                //

                var space = this.isHorizontal ? this.paneWidth : this.paneHeight;
                if(children.length > 1){
                        space -= this.sizerWidth * (children.length - 1);
                }

                //
                // calculate total of SizeShare values
                //
                var outOf = 0;
                dojo.forEach(children, function(child){
                        outOf += child.sizeShare;
                });

                //
                // work out actual pixels per sizeshare unit
                //
                var pixPerUnit = space / outOf;

                //
                // set the SizeActual member of each pane
                //
                var totalSize = 0;
                dojo.forEach(children.slice(0, children.length - 1), function(child){
                        var size = Math.round(pixPerUnit * child.sizeShare);
                        child.sizeActual = size;
                        totalSize += size;
                });

                children[children.length-1].sizeActual = space - totalSize;

                //
                // make sure the sizes are ok
                //
                this._checkSizes();

                //
                // now loop, positioning each pane and letting children resize themselves
                //

                var pos = 0;
                var size = children[0].sizeActual;
                this._movePanel(children[0], pos, size);
                children[0].position = pos;
                pos += size;

                // if we don't have any sizers, our layout method hasn't been called yet
                // so bail until we are called..TODO: REVISIT: need to change the startup
                // algorithm to guaranteed the ordering of calls to layout method
                if(!this.sizers){
                        return;
                }

                dojo.some(children.slice(1), function(child, i){
                        // error-checking
                        if(!this.sizers[i]){
                                return true;
                        }
                        // first we position the sizing handle before this pane
                        this._moveSlider(this.sizers[i], pos, this.sizerWidth);
                        this.sizers[i].position = pos;
                        pos += this.sizerWidth;

                        size = child.sizeActual;
                        this._movePanel(child, pos, size);
                        child.position = pos;
                        pos += size;
                }, this);
        },

        _movePanel: function(panel, pos, size){
                if(this.isHorizontal){
                        panel.domNode.style.left = pos + 'px';  // TODO: resize() takes l and t parameters too, don't need to set manually
                        panel.domNode.style.top = 0;
                        var box = {w: size, h: this.paneHeight};
                        if(panel.resize){
                                panel.resize(box);
                        }else{
                                dojo.marginBox(panel.domNode, box);
                        }
                }else{
                        panel.domNode.style.left = 0;   // TODO: resize() takes l and t parameters too, don't need to set manually
                        panel.domNode.style.top = pos + 'px';
                        var box = {w: this.paneWidth, h: size};
                        if(panel.resize){
                                panel.resize(box);
                        }else{
                                dojo.marginBox(panel.domNode, box);
                        }
                }
        },

        _moveSlider: function(slider, pos, size){
                if(this.isHorizontal){
                        slider.style.left = pos + 'px';
                        slider.style.top = 0;
                        dojo.marginBox(slider, { w: size, h: this.paneHeight });
                }else{
                        slider.style.left = 0;
                        slider.style.top = pos + 'px';
                        dojo.marginBox(slider, { w: this.paneWidth, h: size });
                }
        },

        _growPane: function(growth, pane){
                if(growth > 0){
                        if(pane.sizeActual > pane.sizeMin){
                                if((pane.sizeActual - pane.sizeMin) > growth){

                                        // stick all the growth in this pane
                                        pane.sizeActual = pane.sizeActual - growth;
                                        growth = 0;
                                }else{
                                        // put as much growth in here as we can
                                        growth -= pane.sizeActual - pane.sizeMin;
                                        pane.sizeActual = pane.sizeMin;
                                }
                        }
                }
                return growth;
        },

        _checkSizes: function(){

                var totalMinSize = 0;
                var totalSize = 0;
                var children = this.getChildren();

                dojo.forEach(children, function(child){
                        totalSize += child.sizeActual;
                        totalMinSize += child.sizeMin;
                });

                // only make adjustments if we have enough space for all the minimums

                if(totalMinSize <= totalSize){

                        var growth = 0;

                        dojo.forEach(children, function(child){
                                if(child.sizeActual < child.sizeMin){
                                        growth += child.sizeMin - child.sizeActual;
                                        child.sizeActual = child.sizeMin;
                                }
                        });

                        if(growth > 0){
                                var list = this.isDraggingLeft ? children.reverse() : children;
                                dojo.forEach(list, function(child){
                                        growth = this._growPane(growth, child);
                                }, this);
                        }
                }else{
                        dojo.forEach(children, function(child){
                                child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize));
                        });
                }
        },

        beginSizing: function(e, i){
                var children = this.getChildren();
                this.paneBefore = children[i];
                this.paneAfter = children[i+1];

                this.isSizing = true;
                this.sizingSplitter = this.sizers[i];

                if(!this.cover){
                        this.cover = dojo.doc.createElement('div');
                        this.domNode.appendChild(this.cover);
                        var s = this.cover.style;
                        s.position = 'absolute';
                        s.zIndex = 1;
                        s.top = 0;
                        s.left = 0;
                        s.width = "100%";
                        s.height = "100%";
                }else{
                        this.cover.style.zIndex = 1;
                }
                this.sizingSplitter.style.zIndex = 2;

                // TODO: REVISIT - we want MARGIN_BOX and core hasn't exposed that yet (but can't we use it anyway if we pay attention? we do elsewhere.)
                this.originPos = dojo.coords(children[0].domNode, true);
                if(this.isHorizontal){
                        var client = (e.layerX ? e.layerX : e.offsetX);
                        var screen = e.pageX;
                        this.originPos = this.originPos.x;
                }else{
                        var client = (e.layerY ? e.layerY : e.offsetY);
                        var screen = e.pageY;
                        this.originPos = this.originPos.y;
                }
                this.startPoint = this.lastPoint = screen;
                this.screenToClientOffset = screen - client;
                this.dragOffset = this.lastPoint - this.paneBefore.sizeActual - this.originPos - this.paneBefore.position;

                if(!this.activeSizing){
                        this._showSizingLine();
                }

                //                                      
                // attach mouse events
                //
                this._connects = [];
                this._connects.push(dojo.connect(document.documentElement, "onmousemove", this, "changeSizing"));
                this._connects.push(dojo.connect(document.documentElement, "onmouseup", this, "endSizing"));

                dojo.stopEvent(e);
        },

        changeSizing: function(e){
                if(!this.isSizing){ return; }
                this.lastPoint = this.isHorizontal ? e.pageX : e.pageY;
                this.movePoint();
                if(this.activeSizing){
                        this._updateSize();
                }else{
                        this._moveSizingLine();
                }
                dojo.stopEvent(e);
        },

        endSizing: function(e){
                if(!this.isSizing){ return; }
                if(this.cover){
                        this.cover.style.zIndex = -1;
                }
                if(!this.activeSizing){
                        this._hideSizingLine();
                }

                this._updateSize();

                this.isSizing = false;

                if(this.persist){
                        this._saveState(this);
                }

                dojo.forEach(this._connects,dojo.disconnect); 
        },

        movePoint: function(){

                // make sure lastPoint is a legal point to drag to
                var p = this.lastPoint - this.screenToClientOffset;

                var a = p - this.dragOffset;
                a = this.legaliseSplitPoint(a);
                p = a + this.dragOffset;

                this.lastPoint = p + this.screenToClientOffset;
        },

        legaliseSplitPoint: function(a){

                a += this.sizingSplitter.position;

                this.isDraggingLeft = !!(a > 0);

                if(!this.activeSizing){
                        var min = this.paneBefore.position + this.paneBefore.sizeMin;
                        if(a < min){
                                a = min;
                        }

                        var max = this.paneAfter.position + (this.paneAfter.sizeActual - (this.sizerWidth + this.paneAfter.sizeMin));
                        if(a > max){
                                a = max;
                        }
                }

                a -= this.sizingSplitter.position;

                this._checkSizes();

                return a;
        },

        _updateSize: function(){
        //FIXME: sometimes this.lastPoint is NaN
                var pos = this.lastPoint - this.dragOffset - this.originPos;

                var start_region = this.paneBefore.position;
                var end_region   = this.paneAfter.position + this.paneAfter.sizeActual;

                this.paneBefore.sizeActual = pos - start_region;
                this.paneAfter.position = pos + this.sizerWidth;
                this.paneAfter.sizeActual  = end_region - this.paneAfter.position;

                dojo.forEach(this.getChildren(), function(child){
                        child.sizeShare = child.sizeActual;
                });

                if(this._started){
                        this.layout();
                }
        },

        _showSizingLine: function(){

                this._moveSizingLine();

                dojo.marginBox(this.virtualSizer,
                        this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth });

                this.virtualSizer.style.display = 'block';
        },

        _hideSizingLine: function(){
                this.virtualSizer.style.display = 'none';
        },

        _moveSizingLine: function(){
                var pos = (this.lastPoint - this.startPoint) + this.sizingSplitter.position;
                dojo.style(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px");
                // this.virtualSizer.style[ this.isHorizontal ? "left" : "top" ] = pos + 'px'; // FIXME: remove this line if the previous is better
        },

        _getCookieName: function(i){
                return this.id + "_" + i;
        },

        _restoreState: function(){
                dojo.forEach(this.getChildren(), function(child, i){
                        var cookieName = this._getCookieName(i);
                        var cookieValue = dojo.cookie(cookieName);
                        if(cookieValue){
                                var pos = parseInt(cookieValue);
                                if(typeof pos == "number"){
                                        child.sizeShare = pos;
                                }
                        }
                }, this);
        },

        _saveState: function(){
                dojo.forEach(this.getChildren(), function(child, i){
                        dojo.cookie(this._getCookieName(i), child.sizeShare);
                }, this);
        }
});

// These arguments can be specified for the children of a SplitContainer.
// Since any widget can be specified as a SplitContainer child, mix them
// into the base widget class.  (This is a hack, but it's effective.)
dojo.extend(dijit._Widget, {
        // sizeMin: Integer
        //      Minimum size (width or height) of a child of a SplitContainer.
        //      The value is relative to other children's sizeShare properties.
        sizeMin: 10,

        // sizeShare: Integer
        //      Size (width or height) of a child of a SplitContainer.
        //      The value is relative to other children's sizeShare properties.
        //      For example, if there are two children and each has sizeShare=10, then
        //      each takes up 50% of the available space.
        sizeShare: 10
});

}