 jQuery.fn.splitter = function(opts){
    opts = jQuery.extend({
        type: 'v',
        activeClass: 'active',
        pxPerKey: 5,
        tabIndex: 0,
        accessKey: ''
    },{
        v: {
            keyGrowA: 39,
            keyShrinkA: 37,
            cursor: "e-resize",
            splitbarClass: "vsplitbar",
            eventPos: "pageX", set: "left",
            adjust: "width",  offsetAdjust: "offsetWidth",  adjSide1: "Left", adjSide2: "Right",
            fixed:  "height", offsetFixed:  "offsetHeight", fixSide1: "Top",  fixSide2: "Bottom"
        },
        h: {
            keyGrowA: 40,
            keyShrinkA: 38,
            cursor: "n-resize",
            splitbarClass: "hsplitbar",
            eventPos: "pageY", set: "top",
            adjust: "height", offsetAdjust: "offsetHeight", adjSide1: "Top",  adjSide2: "Bottom",
            fixed:  "width",  offsetFixed:  "offsetWidth",  fixSide1: "Left", fixSide2: "Right"
        }
    }[((opts||{}).type||'v').charAt(0).toLowerCase()], opts||{});

    return this.each(function() {
        function startSplit(e) {
            splitbar.addClass(opts.activeClass);
            if ( e.type == "mousedown" ) {
                paneA._posAdjust = paneA[0][opts.offsetAdjust] - e[opts.eventPos];
                jQuery(document)
                    .bind("mousemove", doSplitMouse)
                    .bind("mouseup", endSplit);
            }
            return true;
        }
        function doSplitKey(e) {
            var key = e.which || e.keyCode;
            var dir = key==opts.keyGrowA? 1 : key==opts.keyShrinkA? -1 : 0;
            if ( dir )
                moveSplitter(paneA[0][opts.offsetAdjust]+dir*opts.pxPerKey);
            return true;
        }
        function doSplitMouse(e) {
            moveSplitter(paneA._posAdjust+e[opts.eventPos]);
        }
        function endSplit(e) {
            splitbar.removeClass(opts.activeClass);
            jQuery(document)
                .unbind("mousemove", doSplitMouse)
                .unbind("mouseup", endSplit);
        }
        function moveSplitter(np) {
            np = Math.max(paneA._min+paneA._padAdjust, group._adjust - (paneB._max||9999), 16,
                Math.min(np, paneA._max||9999, group._adjust - splitbar._adjust -
                    Math.max(paneB._min+paneB._padAdjust, 16)));

            splitbar.css(opts.set, np+"px");
            paneA.css(opts.adjust, np-paneA._padAdjust+"px");
            paneB.css(opts.set, np+splitbar._adjust+"px")
                .css(opts.adjust, group._adjust-splitbar._adjust-paneB._padAdjust-np+"px");

            if ( !jQuery.browser.msie ) {
                paneA.trigger("resize");
                paneB.trigger("resize");
            }
        }
        function cssCache(jq, n, pf, m1, m2) {
            jq[n] = jQuery.boxModel? (parseInt(jq.css(pf+m1))||0) + (parseInt(jq.css(pf+m2))||0) : 0;
        }
        function optCache(jq, pane) {
            jq._min = Math.max(0, opts["min"+pane] || parseInt(jq.css("min-"+opts.adjust)) || 0);
            jq._max = Math.max(0, opts["max"+pane] || parseInt(jq.css("max-"+opts.adjust)) || 0);
        }

        var group = jQuery(this).css({position: "relative"});
        var divs = jQuery(">div", group).css({
            position: "absolute",
            margin: "0",
            border: "0",
            "-moz-user-focus": "ignore"
        });
        var paneA = jQuery(divs[0]);
        var paneB = jQuery(divs[1]);

        var focuser = jQuery('<a href="javascript:void(0)"></a>')
            .bind("focus", startSplit).bind("keydown", doSplitKey).bind("blur", endSplit)
            .attr({accessKey: opts.accessKey, tabIndex: opts.tabIndex});

        var splitbar = jQuery('<div></div>')
            .insertAfter(paneA).append(focuser)
            .attr({"class": opts.splitbarClass, unselectable: "on"})
            .css({position: "absolute", "-khtml-user-select": "none",
                "-moz-user-select": "none", "user-select": "none"})
            .bind("mousedown", startSplit);
        if ( /^(auto|default)$/.test(splitbar.css("cursor") || "auto") )
            splitbar.css("cursor", opts.cursor);

        splitbar._adjust = splitbar[0][opts.offsetAdjust];
        cssCache(group, "_borderAdjust", "border", opts.adjSide1+"Width", opts.adjSide2+"Width");
        cssCache(group, "_borderFixed",  "border", opts.fixSide1+"Width", opts.fixSide2+"Width");
        cssCache(paneA, "_padAdjust", "padding", opts.adjSide1, opts.adjSide2);
        cssCache(paneA, "_padFixed",  "padding", opts.fixSide1, opts.fixSide2);
        cssCache(paneB, "_padAdjust", "padding", opts.adjSide1, opts.adjSide2);
        cssCache(paneB, "_padFixed",  "padding", opts.fixSide1, opts.fixSide2);
        optCache(paneA, 'A');
        optCache(paneB, 'B');

        paneA._init = (opts.initA==true? parseInt(jQuery.curCSS(paneA[0],opts.adjust)) : opts.initA) || 0;
        paneB._init = (opts.initB==true? parseInt(jQuery.curCSS(paneB[0],opts.adjust)) : opts.initB) || 0;
        if ( paneB._init )
            paneB._init = group[0][opts.offsetAdjust] - group._borderAdjust - paneB._init - splitbar._adjust;

        group.bind("resize", function(e,size){
            group._fixed  = group[0][opts.offsetFixed]  - group._borderFixed;
            group._adjust = group[0][opts.offsetAdjust] - group._borderAdjust;

            if ( group._fixed <= 0 || group._adjust <= 0 ) return;

            paneA.css(opts.fixed, group._fixed-paneA._padFixed+"px");
            paneB.css(opts.fixed, group._fixed-paneB._padFixed+"px");
            splitbar.css(opts.fixed, group._fixed+"px");

            moveSplitter(size || (!opts.initB? paneA[0][opts.offsetAdjust] :
                group._adjust-paneB[0][opts.offsetAdjust]-splitbar._adjust));
        }).trigger("resize" , [paneA._init || paneB._init ||
            Math.round((group[0][opts.offsetAdjust] - group._borderAdjust - splitbar._adjust)/2)]);
    });
};

