////////////////////////////////////////////////////////////////////////////
// GLOBALS
////////////////////////////////////////////////////////////////////////////
// images
var ROOT_PATH              = "";
var IMG_TRANCPARENT_SRC            = (ROOT_PATH + "img/transparent.gif");

// key codes
var KEY_CODE_BACKSPACE             = 8;
var KEY_CODE_TAB                   = 9;
var KEY_CODE_ENTER                 = 13;
var KEY_CODE_ESCAPE                = 27;
var KEY_CODE_SPACE                 = 32;
var KEY_CODE_PAGEUP                = 33;
var KEY_CODE_PAGEDOWN              = 34;
var KEY_CODE_END                   = 35;
var KEY_CODE_HOME                  = 36;

var KEY_CODE_LEFT_ARROW            = 37;
var KEY_CODE_UP_ARROW              = 38;
var KEY_CODE_RIGHT_ARROW           = 39;
var KEY_CODE_DOWN_ARROW            = 40;

var KEY_CODE_INSERT                = 45;
var KEY_CODE_DELETE                = 46;

var KEY_CODE_F1                    = 112;
var KEY_CODE_F2                    = 113;
var KEY_CODE_F3                    = 114;
var KEY_CODE_F4                    = 115;
var KEY_CODE_F5                    = 116;
var KEY_CODE_F6                    = 117;
var KEY_CODE_F7                    = 118;
var KEY_CODE_F8                    = 119;
var KEY_CODE_F9                    = 120;
var KEY_CODE_F10                   = 121;
var KEY_CODE_F11                   = 122;
var KEY_CODE_F12                   = 123;

// Dlg
var IDOK                           = 1;
var IDCANCEL                       = 2;
var IDCLOSE                        = 3;
var IDMINIMIZE                     = 4;

var BTN_OK                         = 0x01;
var BTN_CANCEL                     = 0x02;
var BTN_YES                        = 0x04;
var BTN_NO                         = 0x08;
    // Custom
var IDREFRESH                      = 100;
var IDREFRESHING                   = 101;
var IDFIND                         = 102;
////////////////////////////////////////////////////////////////////////////
// Controls Notification
////////////////////////////////////////////////////////////////////////////

// Dlg
var DLGN_FIRST                  = 10;
var DLGN_BTN_CLICKED            = DLGN_FIRST + 1;

// AP
var APN_FIRST                   = 50;
var APN_POSCAHNGES              = APN_FIRST + 1;
var APN_STATE_CHANGED           = APN_FIRST + 2;

// Img Button
var BTN_FIRST                   = 100;
var BTN_CLICKED                 = BTN_FIRST + 1;
var BTN_KEYPRESSED              = BTN_FIRST + 2;
//var BTN_CHECKED                 = BTN_FIRST + 3;

// ToolBar
var TBN_FIRST                   = 200;
var TBN_BUTTON_INSERTED         = TBN_FIRST + 1;
var TBN_BUTTON_DELETED          = TBN_FIRST + 2;
var TBN_BUTTON_CLICKED          = TBN_FIRST + 3;
//var TBN_BUTTON_STATE_CHECKED    = TBN_FIRST + 5;

// Splitter
var SPLTN_FIRST                 = 300;
var SPLTN_POS_CHANGED           = SPLTN_FIRST + 1;

// WndBorder
var WNDBN_FIRST                 = 400;
var WNDBN_BUTTON_INSERTED       = WNDBN_FIRST + 1;
var WNDBN_BUTTON_DELETED        = WNDBN_FIRST + 2;

// ImgInput
var IMGINN_FIRST                = 500;
var IMGINN_ONBTNCLICKED         = IMGINN_FIRST + 1;
var IMGINN_ONCHANGED            = IMGINN_FIRST + 2;
var IMGINN_ONKEYPRESSED         = IMGINN_FIRST + 3;

// TextArea
var TEXTAREAN_FIRST             = 550;
var TEXTAREAN_ONCHANGED         = TEXTAREAN_FIRST + 1;

// TreeView
var TVN_FIRST                   = 600;
var TVN_ITEM_INSERTED           = TVN_FIRST + 1;
var TVN_ITEM_DELETING           = TVN_FIRST + 2;
var TVN_ITEM_DELETED            = TVN_FIRST + 3;
var TVN_BEFORE_UPDATE_TREE      = TVN_FIRST + 4;
var TVN_AFTER_UPDATE_TREE       = TVN_FIRST + 5;
var TVN_FILTERED                = TVN_FIRST + 6;
var TVN_ITEM_COLLAPSED          = TVN_FIRST + 11;
var TVN_ITEM_EXPANDED           = TVN_FIRST + 12;
// TreeView - Click
var TVN_ITEM_CLICKED            = TVN_FIRST + 21;
var TVN_ITEMTEXT_CLICKED        = TVN_FIRST + 22;
var TVN_ITEMICON_CLICKED        = TVN_FIRST + 23;
var TVN_ITEMCHECKBOX_CLICKED    = TVN_FIRST + 24;
var TVN_ITEMMANAGEICON_CLICKED  = TVN_FIRST + 25;
// TreeView - Dbl Click
var TVN_ITEM_DBLCLICKED         = TVN_FIRST + 31;
var TVN_ITEMTEXT_DBLCLICKED     = TVN_FIRST + 32;
var TVN_ITEMICON_DBLCLICKED     = TVN_FIRST + 33;
var TVN_ITEMCHECKBOX_DBLCLICKED = TVN_FIRST + 34;
var TVN_ITEMMANAGEICON_DBLCLICKED = TVN_FIRST + 35;
// TreeView - Context Menu
var TVN_ONCONTEXTMENU           = TVN_FIRST + 41;
var TVN_ITEM_ONCONTEXTMENU      = TVN_FIRST + 42;
var TVN_ITEMTEXT_ONCONTEXTMENU  = TVN_FIRST + 43;
var TVN_ITEMICON_ONCONTEXTMENU  = TVN_FIRST + 44;
var TVN_ITEMCHECKBOX_ONCONTEXTMENU = TVN_FIRST + 45;
var TVN_ITEMMANAGEICON_ONCONTEXTMENU = TVN_FIRST + 46;
// TreeView - Drag Over
var TVN_DRAGOVER                = TVN_FIRST + 51;
var TVN_ITEM_DRAGOVER           = TVN_FIRST + 52;
var TVN_ITEMTEXT_DRAGOVER       = TVN_FIRST + 53;
var TVN_ITEMICON_DRAGOVER       = TVN_FIRST + 54;
var TVN_ITEMCHECKBOX_DRAGOVER   = TVN_FIRST + 55;
var TVN_ITEMMANAGEICON_DRAGOVER = TVN_FIRST + 56;
function TVN_DRAGOVER_EVENT(code) { return (code > (TVN_FIRST + 50) && code < (TVN_FIRST + 60)) ? true : false }
// TreeView - Drop
var TVN_DROP                    = TVN_FIRST + 61;
var TVN_ITEM_DROP               = TVN_FIRST + 62;
var TVN_ITEMTEXT_DROP           = TVN_FIRST + 63;
var TVN_ITEMICON_DROP           = TVN_FIRST + 64;
var TVN_ITEMCHECKBOX_DROP       = TVN_FIRST + 65;
var TVN_ITEMMANAGEICON_DROP     = TVN_FIRST + 66;
function TVN_DROP_EVENT(code) { return (code > (TVN_FIRST + 60) && code < (TVN_FIRST + 70)) ? true : false }
// TreeView - Changed
var TVN_ITEMTEXT_CHANGED        = TVN_FIRST + 71;
var TVN_ITEMDATA_CHANGED        = TVN_FIRST + 72;
var TVN_ITEM_SELECTED           = TVN_FIRST + 81; // get selection or lost selection
var TVN_ITEM_FOCUSED            = TVN_FIRST + 82; // get selection or lost selection
var TVN_ITEM_CHECKED            = TVN_FIRST + 83;
var TVN_ITEM_ENABLED            = TVN_FIRST + 84;
var TVN_ITEM_VISIBLE            = TVN_FIRST + 85;
var TVN_ITEM_SELLISTCHANGED     = TVN_FIRST + 86;
var TVN_ITEM_CHECKLISTCHANGED   = TVN_FIRST + 87;
// TreeView - Keyboard Action
var TVN_ITEM_KEYPRESS           = TVN_FIRST + 91; // param - { itemId, key, ctrl, shift }
var TVN_ITEM_GO_NEXT            = TVN_FIRST + 92;
var TVN_ITEM_GO_PREV            = TVN_FIRST + 93;
var TVN_ITEM_GO_PARENT          = TVN_FIRST + 94;
var TVN_ITEM_DO_COLLAPSE        = TVN_FIRST + 95;
var TVN_ITEM_DO_EXPAND          = TVN_FIRST + 96;
var TVN_ITEM_DO_INSERT          = TVN_FIRST + 97;
var TVN_ITEM_DO_DELETE          = TVN_FIRST + 98;
var TVN_ITEM_DO_TEXTMODIFY      = TVN_FIRST + 99;
var TVN_ITEM_EDIT_ESCAPE        = TVN_FIRST + 100;
var TVN_ITEM_EDIT_CONFIRM       = TVN_FIRST + 101;

// ComboBox
var CBN_FIRST                   = 800;
var CBN_ONITEM_CLICKED          = CBN_FIRST + 1;
var CBN_ONCHANGED               = CBN_FIRST + 2;
var CBN_ITEM_STATE_CHANGED      = CBN_FIRST + 3;
var CBN_ITEM_INSERTED           = CBN_FIRST + 4;
var CBN_ITEM_DELETING           = CBN_FIRST + 5;
var CBN_ITEM_DELETED            = CBN_FIRST + 6;

// Column Header
var HDRN_FIRST                  = 850;
var HDRN_ITEM_INSERTED          = HDRN_FIRST + 1;
var HDRN_ITEM_DELETING          = HDRN_FIRST + 2;
var HDRN_ITEM_DELETED           = HDRN_FIRST + 3;
var HDRN_ITEM_STATE_CHANGED     = HDRN_FIRST + 4;
var HDRN_ITEM_CLICKED           = HDRN_FIRST + 11;
var HDRN_ITEM_DBLCLICKED        = HDRN_FIRST + 12;
var HDRN_ITEMTEXT_CHANGED       = HDRN_FIRST + 21;
var HDRN_ITEM_GETSPLTHEIGHT     = HDRN_FIRST + 30;
var HDRN_ITEM_SIZING            = HDRN_FIRST + 31;
var HDRN_ITEM_SIZED             = HDRN_FIRST + 32;

// Time Input
var TIN_FIRST                   = 900;
var TIN_CHANGED                 = TIN_FIRST + 1;
////////////////////////////////////////////////////////////////////////////
// Controls Notification
////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////
// class AP
////////////////////////////////////////////////////////////////////////////
// states
var APS_DISABLED                   = 0x1000;
var APS_REDRAW_FALSE               = 0x8000;
////////////////////////////////////////////////////////////////////////////
// AP constructor
////////////////////////////////////////////////////////////////////////////
AP = function()
{
    this.notifyHandlers = new Array();
    this.blockedNotifyCodes = {};
    this.element = null;
    this.flags = 0;
    this._state = 0;

    this.className = "AP";  
}

////////////////////////////////////////////////////////////////////////////
// __destructor
////////////////////////////////////////////////////////////////////////////
AP.prototype.__destructor = function()
{
    this.notifyHandlers = null;
    this.blockedNotifyCodes = null;
    this.element = null;
    this.flags = null;
    this._state = null;
    
    this.className = null;
}

////////////////////////////////////////////////////////////////////////////
// initialize
////////////////////////////////////////////////////////////////////////////
AP.initialize = function(params)
{
    if ( AP.is_set(params.root) )
    {
        ROOT_PATH = params.root;
    }
}

////////////////////////////////////////////////////////////////////////////
// destroyInstance
////////////////////////////////////////////////////////////////////////////
AP.destroyInstance = function(obj)
{
    obj.destroy();
    obj.__destructor();
    obj = null;
    return null;
}

////////////////////////////////////////////////////////////////////////////
// destroy
////////////////////////////////////////////////////////////////////////////
AP.prototype.destroy = function()
{
    if ( AP.is_set(this.element) )
    {
        this.element.instance = null;
        if ( this.element.parentNode )
        {
            this.element.parentNode.removeChild(this.element);
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
AP.prototype.create = function(elParent, className)
{
    this.element = AP.createElement("div", elParent, className);
    this.element.instance = this;
    this.element.id = AP.uniqueId(className);

    return this.element;
}

////////////////////////////////////////////////////////////////////////////
// enable
////////////////////////////////////////////////////////////////////////////
AP.prototype.enable = function(doEnable)
{
    if ( AP.is_set(doEnable) )
    {
        var curState = this.state();
        if ( doEnable )
        {
            curState &= ~APS_DISABLED;
        }
        else
        {
            curState |= APS_DISABLED;
        }
        this.state(curState);
    }
    
    return !(this.state() & APS_DISABLED);
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
var POS_FLAGS_RECALCULATE          = 0x0001;
AP.prototype.setPosition = function(pos)
{
    AP.setPosition(this.element, pos);
}

////////////////////////////////////////////////////////////////////////////
// state - get/set AP object state
////////////////////////////////////////////////////////////////////////////
AP.prototype.state = function(state)
{
    if ( !AP.is_set(state) )
        return this._state;

    var stateOld = this.state();
    if ( stateOld == state )
        return null;
        
    this._state = state;
    if ( !(this._state & APS_REDRAW_FALSE) )
    {
        this._stateToClassName();
    }
    this.fireNotify(APN_STATE_CHANGED, {
        old_state: stateOld,
        new_state: this._state,
        diff_state: stateOld ^ this._state
    });

    return this._state;
}

////////////////////////////////////////////////////////////////////////////
// tabIndex - get/set tabIndex
////////////////////////////////////////////////////////////////////////////
AP.prototype.tabIndex = function(tabIndex)
{
    if ( AP.is_set(tabIndex) )
    {
        this.element.tabIndex = tabIndex
    }
    
    return AP.is_set(this.element.tabIndex, -1);
}

////////////////////////////////////////////////////////////////////////////
// _stateToClassName
////////////////////////////////////////////////////////////////////////////
AP.prototype._stateToClassName = function() { }

////////////////////////////////////////////////////////////////////////////
// addNotifyHandler
// param =  [
//              function({
//                  subscriber: APprototype,
//                  producer: APprototype,
//                  code: notifyCode,
//                  param: notifyParam}),
//              APprototype (subscriber)
//          ]
////////////////////////////////////////////////////////////////////////////
AP.prototype.addNotifyHandler = function(param)
{
    this.notifyHandlers.push(param);
}

////////////////////////////////////////////////////////////////////////////
// notifyCodeBlocked
////////////////////////////////////////////////////////////////////////////
AP.prototype.notifyCodeBlocked = function(code, block)
{
    if ( AP.is_set(block) )
    {
        if ( block )
        {
            this.blockedNotifyCodes[code] = true;
        }
        else if ( this.notifyCodeBlocked(code) )
        {
            this.blockedNotifyCodes[code] = null;
            delete this.blockedNotifyCodes[code];
        }
    }
    return AP.is_set(this.blockedNotifyCodes[code]);
}

////////////////////////////////////////////////////////////////////////////
// blockNotifyCodes
////////////////////////////////////////////////////////////////////////////
AP.prototype.blockNotifyCodes = function()
{
    for (var loop = 0; loop < arguments.length; loop++ )
    {
        this.notifyCodeBlocked(arguments[loop], true);
    }
}

////////////////////////////////////////////////////////////////////////////
// unBlockNotifyCodes
////////////////////////////////////////////////////////////////////////////
AP.prototype.unBlockNotifyCodes = function()
{
    if ( !arguments.length )
    {
        for ( x in this.blockedNotifyCodes )
        {
            this.notifyCodeBlocked(x, false);
        }
    }
    else
    {
        for (var loop = 0; loop < arguments.length; loop++ )
        {
            this.notifyCodeBlocked(arguments[loop], false);
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// fireNotify
////////////////////////////////////////////////////////////////////////////
AP.prototype.fireNotify = function(code, param)
{
    if ( this.notifyCodeBlocked(code) )
    {
        return 0;
    }
    
    var retValue = 0;
    
    for ( var loop = 0; loop < this.notifyHandlers.length; loop++ )
    {
        retValue = this.notifyHandlers[loop][0]({
            subscriber: this.notifyHandlers[loop][1],
            producer: this,
            code: code,
            param: param
        });
        
        if ( retValue )
        {
            break;
        }
    }

    return retValue;
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
AP.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && AP.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// isIE - Browser detection
////////////////////////////////////////////////////////////////////////////
AP.isIE = function()
{
    return ( /msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent) );
}

////////////////////////////////////////////////////////////////////////////
// isFF - Browser detection
////////////////////////////////////////////////////////////////////////////
AP.isFF = function()
{
    return /firefox/i.test(navigator.userAgent);
}

////////////////////////////////////////////////////////////////////////////
// FFVer - Browser detection
////////////////////////////////////////////////////////////////////////////
AP.FFVer = function()
{
    var ffver = navigator.userAgent.match(/firefox\/(\d+)/i);
    return ffver?ffver[1]:-1;
}

////////////////////////////////////////////////////////////////////////////
// getXmlHttp - get HttpRequest Object
////////////////////////////////////////////////////////////////////////////
AP.getXmlHttp = function()
{
    var xmlhttp;
    try
    {
        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (e)
    {
        try
        {
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch(E)
        {
            xmlhttp = false;
        }
    }
  
    if ( !xmlhttp && typeof XMLHttpRequest != "undefined" )
    {
        xmlhttp = new XMLHttpRequest();
    }
  
    return xmlhttp;
}

////////////////////////////////////////////////////////////////////////////
// is_set - check if value is set (not null, not undefined)
////////////////////////////////////////////////////////////////////////////
AP.is_set = function(value, def_value)
{
    if ( (typeof def_value != "undefined") && (def_value !== null) )
    {
        if ( (typeof value == "undefined") || (value === null) )
        {
            return def_value;
        }
        else
        {
            return value;
        }
    }
    else
    {
        if ( (typeof value == "undefined") || (value === null) )
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    return null;
}


////////////////////////////////////////////////////////////////////////////
// AP.LZ - padding "0"
////////////////////////////////////////////////////////////////////////////
AP.LZ = function(x)
{
    return (x<0||x>9?"":"0")+x;
}

////////////////////////////////////////////////////////////////////////////
// duplicateObject
////////////////////////////////////////////////////////////////////////////
AP.duplicateObject = function(obj)
{
    var ret = {};

    for ( var key in obj )
    {
        ret[key] = obj[key];
    }
    
    return ret;
}

////////////////////////////////////////////////////////////////////////////
// addEvent - Add Event Listener to DOM Element
////////////////////////////////////////////////////////////////////////////
AP.addEvent = function(el, evname, func)
{
    if ( el.attachEvent ) // IE
    {
        el.attachEvent("on" + evname, func);
    }
    else if ( el.addEventListener ) // Gecko / W3C
    {
        el.addEventListener(evname, func, true);
    }
    else
    {
        //el["on" + evname] = func;
        throw "not supported browser";
    }
}

////////////////////////////////////////////////////////////////////////////
// stopEvent - break event
////////////////////////////////////////////////////////////////////////////
AP.stopEvent = function(ev)
{
    ev || (ev = window.event);
    if ( AP.isIE() )
    {
        ev.cancelBubble = true;
        ev.returnValue = false;
    }
    else
    {
        ev.preventDefault();
        ev.stopPropagation();
    }

    return false;
}

////////////////////////////////////////////////////////////////////////////
// removeEvent - Remove Event Listener from DOM element
////////////////////////////////////////////////////////////////////////////
AP.removeEvent = function(el, evname, func)
{
    if (el.detachEvent) { // IE
        el.detachEvent("on" + evname, func);
    } else if (el.removeEventListener) { // Gecko / W3C
        el.removeEventListener(evname, func, true);
    } else {
        el["on" + evname] = null;
    }
}

////////////////////////////////////////////////////////////////////////////
// onRestrictedEvent
////////////////////////////////////////////////////////////////////////////
AP.onRestrictedEvent = function(ev)
{
    ev || (ev = window.event);
    return AP.stopEvent(ev);
}

////////////////////////////////////////////////////////////////////////////
// getElement - get DOM Element by Id
////////////////////////////////////////////////////////////////////////////
AP.getElement = function(id)
{
    /*
    if( document.all ) { // if Inernet Explorer
        return document.all(id);
    }
    else if( document.getElementById ) {  // if Mozilla (Netscape 6)
        return document.getElementById(id);
    }
    return null;
    */
    return document.getElementById(id);
}

////////////////////////////////////////////////////////////////////////////
// createElement - create DOM Element
////////////////////////////////////////////////////////////////////////////
AP.createElement = function(type, elParent, className)
{
    var elRet = document.createElement(type);
    if ( AP.is_set(className) )
    {
        elRet.className = "ap " + className;
    }
    if ( AP.is_set(elParent) )
    {
        elParent.appendChild(elRet);
    }
    return elRet;
}

////////////////////////////////////////////////////////////////////////////
// createNamedElement - create Named DOM Element
// !!! - remember that in IE "name" attribute is read only - !!!!
////////////////////////////////////////////////////////////////////////////
/*
var elParams = {
    tagName: "input",
    name: "string",
    parent: elParent,
    className: "className string",
    type: "checkbox" // if tagName == input
};
*/
AP.createNamedElement = function(elParams)
{
    // type, name, elParent, className
    var element = null;
    elParams.name = AP.is_set(elParams.name, "");

    try
    {   // Try the IE way; this fails on standards-compliant browsers
        element = AP.createElement('<'+elParams.tagName+' name="'+elParams.name+'">', null, elParams.className);
    }catch (e)
    {
    }
    
    if ( !element || element.nodeName != elParams.tagName.toUpperCase() )
    {
        // Non-IE browser; use canonical method to create named element
        element = AP.createElement(elParams.tagName, null, elParams.className);
        element.name = elParams.name;
    }
    
    if ( elParams.type )
    {
        element.type = elParams.type;
    }
    
    if ( elParams.parent )
    {
        elParams.parent.appendChild(element);
    }
    
    return element;
}

////////////////////////////////////////////////////////////////////////////
// setInnerText - use this method to "safe" set inner text properties in different browsers
////////////////////////////////////////////////////////////////////////////
AP.setInnerText = function(obj, text)
{
    if ( typeof obj.innerText != "undefined" )
    {
        obj.innerText = text;
    }
    else
    {
        obj.text = text;
    }
}

////////////////////////////////////////////////////////////////////////////
// swapNodes
////////////////////////////////////////////////////////////////////////////
AP.swapNodes = function(id1, id2)
{
    var item1 = typeof id1 == "string" ? AP.getElement(id1) : id1;
    var item2 = typeof id2 == "string" ? AP.getElement(id2) : id2;
    
    // We need a clone of the node we want to swap
    var itemtmp = item1.cloneNode(1);
    
    // We also need the parentNode of the items we are going to swap.
    var parent = item1.parentNode;
    
    // First replace the second node with the copy of the first node
    // which returns a the new node
    item2 = parent.replaceChild(itemtmp,item2);
    
    //Then we need to replace the first node with the new second node
    parent.replaceChild(item2,item1);
    
    // And finally replace the first item with it's copy so that we
    // still use the old nodes but in the new order. This is the reason
    // we don't need to update our Behaviours since we still have
    // the same nodes.
    parent.replaceChild(item1,itemtmp);
    
    // Free up some memory, we don't want unused nodes in our document.
    itemtmp = null;
}

////////////////////////////////////////////////////////////////////////////
// nextSibling - get Next Visible Sibling Element
////////////////////////////////////////////////////////////////////////////
AP.nextSibling = function(obj)
{
    for ( var ret = obj.nextSibling; ret && ((ret.nodeName == "#text") || (ret.nodeName == "#comment")); ret = ret.nextSibling );
    return ret; 
}

////////////////////////////////////////////////////////////////////////////
// prevSibling - get Previous Visible Sibling Element
////////////////////////////////////////////////////////////////////////////
AP.prevSibling = function(obj)
{
    for ( var ret = obj.previousSibling; ret && ((ret.nodeName == "#text") || (ret.nodeName == "#comment")); ret = ret.previousSibling );
    return ret;
}

////////////////////////////////////////////////////////////////////////////
// firstChild - get First Visible Child Element
////////////////////////////////////////////////////////////////////////////
AP.firstChild = function(obj)
{
    var ret = obj.firstChild;
    if ( ret && (ret.nodeName == "#text" || ret.nodeName == "#comment") )
    {
        ret = AP.nextSibling(ret);
    }
    return ret;
}

////////////////////////////////////////////////////////////////////////////
// show - show/hide DOM Element
////////////////////////////////////////////////////////////////////////////
AP.show = function(obj, bShow)
{
    if ( AP.is_set(bShow) !== false )
    {
        if ( typeof obj == "string" )
        {
            obj = AP.getElement(obj);
            if ( !obj )
                throw ("Element Not Found");
        }
        obj.style.display = (bShow ? "" : "none");
    }
    return (obj.style.display != "none");
}

////////////////////////////////////////////////////////////////////////////
// visible - block/unblock DOM Element drawing
////////////////////////////////////////////////////////////////////////////
AP.visible = function(obj, bVisible)
{
    if ( AP.is_set(bVisible) )
    {
        if ( typeof obj == "string" )
        {
            obj = AP.getElement(obj);
            if ( !obj )
                throw ("Element Not Found");
        }
        if ( bVisible )
        {
            obj.style.visibility = "";
        }
        else
        {
            obj.style.visibility = "hidden";
        }
    }
    return (obj.style.visibility.toLowerCase() != "hidden");
}

////////////////////////////////////////////////////////////////////////////
// getStyle - get DOM element Current Style
////////////////////////////////////////////////////////////////////////////
AP.getStyle = function(element)
{
    var computedStyle;
    if (typeof element.currentStyle != "undefined")
    {
        computedStyle = element.currentStyle;
    }
    else if (typeof document.defaultView != "undefined" && typeof document.defaultView.getComputedStyle != "undefined")
    {
        computedStyle = document.defaultView.getComputedStyle(element, "");
    }
    else
    {
        computedStyle = element.style;
    }
    return computedStyle;
}

////////////////////////////////////////////////////////////////////////////
// appendClassName
////////////////////////////////////////////////////////////////////////////
AP.appendClassName = function(element, className)
{
    if ( !AP.is_set(element) || !AP.is_set(element.className) )
    {
        return;
    }
    var newClassName = AP.appendSubString(element.className, className, " ");
    if ( element.className != newClassName )
    {
        element.className = newClassName;
    }
}

////////////////////////////////////////////////////////////////////////////
// removeClassName
////////////////////////////////////////////////////////////////////////////
AP.removeClassName = function(element, className)
{
    if ( !AP.is_set(element) || !AP.is_set(element.className) )
    {
        return;
    }
    var newClassName = AP.removeSubString(element.className, className, " ");
    if ( element.className != newClassName )
    {
        element.className = newClassName;
    }
}

////////////////////////////////////////////////////////////////////////////
// isSetClassName
////////////////////////////////////////////////////////////////////////////
AP.isSetClassName = function(element, className)
{
    if ( !AP.is_set(element) || !AP.is_set(element.className) )
    {
        return false;
    }
    
    var source = element.className.toLowerCase();
    var need = className.toLowerCase();
    
    if ( source == need )
    {
        return true;
    }
    else if ( source.indexOf(" " + need  + " ") != -1 )
    {
        return true;
    }
    else if ( source.indexOf(need  + " ") == 0 )
    {
        return true;
    }
    else
    {
        var index = source.indexOf(" " + need);
        if ( index != -1 && index == source.length - (" " + need).length)
        {
            return true;
        }
    }
    
    return false;
}

////////////////////////////////////////////////////////////////////////////
// offsetLeft - return Left coord.
////////////////////////////////////////////////////////////////////////////
AP.offsetLeft = function(obj)
{
    var st = AP.getStyle(obj);
    var left = parseInt(st.left);
    var px = ( st.left && (st.left.toLowerCase().indexOf("px") != -1) );
    var absolute = ( obj.style.position && (obj.style.position.toLowerCase() == "absolute") );
    
    if ( isNaN(left) || !px || !absolute )
    {
        left = obj.offsetLeft;
    }

    return left;
}

////////////////////////////////////////////////////////////////////////////
// offsetTop - return Left coord.
////////////////////////////////////////////////////////////////////////////
AP.offsetTop = function(obj)
{
    var st = AP.getStyle(obj);
    var top = parseInt(st.top);
    var px = ( st.top && (st.top.toLowerCase().indexOf("px") != -1) );
    var absolute = ( obj.style.position && (obj.style.position.toLowerCase() == "absolute") );
    
    if ( isNaN(top) || !px || !absolute )
    {
        top = obj.offsetTop;
    }

    return top;
}

////////////////////////////////////////////////////////////////////////////
// offsetWidth - return offset Width of Element
////////////////////////////////////////////////////////////////////////////
AP.offsetWidth = function(obj)
{
    var st = AP.getStyle(obj);
    var width = parseInt(st.width);
    var px = ( st.width && (st.width.toLowerCase().indexOf("px") != -1) );

    if ( isNaN(width) || !px )
    {
        width = obj.offsetWidth;
    }
    else
    {
        var diff = (isNaN(parseInt(st.marginLeft)) ? 0 : parseInt(st.marginLeft));
        diff += (isNaN(parseInt(st.marginRight)) ? 0 : parseInt(st.marginRight));
        diff += (isNaN(parseInt(st.borderLeftWidth)) ? 0 : parseInt(st.borderLeftWidth));
        diff += (isNaN(parseInt(st.borderRightWidth)) ? 0 : parseInt(st.borderRightWidth));
        width += diff;
    }

    return width;
}

////////////////////////////////////////////////////////////////////////////
// offsetHeight - return offset Height of Element
////////////////////////////////////////////////////////////////////////////
AP.offsetHeight = function(obj)
{
    var st = AP.getStyle(obj);
    var height = parseInt(st.height);
    var px = ( st.height && (st.height.toLowerCase().indexOf("px") != -1) );

    if ( isNaN(height) || !px )
    {
        height = obj.offsetHeight;
    }
    else
    {
        var diff = (isNaN(parseInt(st.marginTop)) ? 0 : parseInt(st.marginTop));
        diff += (isNaN(parseInt(st.marginBottom)) ? 0 : parseInt(st.marginBottom));
        diff += (isNaN(parseInt(st.borderTopWidth)) ? 0 : parseInt(st.borderTopWidth));
        diff += (isNaN(parseInt(st.borderBottomWidth)) ? 0 : parseInt(st.borderBottomWidth));
        height += diff;
    }

    return height;
}

////////////////////////////////////////////////////////////////////////////
// clientWidth - return client Width of Element
////////////////////////////////////////////////////////////////////////////
AP.clientWidth = function(obj)
{
    if ( obj.clientWidth > 0 )
    {
        return obj.clientWidth;
    }
    
    var st = AP.getStyle(obj);
    var diff = (isNaN(parseInt(st.paddingLeft)) ? 0 : parseInt(st.paddingLeft));
    diff += (isNaN(parseInt(st.paddingRight)) ? 0 : parseInt(st.paddingRight));

    var px = ( st.width && (st.width.toLowerCase().indexOf("px") != -1) );
    var width = ( (isNaN(parseInt(st.width)) || !px) ? obj.clientWidth : parseInt(st.width) );
    
    return width - diff;
}

////////////////////////////////////////////////////////////////////////////
// clientHeight - return client Height of Element
////////////////////////////////////////////////////////////////////////////
AP.clientHeight = function(obj, fromStyle)
{
    var st = AP.getStyle(obj);
    var diff = (isNaN(parseInt(st.paddingTop)) ? 0 : parseInt(st.paddingTop));
    diff += (isNaN(parseInt(st.paddingBottom)) ? 0 : parseInt(st.paddingBottom));

    var px = ( st.height && (st.height.toLowerCase().indexOf("px") != -1) );
    var height = 0;
    if ( obj.clientHeight > 0 )
    {
        height = obj.clientHeight;
    }
    else if ( isNaN(parseInt(st.height)) || !px )
    {
        height = 0;
    }
    else
    {
        height = parseInt(st.height);
    }
    
    var clientHeight = height;
    // !!! Moz Hack (Body Height = real child elements height)
    // !!! IE 7 also needs
    if ( /*!AP.isIE() && */(obj == document.body) )
    {
        var tmp = AP.createElement("div");
        tmp.style.visibility = "hidden";
        tmp.style.position = "absolute";
        tmp.style.background = "red";
        tmp.innerHTML = "HACK";
        tmp.style.left = "0px"; tmp.style.top = "0px"; tmp.style.width = "100%"; tmp.style.height = "100%";
        document.body.appendChild(tmp);
        clientHeight = tmp.clientHeight;
        document.body.removeChild(tmp);
        tmp = null;
    }
    // -- !!! Moz Hack  
    
    return clientHeight - diff;
}

////////////////////////////////////////////////////////////////////////////
// AP.width - get/set element offset width
////////////////////////////////////////////////////////////////////////////
AP.width = function(obj, cx)
{
    if ( AP.is_set(cx) !== false )
    {
        if ( typeof cx == "string" && cx.toLowerCase() == "auto" )
        {
            obj.style.width = "auto";
        }
        else
        {
            if ( typeof cx != "number" )
            {
                cx = parseInt(cx);
                if ( isNaN(cx) )
                    throw ("Invalid Argument");
            }
    
            var st = AP.getStyle(obj);
            var diff = (isNaN(parseInt(st.paddingLeft)) ? 0 : parseInt(st.paddingLeft));
            diff += (isNaN(parseInt(st.paddingRight)) ? 0 : parseInt(st.paddingRight));
            cx -= diff;
            
            var margin = (isNaN(parseInt(st.marginLeft)) ? 0 : parseInt(st.marginLeft));
            margin += (isNaN(parseInt(st.marginRight)) ? 0 : parseInt(st.marginRight));
            cx -= margin;
    
            var border = (isNaN(parseInt(st.borderLeftWidth)) ? 0 : parseInt(st.borderLeftWidth));
            border += (isNaN(parseInt(st.borderRightWidth)) ? 0 : parseInt(st.borderRightWidth));
            cx -= border;
            
            cx = Math.max(0, cx);
            obj.style.width = cx + "px";
        }
    }

    return AP.offsetWidth(obj);
}

////////////////////////////////////////////////////////////////////////////
// AP.height - get/set element offset height
////////////////////////////////////////////////////////////////////////////
AP.height = function(obj, cy)
{
    if ( AP.is_set(cy) !== false )
    {
        if ( typeof cy == "string" && cy.toLowerCase() == "auto" )
        {
            obj.style.height = "auto";
        }
        else
        {
            if ( typeof cy != "number" )
            {
                cy = parseInt(cy);
                if ( isNaN(cy) )
                    throw ("Invalid Argument");
            }
            var st = AP.getStyle(obj);
            var diff = (isNaN(parseInt(st.paddingTop)) ? 0 : parseInt(st.paddingTop));
            diff += (isNaN(parseInt(st.paddingBottom)) ? 0 : parseInt(st.paddingBottom));
            cy -= diff;
            
            var margin = (isNaN(parseInt(st.marginTop)) ? 0 : parseInt(st.marginTop));
            margin += (isNaN(parseInt(st.marginBottom)) ? 0 : parseInt(st.marginBottom));
            cy -= margin;
    
            var border = (isNaN(parseInt(st.borderTopWidth)) ? 0 : parseInt(st.borderTopWidth));
            border += (isNaN(parseInt(st.borderBottomWidth)) ? 0 : parseInt(st.borderBottomWidth));
            cy -= border;
            
            cy = Math.max(0, cy);
            obj.style.height = cy + "px";
        }
    }

    return AP.offsetHeight(obj);
}

////////////////////////////////////////////////////////////////////////////
// getAbsolutePos - get element absolute position
////////////////////////////////////////////////////////////////////////////
AP.getAbsolutePos = function(el)
{
    var SL = 0, ST = 0;
    var is_div = /^div$/i.test(el.tagName);
    
    if (is_div && el.scrollLeft)
    {
        SL = el.scrollLeft;
    }
    if (is_div && el.scrollTop)
    {
        ST = el.scrollTop;
    }

    var r = { x: AP.offsetLeft(el) - SL, y: AP.offsetTop(el) - ST };
    if ( el.offsetParent )
    {
        var tmp = AP.getAbsolutePos(el.offsetParent);
        var is_div_2 = /^div$/i.test(el.offsetParent.tagName);
        if ( is_div_2 )
        {
            var st = AP.getStyle(el.offsetParent);
            tmp.x += (isNaN(parseInt(st.borderLeftWidth)) ? 0 : parseInt(st.borderLeftWidth));
            tmp.x += (isNaN(parseInt(st.marginLeft)) ? 0 : parseInt(st.marginLeft));
            
            tmp.y += (isNaN(parseInt(st.borderTopWidth)) ? 0 : parseInt(st.borderTopWidth));
            tmp.y += (isNaN(parseInt(st.marginTop)) ? 0 : parseInt(st.marginTop));
        }
        r.x += tmp.x;
        r.y += tmp.y;
    }
    
    return r;
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
AP.setPosition = function(element, pos)
{
    if ( AP.is_set(pos.x) !== false )
    {
        element.style.left = pos.x + "px";
    }
    if ( AP.is_set(pos.y) !== false )
    {
        element.style.top = pos.y + "px";
    }
    
    AP.width(element, pos.cx);
    AP.height(element, pos.cy);
}

////////////////////////////////////////////////////////////////////////////
// uniqueId - create local ducument Unique Id
////////////////////////////////////////////////////////////////////////////
AP.uniqueId = function(prefix)
{
    prefix = AP.is_set(prefix, "");
    return prefix + AP.getTickCount() + Math.floor(Math.random()*Math.pow(10, 10));
}

////////////////////////////////////////////////////////////////////////////
// getTickCount
////////////////////////////////////////////////////////////////////////////
AP.getTickCount = function()
{
    var date = new Date();
    var ret = date.getTime();
    delete date;
    return ret;
}

////////////////////////////////////////////////////////////////////////////
// getCookie
////////////////////////////////////////////////////////////////////////////
AP.getCookie = function(name, def_value)
{
    if ( AP.is_set(def_value) === false )
    {
        def_value = null;
    }

    var start = document.cookie.indexOf( name + "=" );
    var len = start + name.length + 1;
    if ( ( !start ) && ( name != document.cookie.substring( 0, name.length ) ) )
    {
        return def_value;
    }
    
    if ( start == -1 )
    {
        return def_value;
    }
    
    var end = document.cookie.indexOf( ";", len );
    if ( end == -1 )
    {
        end = document.cookie.length;
    }

    return unescape( document.cookie.substring( len, end ) );
}

////////////////////////////////////////////////////////////////////////////
// setCookie
////////////////////////////////////////////////////////////////////////////
AP.setCookie = function(name, value, daysExpires, path, domain, secure)
{
    // set time, it's in milliseconds
    var today = new Date();
    today.setTime( today.getTime() );

    var expires_date = null;
    if ( daysExpires )
    {
        daysExpires = daysExpires * 1000 * 60 * 60 * 24;
        expires_date = new Date( today.getTime() + (daysExpires) );
    }

    document.cookie = name + "=" + escape( value ) +
        ( ( expires_date ) ? ";expires=" + expires_date.toGMTString() : "" ) + 
        ( ( path ) ? ";path=" + path : ";path=/" ) + 
        ( ( domain ) ? ";domain=" + domain : "" ) +
        ( ( secure ) ? ";secure" : "" );
}

////////////////////////////////////////////////////////////////////////////
// delCookie
////////////////////////////////////////////////////////////////////////////
AP.delCookie = function(name, path, domain)
{
    if ( AP.getCookie(name) )
    {
        document.cookie = name + "=" +
            ";path=" + ( ( path ) ? path : "/") +
            ( ( domain ) ? ";domain=" + domain : "" ) +
            ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
    }
}

////////////////////////////////////////////////////////////////////////////
// appendSubString
////////////////////////////////////////////////////////////////////////////
AP.appendSubString = function(str, pattern, separator, case_sensitive)
{
    case_sensitive = AP.is_set(case_sensitive, true);
    separator = AP.is_set(separator, " ");
    var r_str = case_sensitive ? str : str.toLowerCase();
    var r_pattern = case_sensitive ? pattern : pattern.toLowerCase();
    if (r_str)
    {
        var index = r_str.indexOf(r_pattern);
        if (index == -1)
        {
            if (r_str.charAt(r_str.length - 1) != separator)
            {
                str += separator;
            }
            str += pattern;
        }
    }
    else
    {
        str = pattern;
    }
    
    return str;
}

////////////////////////////////////////////////////////////////////////////
// removeSubString
////////////////////////////////////////////////////////////////////////////
AP.removeSubString = function(str, pattern, separator, case_sensitive)
{
    case_sensitive = AP.is_set(case_sensitive, true);
    separator = AP.is_set(separator, " ");
    var r_str = case_sensitive ? str : str.toLowerCase();
    var r_pattern = case_sensitive ? pattern : pattern.toLowerCase();
    if ( r_str )
    {
        if (r_str == r_pattern)
        {
            // if string is pattern
            str = "";
            return str;
        }
        
        if (r_str.substring(r_str.length - r_pattern.length - 1, r_str.length) == separator + r_pattern) 
        {
            // if pattern is in the end of string
            str = str.substring(0, r_str.length - r_pattern.length - 1);
            return str;
        }
        
        var index = r_str.indexOf(separator + r_pattern + separator);
        if (index != -1)
        {
            // if pattern is in the middle of string
            str = str.substring(0, index) + str.substring(index + r_pattern.length + 1, r_str.length);
            return str;
        }
        
        if (r_str.substring(0, r_pattern.length + 1) == r_pattern + separator)
        {
            // if pattern is in the start of string
            str = str.substring(r_pattern.length + 1, r_str.length);
            return str;
        }
    }
    return str;
}

////////////////////////////////////////////////////////////////////////////
// validString
////////////////////////////////////////////////////////////////////////////
AP.validString = function(str)
{
    str = AP.trim(str);
    if ( !str.length )
    {
        return false;
    }
    return str;
}

////////////////////////////////////////////////////////////////////////////
// validEMail
////////////////////////////////////////////////////////////////////////////
AP.validEMail = function(email)
{
    email = AP.trim(email);
    var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
    if ( reg.test(email) == false )
    {
        return false;
    }
    return email;
}

////////////////////////////////////////////////////////////////////////////
// validUrl
////////////////////////////////////////////////////////////////////////////
AP.validUrl = function(url)
{
    url = AP.trim(url);
    var reg = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
    if ( reg.test(url) == false )
    {
        return false;
    }
    return url;
}

////////////////////////////////////////////////////////////////////////////
// validFileName
////////////////////////////////////////////////////////////////////////////
AP.validFileName = function(fname, trim)
{
    trim = AP.is_set(trim, true);
    if ( trim )
    {
        fname = AP.trim(fname);
    }
    if ( !fname.length )
    {
        return false;
    }
    var reg = /[\\\/\:\*\?\"\<\>\|]/;
    if ( reg.test(fname) )
    {
        return false;
    }
    return fname;
}

////////////////////////////////////////////////////////////////////////////
// trimLeft
////////////////////////////////////////////////////////////////////////////
AP.trimLeft = function(input_str, chr)
{
    chr = AP.is_set(chr, " ");
    while ( input_str.charAt(0) == chr )
    {
        input_str = input_str.substring(1, input_str.length);
    }
    return input_str;
}

////////////////////////////////////////////////////////////////////////////
// trimRight
////////////////////////////////////////////////////////////////////////////
AP.trimRight = function(input_str, chr)
{
    chr = AP.is_set(chr, " ");
    while ( input_str.charAt(input_str.length - 1) == chr )
    {
        input_str = input_str.substring(0, input_str.length - 1);
    }
    return input_str;
}

////////////////////////////////////////////////////////////////////////////
// trim
////////////////////////////////////////////////////////////////////////////
AP.trim = function(input_str, chr)
{
    chr = AP.is_set(chr, " ");
    input_str = AP.trimLeft(input_str, chr);
    input_str = AP.trimRight(input_str, chr);
    return input_str;
}

////////////////////////////////////////////////////////////////////////////
// strRepeat
////////////////////////////////////////////////////////////////////////////
AP.strRepeat = function(str, num, length)
{
    var ret = "";
    num = AP.is_set(num, -1);
    length = AP.is_set(length, -1);
    if ( num != -1 )
    {
        for ( var loop = 0; loop < num; loop++ )
        {
            ret += str;
        }
    }
    else if ( length != -1 )
    {
        while ( ret.length < length )
        {
            ret += str;
        }
    }
    if ( length != -1 )
    {
        ret = ret.substr(0, length);
    }
    return ret;
}

////////////////////////////////////////////////////////////////////////////
// strPad
// pad_type - 'STR_PAD_LEFT'/'STR_PAD_RIGHT'/'STR_PAD_BOTH'(default)
////////////////////////////////////////////////////////////////////////////
AP.strPad = function(input, pad_length, pad_string, pad_type)
{
    pad_type = AP.is_set(pad_type, 'STR_PAD_RIGHT');
    if ( pad_type != 'STR_PAD_LEFT'
         && pad_type != 'STR_PAD_RIGHT'
         && pad_type != 'STR_PAD_BOTH' )
    {
        pad_type = 'STR_PAD_RIGHT';
    }
    
    var pad_to_go = null;
    if ( (pad_to_go = pad_length - input.length) > 0 )
    {
        if ( pad_type == 'STR_PAD_LEFT' )
        {
            input = AP.strRepeat(pad_string, -1, pad_to_go) + input;
        }
        else if ( pad_type == 'STR_PAD_RIGHT' )
        {
            input = input + AP.strRepeat(pad_string, -1, pad_to_go);
        }
        else if (pad_type == 'STR_PAD_BOTH')
        {
            var half = AP.strRepeat(pad_string, -1, Math.ceil(pad_to_go/2));
            input = half + input + half;
            input = input.substr(0, pad_length);
        }
    }
    
    return input;
}

////////////////////////////////////////////////////////////////////////////
// strKeyConvert
////////////////////////////////////////////////////////////////////////////
AP.strKeyConvert = function(text, key)
{
    if ( !key || !key.length )
    {
        return text;
    }

    // remove the spaces in the key
    key = key.replace(' ', '');
    if ( !key || !key.length )
//    if ( key.length < 8 )
    {
        throw ("[AP::strKeyConvert] key error");
    }
    
    
    // set key length to be no more than 32 characters
    var key_len = key.length;
    if ( key_len > 32 )
    {
        key_len = 32;
    }

    // A wee bit of tidying in case the key was too long
    key = key.substr(0, key_len);

    // We use this a couple of times or so
    var text_len = text.length;

    // fill key with the bitwise AND of the ith key character and 0x1F, padded to length of text.
    key = AP.strPad("", text_len, key); // this one _does_ need to be str_pad

    // {en|de}cryption algorithm
    var result = "";
    for ( var loop = 0; loop < text_len; loop++ )
    {
        var chrTxt = text.charCodeAt(loop);
        var chrKey = key.charCodeAt(loop);
        var chr = ((chrTxt ^ chrKey) & 0x1F) | (chrTxt & 0xE0);
        result += String.fromCharCode(chr);
    }
    
    return result;
}


////////////////////////////////////////////////////////////////////////////
// base64_encode
////////////////////////////////////////////////////////////////////////////
// private property
AP._keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

AP.base64_encode = function (input) {
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;

    input = AP._utf8_encode(input);

    while (i < input.length) {

        chr1 = input.charCodeAt(i++);
        chr2 = input.charCodeAt(i++);
        chr3 = input.charCodeAt(i++);

        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;

        if (isNaN(chr2)) {
            enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
            enc4 = 64;
        }

        output = output +
        AP._keyStr.charAt(enc1) + AP._keyStr.charAt(enc2) +
        AP._keyStr.charAt(enc3) + AP._keyStr.charAt(enc4);

    }

    return output;
}
////////////////////////////////////////////////////////////////////////////
// base64_decode
////////////////////////////////////////////////////////////////////////////
AP.base64_decode = function (input) {
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

    while (i < input.length) {

        enc1 = AP._keyStr.indexOf(input.charAt(i++));
        enc2 = AP._keyStr.indexOf(input.charAt(i++));
        enc3 = AP._keyStr.indexOf(input.charAt(i++));
        enc4 = AP._keyStr.indexOf(input.charAt(i++));

        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;

        output = output + String.fromCharCode(chr1);

        if (enc3 != 64) {
            output = output + String.fromCharCode(chr2);
        }
        if (enc4 != 64) {
            output = output + String.fromCharCode(chr3);
        }

    }

    output = AP._utf8_decode(output);

    return output;

}

////////////////////////////////////////////////////////////////////////////
// _utf8_encode private method for UTF-8 encoding
////////////////////////////////////////////////////////////////////////////
AP._utf8_encode = function (string) {
    string = string.replace(/\r\n/g,"\n");
    var utftext = "";

    for (var n = 0; n < string.length; n++) {

        var c = string.charCodeAt(n);

        if (c < 128) {
            utftext += String.fromCharCode(c);
        }
        else if((c > 127) && (c < 2048)) {
            utftext += String.fromCharCode((c >> 6) | 192);
            utftext += String.fromCharCode((c & 63) | 128);
        }
        else {
            utftext += String.fromCharCode((c >> 12) | 224);
            utftext += String.fromCharCode(((c >> 6) & 63) | 128);
            utftext += String.fromCharCode((c & 63) | 128);
        }

    }

    return utftext;
}

////////////////////////////////////////////////////////////////////////////
// _utf8_decode private method for UTF-8 decoding
////////////////////////////////////////////////////////////////////////////
AP._utf8_decode = function (utftext) {
    var string = "";
    var i = 0;
    var c = c1 = c2 = 0;

    while ( i < utftext.length ) {

        c = utftext.charCodeAt(i);

        if (c < 128) {
            string += String.fromCharCode(c);
            i++;
        }
        else if((c > 191) && (c < 224)) {
            c2 = utftext.charCodeAt(i+1);
            string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
            i += 2;
        }
        else {
            c2 = utftext.charCodeAt(i+1);
            c3 = utftext.charCodeAt(i+2);
            string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
            i += 3;
        }

    }

    return string;
}

////////////////////////////////////////////////////////////////////////////
// encodeStr
////////////////////////////////////////////////////////////////////////////

AP.encodeStr = function (str, key)
{
    str = AP._utf8_encode(str);
    str = AP.strKeyConvert(str, key);
    str = AP.base64_encode(str);
    return str;
}
////////////////////////////////////////////////////////////////////////////
// decodeStr
////////////////////////////////////////////////////////////////////////////
AP.decodeStr = function (str, key)
{
    str = AP.base64_decode(str);
    str = AP.strKeyConvert(str, key);
    str = AP._utf8_decode(str);
    return str;
}


////////////////////////////////////////////////////////////////////////////
// date_format
////////////////////////////////////////////////////////////////////////////
AP.date_format = function (str, params)
{
    if (AP.is_set(params))
    {
        for (var loop = 0; loop < params.length; loop++)
        {
            str = str.replace(/\%(\d+)d/, AP.strPad(params[loop].toString(), 2, "0".toString(), "STR_PAD_LEFT"));
        }
    }
    return str;
}

////////////////////////////////////////////////////////////////////////////
// merge_sort
////////////////////////////////////////////////////////////////////////////
var SORT_INSERTION_BOUND = 8; /* boundary point to use insertion sort */
AP.merge_sort = function(arr, fun_ptr, the_len)
{
    the_len = AP.is_set(the_len, arr.length);
    var span;
    var lb;
    var ub;
    var indx;
    var indx2;

    if ( the_len <= 1 )
    {
        return;
    }

    span = SORT_INSERTION_BOUND;
    
    /* insertion sort the first pass */
    { 
        var prev_val;
        var cur_val;
        var temp_val;

        for (lb = 0; lb < the_len; lb += span)
        {
            if ( (ub = lb + span) > the_len )
            {
                ub = the_len;
            }

            prev_val = arr[lb];

            for ( indx = lb + 1; indx < ub; ++indx )
            {
                cur_val = arr[indx];
                
                if ( fun_ptr(prev_val, cur_val) > 0 )
                {
                    /* out of order: array[indx-1] > array[indx] */
                    arr[indx] = prev_val; /* move up the larger item first */
                    
                    /* find the insertion point for the smaller item */
                    for ( indx2 = indx - 1; indx2 > lb; )
                    {
                        temp_val = arr[indx2 - 1];
                        if ( fun_ptr(temp_val, cur_val) > 0 )
                        {
                            arr[indx2--] = temp_val;
                            /* still out of order, move up 1 slot to make room */
                        }
                        else
                        {
                            break;
                        }
                    }
                    arr[indx2] = cur_val; /* insert the smaller item right here */
                }
                else
                {
                    /* in order, advance to next element */
                    prev_val = cur_val;
                }
            }
        }
    }

    /* second pass merge sort */
    {
        var median;
        var aux = new Array();

        while ( span < the_len )
        {
            /* median is the start of second file */
            for ( median = span; median < the_len; )
            {
                indx2 = median - 1;
                if ( fun_ptr(arr[indx2], arr[median]) > 0 )
                {
                    /* the two files are not yet sorted */
                    if ( (ub = median + span) > the_len )
                    {
                        ub = the_len;
                    }
                    /* skip over the already sorted largest elements */
                    while ( fun_ptr(arr[--ub], arr[indx2]) >= 0 )
                    {
                    }
                    
                    /* copy second file into buffer */
                    for ( indx = 0; indx2 < ub; ++indx )
                    {
                        aux[indx] = arr[++indx2];
                    }
                    --indx;
                    indx2 = median - 1;
                    lb = median - span;
                    /* merge two files into one */
                    var done = false;
                    while (!done)
                    {
                        if ( fun_ptr(aux[indx], arr[indx2]) >= 0 )
                        {
                            arr[ub--] = aux[indx];
                            if ( indx > 0 )
                            {
                                --indx;
                            }
                            else
                            {
                                /* second file exhausted */
                                while (true)
                                {
                                    arr[ub--] = arr[indx2];
                                    if ( indx2 > lb )
                                    {
                                        --indx2;
                                    }
                                    else
                                    {
                                        done = true;
                                        break; /* done */
                                    }
                                }
                            }
                        }
                        else
                        {
                            arr[ub--] = arr[indx2];
                            if ( indx2 > lb )
                            {
                                --indx2;
                            }
                            else
                            {
                                /* first file exhausted */
                                while (true)
                                {
                                    arr[ub--] = aux[indx];
                                    if ( indx > 0 )
                                    {
                                        --indx;
                                    }
                                    else
                                    {
                                        done = true;
                                        break; /* done */
                                    }
                                }
                            }
                        }
                    }
                }
                median += span + span;
            }
            span += span;
        }
        delete aux;
    }
}

////////////////////////////////////////////////////////////////////////////
// createTable
////////////////////////////////////////////////////////////////////////////
/*
var cells = [
                [
                    [
                        className,
                        [
                            [className, colSpan, rowSpan, align, vAlign], //td
                            ........
                        ] // td collection
                    ] // tr
                    ............
                ] //body (tr collection)
                ...........
            ]; // tbody collection
*/
AP.createTable = function(cellSpacing, cellPadding, cells, elParent, className)
{
    var el = AP.createElement("table", elParent, className);
    el.cellSpacing = cellSpacing;
    el.cellPadding = cellPadding;
    
    for ( var loopBd = 0; loopBd < cells.length; loopBd++ )
    {
        var bd = AP.createElement("tbody");
        el.appendChild(bd);
        
        for ( var loopTr = 0; loopTr < cells[loopBd].length; loopTr++ )
        {
            var tr = bd.insertRow(bd.rows.length);
            if ( cells[loopBd][loopTr][0] ) tr.className = cells[loopBd][loopTr][0];
            
            for ( var loopTd = 0; loopTd < cells[loopBd][loopTr][1].length; loopTd++ )
            {
                var td = tr.insertCell(tr.cells.length);
                if ( cells[loopBd][loopTr][1][loopTd][0] ) td.className = cells[loopBd][loopTr][1][loopTd][0];
                if ( cells[loopBd][loopTr][1][loopTd][1] ) td.colSpan = cells[loopBd][loopTr][1][loopTd][1];
                if ( cells[loopBd][loopTr][1][loopTd][2] ) td.rowSpan = cells[loopBd][loopTr][1][loopTd][2];
                if ( cells[loopBd][loopTr][1][loopTd][3] ) td.align = cells[loopBd][loopTr][1][loopTd][3];
                if ( cells[loopBd][loopTr][1][loopTd][4] ) td.vAlign = cells[loopBd][loopTr][1][loopTd][4];
            }
        }
    }
    
    return el;
}

////////////////////////////////////////////////////////////////////////////
// _calcDispose
////////////////////////////////////////////////////////////////////////////
/*
var param = {
                type: "dropDownDispose" / "popupDispose", // disposition request type
                lValue: 0, // x/y value
                lOffsetParentWidth: 0, // element extended offset if need dispose
                lWidth: 0, // element width (cx/cy)
                lMinWidth: 0, // min element width (min cx/cy)
                lParentWidth: 0 // parent width
            }
*/
AP._calcDispose = function(param)
{
    var retValue = {lValue: param.lValue, lWidth: null};
    
    if ( param.type == "popupDispose" )
    {   //lValue, lWidth, lParentWidth
        var rDistance = param.lParentWidth - param.lValue;
        var lDistance = param.lValue;
        if ( rDistance < param.lWidth )
        {
            if ( lDistance < param.lWidth )
            {
                var rOffset = param.lWidth - rDistance;
                if ( rOffset > lDistance )
                {
                     retValue.lValue = 0;
                     retValue.lWidth = lDistance + rDistance;
                }
                else
                {
                    retValue.lValue -= rOffset;
                }
            }
            else
            {
                retValue.lValue -= param.lWidth;
            }
        }
    }
    else if ( param.type == "dropDownDispose" )
    {   //
        var rDistance = param.lParentWidth - param.lValue;
        var lDistance = param.lValue + param.lOffsetParentWidth;
        if ( rDistance < param.lWidth )
        {
            if ( lDistance < param.lWidth )
            {
                if ( lDistance > rDistance )
                {
                    retValue.lWidth = lDistance;
                    retValue.lValue -= retValue.lWidth;
                    retValue.lValue += param.lOffsetParentWidth;
                }
                else
                {
                    retValue.lWidth = rDistance;
                }
            }
            else
            {
                retValue.lValue -= param.lWidth;
                retValue.lValue += param.lOffsetParentWidth;
            }
        }
        else if ( param.lMinWidth && param.lWidth < param.lMinWidth )
        {
            retValue.lWidth = param.lMinWidth;
        }
    }
    
    return retValue;
}

////////////////////////////////////////////////////////////////////////////
// popup
////////////////////////////////////////////////////////////////////////////
/*
var param = {
                x: 0, // x value
                y: 0, // y value
                self: AP.object, // this object
                parent: node, // parent element
                flags: 0 // flags
            }
*/
AP.popup = function(param)
{
    param.flags = AP.is_set(param.flags, 0);
    
    AP.visible(param.self.element, false);
    AP.show(param.parent, true);

    var xDispParam = {  type: "popupDispose",
                        lValue: param.x,
                        lWidth: AP.offsetWidth(param.self.element),
                        lParentWidth: AP.clientWidth(param.parent) };
    var xDispRet = AP._calcDispose(xDispParam);
    
    var yDispParam = {  type: "popupDispose",
                        lValue: param.y,
                        lWidth: AP.offsetHeight(param.self.element),
                        lParentWidth: AP.clientHeight(param.parent) };
    var yDispRet = AP._calcDispose(yDispParam);
    
    param.self.setPosition({
        x: xDispRet.lValue,
        y: yDispRet.lValue,
        cx: xDispRet.lWidth,
        cy: yDispRet.lWidth
    });
    
    AP.visible(param.self.element, true);
}

////////////////////////////////////////////////////////////////////////////
// dropdown
////////////////////////////////////////////////////////////////////////////
/*
var param = {
                self: AP.object, // this element
                parent: node, // parent element
                offsetParent: parent, // offset parent element
                flags: flags // flags
            }
*/
AP.dropdown = function(param)
{
    var ddPos = {};
    
    param.flags = AP.is_set(param.flags, 0);
    
    //AP.visible(param.self, false);
    //AP.show(param.parent, true);
    
    var prntPos = AP.getAbsolutePos(param.offsetParent);
    var xDispParam = {  type: "dropDownDispose",
                        lValue: prntPos.x,
                        lOffsetParentWidth: AP.offsetWidth(param.offsetParent),
                        lWidth: AP.offsetWidth(param.self),
                        lMinWidth: AP.offsetWidth(param.offsetParent),
                        lParentWidth: AP.clientWidth(param.parent) };
    var xDispRet = AP._calcDispose(xDispParam);
    
    var yDispParam = {  type: "dropDownDispose",
                        lValue: prntPos.y + AP.offsetHeight(param.offsetParent),
                        lOffsetParentWidth: -AP.offsetHeight(param.offsetParent),
                        lWidth: AP.offsetHeight(param.self),
                        lParentWidth: AP.clientHeight(param.parent) };
    var yDispRet = AP._calcDispose(yDispParam);
    
    return {
        x: xDispRet.lValue,
        y: yDispRet.lValue,
        cx: xDispRet.lWidth,
        cy: yDispRet.lWidth
    };
}

////////////////////////////////////////////////////////////////////////////
// SubmitParams
////////////////////////////////////////////////////////////////////////////
/*
var submitParams = {
    url: url,
    target: windowName
    method: "get"/"post",
    postData: array[name, value]
}
*/
AP.submit = function(submitParams)
{
    if ( !submitParams.url )
        throw ("[AP::SUBMIT] Invalid URL");
    submitParams.method = AP.is_set(submitParams.method, "GET");
    
    if ( !submitParams.postData && (submitParams.method.toUpperCase() == "GET") )
    {
        submitParams.target = AP.is_set(submitParams.target, "_self");
        window.open(submitParams.url, submitParams.target);
    }
    else
    {
        var f = AP.createElement("form", document.body);
        
        f.action = submitParams.url;
        if ( submitParams.method )
        {
            f.method = submitParams.method;
        }
        if ( submitParams.target )
        {
            f.target = submitParams.target;
        }
        if ( submitParams.postData )
        {
            for ( var loop = 0; loop < submitParams.postData.length; loop++ )
            {
                if (typeof submitParams.postData[loop] == "object")
                {
                    var input = AP.createNamedElement({
                        tagName: "input",
                        parent: f,
                        type: "hidden",
                        name: submitParams.postData[loop][0]
                    });
                    input.value = submitParams.postData[loop][1];
                }
            }
        }
    
        f.submit();
        document.body.removeChild(f);
    }
}
//////////////////////////////////////////////////////////////////////////////
//// onFocus()
//////////////////////////////////////////////////////////////////////////////
//AP.onFocus = function(ev)
//{
//    ev || (ev = window.event);
//    var obj = (AP.isIE() ? ev.srcElement : ev.target);
//    var ap = AP.getInstance(obj);
//    
//    var a = ap ? ap.className : "???";
//    if ( ap )
//    {
//    }
//    
//    DebugLog.writeErr("onfocus", ap, a, obj.nodeName);
//    
//    return true;
//}
//AP.addEvent(document, "focus", AP.onFocus);
//AP.addEvent(document, "focusin", AP.onFocus);

////////////////////////////////////////////////////////////////////////////
// class APHttpRequest
////////////////////////////////////////////////////////////////////////////
APHttpRequest = function()
{
    this.req = AP.getXmlHttp();
    this.id = AP.uniqueId("_http_req_id_");
    this._notifyFunc = null;
    this._userData = null;
    this._state = 0;
    this._autoRelease = false;
    if ( !APHttpRequest._listReq )
    {
        APHttpRequest._listReq = {};
    }
    APHttpRequest._listReq[this.id] = this;
    setTimeout("APHttpRequest._onTimer(\"" + this.id + "\");", 10);
}
APHttpRequest._listReq = null;
APHttpRequest.createInstance = function()
{
    return new APHttpRequest();
}
APHttpRequest.destroyInstance = function(req)
{
    if ( APHttpRequest._listReq && APHttpRequest._listReq[req.id] )
    {
        delete APHttpRequest._listReq[req.id];
    }
    delete req;
}
APHttpRequest.prototype.readyState = function()
{
    return this._state;
}
APHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword)
{
    this._state = 0;
    return this.req.open(sMethod, sUrl, bAsync, sUser, sPassword);
}
APHttpRequest.prototype.send = function(data){return this.req.send(data);}
APHttpRequest.prototype.setRequestHeader = function(sHeader, sValue){return this.req.setRequestHeader(sHeader, sValue);}
APHttpRequest.prototype.abort = function()
{
    this._state = -1;
    return this.req.abort();
}
APHttpRequest.prototype.getAllResponseHeaders = function(){return this.req.getAllResponseHeaders();}
APHttpRequest.prototype.getResponseHeader = function(sHeader){return this.req.getResponseHeader(sHeader);}
APHttpRequest.prototype.responseText = function(){return this.req.responseText;}
APHttpRequest.prototype.responseXML = function(){return this.req.responseXML;}
APHttpRequest.prototype.readyState = function(){return this.req.readyState;}
APHttpRequest.prototype.statusText = function(){return this.req.statusText;}
APHttpRequest.prototype.sendRequest = function(params)
{
    params.req = this;
    return APHttpRequest.sendRequest(params);
}
APHttpRequest._onTimer = function(id)
{
    if ( !APHttpRequest._listReq || !APHttpRequest._listReq[id] )
    {
        // DEBUGINFO - DebugLog.write("stop...");
        return;
    }
    
    var req = APHttpRequest._listReq[id];
    if ( req._state != req.req.readyState )
    {
        if ( req._state != -1 ) // !aborted
        {
            req._state = req.req.readyState;
        }
        if ( req._notifyFunc )
        {
            req._notifyFunc(req, req._userData);
        }
        if ( req._state == -1 // aborted
             || req.req.readyState == 4 ) // completed
        {
            if ( req._autoRelease )
            {
                APHttpRequest.destroyInstance(req);
            }
        }
    }
    
    setTimeout("APHttpRequest._onTimer(\"" + id + "\");", 10);
}
////////////////////////////////////////////////////////////////////////////
// sendRequest
////////////////////////////////////////////////////////////////////////////
/*
var params = {
    req: APHttpRequest
    method: "GET" (default) / "POST",
    action*: url,
    data: array( pair<key, value> ) / { buffer: , length:  },
    hdr: array ( pair<key, value> ),
    async: true (default) / false,
    userName: string,
    password: string,
    func_onload: function () {
        
    },
    timeout: sec,
    nocache: true (default) / false,
    user_data: null
}
    * - required
*/
APHttpRequest.sendRequest = function(params)
{
    if ( !AP.is_set(params.action) )
    {
        return false;
    }
    if ( AP.is_set(params.req) )
    {
        params.req = params.req;
    }
    else
    {
        params.req = APHttpRequest.createInstance();
        params.req._autoRelease = true;
    }
    params.method = AP.is_set(params.method, "GET");
    params.data = AP.is_set(params.data, []);
    params.async = AP.is_set(params.async, true);
    params.nocache = AP.is_set(params.nocache, true);
    
    params.req._notifyFunc = AP.is_set(params.func_onload) ? params.func_onload : null;
    params.req._userData = AP.is_set(params.user_data) ? params.user_data : null;

    var url = params.action;
    var data = params.data.length ? "" : null;
    for ( var loop = 0; loop < params.data.length; loop++ )
    {
        if ( data.length )
        {
            data += "&";
        }
        data += params.data[loop][0];
        data += "=";
        data += params.data[loop][1];
    }
    if ( data && data.length )
    {

        var curDate = new Date();
        var timezoneOffset = curDate.getTimezoneOffset()/60;

        var H = AP.LZ(curDate.getHours());
        var i = AP.LZ(curDate.getMinutes());
        var s = AP.LZ(curDate.getSeconds());
        
        var curTime = H+":"+i+":"+s;

        //INPUT_TIMEZONE_OFFSET
        data += "&";
        data += "timezone_offset";
        data += "=";
        data += timezoneOffset;

        //INPUT_USER_TIME
        data += "&";
        data += "user_time";
        data += "=";
        data += curTime;

        data += "&";
        data += AP.getTickCount();
        
        if ( params.method.toUpperCase() == "GET" )
        {
            url += "?";
            url += data;
            data = null;
        }
    }
    
    params.req.open(params.method,
                    url,
                    params.async,
                    params.userName,
                    params.password);
    
    if ( params.method.toUpperCase() == "POST" )
    {
        if ( AP.is_set(data) )
        {
            // POST
            params.req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            params.req.setRequestHeader("Content-Length", data.length);
        }
        else
        {
            params.req.setRequestHeader("Content-Length", 0);
        }
    }
    
    if ( AP.is_set(params.hdr) && params.hdr.length )
    {
        for ( var loop = 0; loop < params.hdr.length; loop++ )
        {
            params.req.setRequestHeader(params.hdr[loop][0], params.hdr[loop][1]);
        }
    }
    
    params.req.send(data);
    
    return params.req;
}

////////////////////////////////////////////////////////////////////////////
// class ImgButton extends AP
////////////////////////////////////////////////////////////////////////////
/*
<div class="img_button">
    <input name=%name% type="hidden">
    <table class="img_btn_body" cellspacing="0" cellpadding="0">
        <tr>
            <td class="img_btn_body_td" align="center/left/right" valign="top/middle/bottom">
                <table cellspacing="0" cellpadding="0">
                    <tr><td align="center" vAlign="middle" class="img_btn_image">
                        <img>
                    </td></tr>
                    <tr><td align="center" vAlign="middle" class="img_btn_text">
                        %text%
                    </td></tr>
                </table>
            </td>
        </tr>
    </table>
</div>
*/
var IMGBTN_FLAGS_DISABLED       = 0x0001;
var IMGBTN_FLAGS_AUTOSTATE      = 0x0002;
var IMGBTN_FLAGS_DYNAMIC        = 0x0004;
var IMGBTN_FLAGS_SUBMITSTATE    = 0x0008;
var IMGBTN_FLAGS_IMAGELINE      = 0x0010;
// alignment
var IMGBTN_FLAGS_ALIGN_CENTER   = 0x0000;
var IMGBTN_FLAGS_ALIGN_LEFT     = 0x0100;
var IMGBTN_FLAGS_ALIGN_RIGHT    = 0x0200;
var IMGBTN_FLAGS_VALIGN_CENTER  = 0x0000;
var IMGBTN_FLAGS_VALIGN_TOP     = 0x0400;
var IMGBTN_FLAGS_VALIGN_BOTTOM  = 0x0800;
// button states
var BTS_OVER                    = 0x0001;
var BTS_DOWN                    = 0x0002;
var BTS_CHECKED                 = 0x0004;
var BTS_INDETERMINATE           = 0x0008;

var IMG_BTN_IMAGE_LIST_CHECK = {
    base: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/check_.gif"},
    over: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/checkover_.gif"},
    down: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/checkover_.gif"},
    
    checked: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/check_v.gif"},
    overchecked: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/checkover_v.gif"},
    downchecked: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/checkover_v.gif"},

    indeterminate: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/check_x.gif"},
    overindeterminate: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/checkover_x.gif"},
    downindeterminate: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/checkover_x.gif"},

    disabled: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/checkdis_.gif"},
    checkeddisabled: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/checkdis_v.gif"},
    indeterminatedisabled: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/checkdis_x.gif.gif"}
};
var IMG_BTN_IMAGE_LIST_RADIO = {
    base: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/radio_.gif"},
    over: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/radioover_.gif"},
    down: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/radioover_.gif"},

    checked: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/radio_v.gif"},
    overchecked: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/radioover_v.gif"},
    downchecked: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/radioover_v.gif"},

    disabled: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/radiodis_.gif"},
    checkeddisabled: {cx: 16, cy: 16, src: ROOT_PATH + "img/btn/radiodis_v.gif"}
};
// ImgButton constructor
////////////////////////////////////////////////////////////////////////////
ImgButton = function()
{
    this.subclass = AP;
    this.subclass();
    
    this.images = {
        base: null,
        over: null,
        down: null,
        
        checked: null,
        overchecked: null,
        downchecked: null,
        
        indeterminate: null,
        overindeterminate: null,
        downindeterminate: null,

        disabled: null,
        checkeddisabled: null,
        indeterminatedisabled: null
    }

    this.className = "ImgButton";
}
ImgButton.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// destroy
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype.destroy = function()
{
    this.subclass = AP.prototype.destroy;
    this.subclass();
    //TODO:
}

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype.create = function(elParent, flags, tabIndex, name)
{
    this.subclass = AP.prototype.create;
    this.subclass(elParent, "img_button");
    
    this.flags = AP.is_set(flags, 0);

    this.input = null;
    if ( this.flags & IMGBTN_FLAGS_SUBMITSTATE )
    {
        if ( !AP.is_set(name) )
        {
            this.flags &= ~IMGBTN_FLAGS_SUBMITSTATE;
        }
        else
        {
            this.input = AP.createNamedElement({
                tagName: "input",
                name: name,
                parent: this.element,
                type: "hidden"
            });
            this.input.value = 0;
        }
    }
    
    // img_btn body
    var align = "center";
    if ( this.flags & IMGBTN_FLAGS_ALIGN_LEFT )
    {
        align = "left";
    }
    else if ( this.flags & IMGBTN_FLAGS_ALIGN_RIGHT )
    {
        align = "right";
    }
    var vAlign = "middle";
    if ( this.flags & IMGBTN_FLAGS_VALIGN_TOP )
    {
        vAlign = "top";
    }
    else if ( this.flags & IMGBTN_FLAGS_VALIGN_BOTTOM )
    {
        vAlign = "bottom";
    }
    var cells = [[
                    [
                        null,
                        [["img_btn_body_td", null, null, align, vAlign]]
                    ]
                ]];
    this.tblBody = AP.createTable(0, 0, cells, this.element, "img_btn_body");
    var elBody = this.tblBody.tBodies[0].rows[0].cells[0];
    
    // image & text container
    var cells = null;
    if ( this.flags & IMGBTN_FLAGS_IMAGELINE )
    {   // 1tr, 2td
        cells = [[
                    [
                        null,
                        [
                            ["img_btn_image", null, null, "center", "middle"],
                            ["img_btn_text", null, null, "center", "middle"]
                        ]
                    ]
                ]];
    }
    else
    {   // 2tr, 1td
        cells = [[
                    [
                        null,
                        [["img_btn_image", null, null, "center", "middle"]]
                    ],
                    [
                        null,
                        [["img_btn_text", null, null, "center", "middle"]]
                    ]
                ]];
    }
    this.tblImgText = AP.createTable(0, 0, cells, elBody, null);
    
    // image
    this.tdImg = this.tblImgText.tBodies[0].rows[0].cells[0];
    this.img = AP.createElement("img", this.tdImg);
    AP.show(this.tdImg, false);
    
    // text
    if ( this.flags & IMGBTN_FLAGS_IMAGELINE )
    {
        this.tdText = this.tblImgText.tBodies[0].rows[0].cells[1];
    }
    else
    {
        this.tdText = this.tblImgText.tBodies[0].rows[1].cells[0];
    }
    this.spanText = AP.createElement("span", this.tdText, "img_btn_text");
    
    this.tabIndex( AP.is_set(tabIndex, 0) );

    if ( this.flags & IMGBTN_FLAGS_DYNAMIC )
    {
        AP.addEvent(this.element, "mouseover", ImgButton.onEvent);
        AP.addEvent(this.element, "mouseout", ImgButton.onEvent);
        AP.addEvent(this.element, "mousedown", ImgButton.onEvent);
        AP.addEvent(this.element, "mouseup", ImgButton.onEvent);
    }
    AP.addEvent(this.element, "click", ImgButton.onEvent);
    AP.addEvent(this.element, "keypress", ImgButton.onEvent);

    //this.setPosition();
}

////////////////////////////////////////////////////////////////////////////
// getName
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype.getName = function()
{
    return ( (this.input.name && this.input.name.length) ? this.input.name : null );
}

////////////////////////////////////////////////////////////////////////////
// ImgButton tabIndex get / set tabIndex
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype.tabIndex = function(tabIndex)
{
    if ( AP.is_set(tabIndex) !== false )
    {
        this.spanText.tabIndex = tabIndex;
    }
    return this.spanText.tabIndex;
}

////////////////////////////////////////////////////////////////////////////
// image - get/set image
////////////////////////////////////////////////////////////////////////////
/*
1. img - path
2. img - struct {
    src: path,
    cx: image width
    cy: image height
}
3. img - struct {
    base: img (1 or 2),
    over: img,
    down: img,
    
    checked: img,
    overchecked: img,
    downchecked: img,
    
    indeterminate: img,
    overindeterminate: img,
    downindeterminate: img,

    disabled: img,
    checkeddisabled: img,
    indeterminatedisabled: img
}
*/
ImgButton.prototype.image = function(img)
{
    if ( AP.is_set(img) )
    {
        if ( typeof img == "string" )
        // 1.
        {
            this.images.base = {
                src: img,
                cx: "auto",
                cy: "auto"
            };
        }
        else if ( img.src )
        // 2.
        {
            this.images.base = {
                src: img.src,
                cx: (img.cx ? img.cx : "auto"),
                cy: (img.cy ? img.cy : "auto")
            };
        }
        else
        // 3.
        {
            // Base
            var imgObj = this._set_image(img.base);
            if ( imgObj !== false )
            {
                this.images.base = imgObj;
            }
            var imgObj = this._set_image(img.over);
            if ( imgObj !== false )
            {
                this.images.over = imgObj;
            }
            var imgObj = this._set_image(img.down);
            if ( imgObj !== false )
            {
                this.images.down = imgObj;
            }
            
            // Checked
            var imgObj = this._set_image(img.checked);
            if ( imgObj !== false )
            {
                this.images.checked = imgObj;
            }
            var imgObj = this._set_image(img.overchecked);
            if ( imgObj !== false )
            {
                this.images.overchecked = imgObj;
            }
            var imgObj = this._set_image(img.downchecked);
            if ( imgObj !== false )
            {
                this.images.downchecked = imgObj;
            }
            
            // Indeterminate
            var imgObj = this._set_image(img.indeterminate);
            if ( imgObj !== false )
            {
                this.images.indeterminate = imgObj;
            }
            var imgObj = this._set_image(img.overindeterminate);
            if ( imgObj !== false )
            {
                this.images.overindeterminate = imgObj;
            }
            var imgObj = this._set_image(img.downindeterminate);
            if ( imgObj !== false )
            {
                this.images.downindeterminate = imgObj;
            }
            
            // Disabled
            var imgObj = this._set_image(img.disabled);
            if ( imgObj !== false )
            {
                this.images.disabled = imgObj;
            }
            var imgObj = this._set_image(img.checkeddisabled);
            if ( imgObj !== false )
            {
                this.images.checkeddisabled = imgObj;
            }
            var imgObj = this._set_image(img.indeterminatedisabled);
            if ( imgObj !== false )
            {
                this.images.indeterminatedisabled = imgObj;
            }
        }
    }
    
    this._stateToClassName();
}

////////////////////////////////////////////////////////////////////////////
// _set_image
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype._set_image = function(input)
{
    var output = false;
    
    if ( typeof input != "undefined" )
    {
        if ( input )
        {
            output = {};
            if ( typeof input == "string" )
            {
                output.src = input;
                output.cx = "auto";
                output.cy = "auto";
            }
            else
            {
                if ( input.src )
                {
                    output.src = input.src;
                    output.cx = "auto";
                    output.cy = "auto";
                }
                if ( input.cx )
                {
                    output.cx = input.cx;
                }
                if ( input.cy )
                {
                    output.cy = input.cy;
                }
            }
        }
        else
        {
            output = null;
        }
    }
    
    return output;
}

////////////////////////////////////////////////////////////////////////////
// image (internal function)
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype._image = function(img)
{
    var cx = 0;
    var cy = 0;
    
    if ( AP.is_set(img) )
    {
        var src = img.src ? img.src : img;
        if ( this.img.src != src )
        {
            this.img.src = src;
            
            if ( src.length != 0 )
            {
                cx = "auto";
                cy = "auto";
                AP.show(this.tdImg, true);
                
                if ( img.cx )
                {
                    cx = img.cx;
                }
                if ( img.cy )
                {
                    cy = img.cy;
                }
            }
            else
            {
                AP.show(this.tdImg, false);
            }
        }
    }
    
    AP.width(this.img, cx);
    AP.height(this.img, cy);
    //this.setPosition();
}

////////////////////////////////////////////////////////////////////////////
// text - get/set Button Text
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype.text = function(newText)
{
    if ( AP.is_set(newText) )
    {
        this.spanText.innerHTML = newText;
    }
    return this.spanText.innerHTML;
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype.setPosition = function(pos)
{
    this.subclass = AP.prototype.setPosition;
    this.subclass(pos);
}

////////////////////////////////////////////////////////////////////////////
// check
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype.check = function(checkState)
{
    var state = this.state();
    
    state &= ~BTS_CHECKED;
    state &= ~BTS_INDETERMINATE;
    
    if ( checkState == BTS_CHECKED )
    {
        state |= BTS_CHECKED;
    }
    else if ( checkState == BTS_INDETERMINATE )
    {
        state |= BTS_INDETERMINATE;
    }
    
    if ( this.state() != state )
    {
        this.state(state);
    }
    
    return false;
}

////////////////////////////////////////////////////////////////////////////
// checked
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype.checked = function(check)
{
    checkState = this.state() & (BTS_CHECKED | BTS_INDETERMINATE);
    return checkState;
}

////////////////////////////////////////////////////////////////////////////
// _stateToClassName
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype._stateToClassName = function()
{
    var className = "ap img_button";
    var img = (this.images.base ? this.images.base : null);
    if ( this._state & BTS_CHECKED )
    {
        className += " img_button_checked";
        if ( this.images.checked )
        {
            img = this.images.checked;
        }
    }
    else if ( this._state & BTS_INDETERMINATE )
    {
        className += " img_button_indeterminate";
        if ( this.images.indeterminate )
        {
            img = this.images.indeterminate;
        }
    }
    if ( this._state & APS_DISABLED )
    {
        className += " img_button_disabled";
        if ( (this._state & BTS_CHECKED) && this.images.checkeddisabled )
        {
            img = this.images.checkeddisabled;
        }
        else if ( (this._state & BTS_INDETERMINATE) && this.images.indeterminatedisabled )
        {
            img = this.images.indeterminatedisabled;
        }
        else if ( this.images.disabled )
        {
            img = this.images.disabled;
        }
    }
    else
    {
        if ( this._state & BTS_OVER )
        {
            className += " img_button_over";
            if ( (this._state & BTS_CHECKED) && this.images.overchecked )
            {
                img = this.images.overchecked;
            }
            else if ( (this._state & BTS_INDETERMINATE) && this.images.overindeterminate )
            {
                img = this.images.overindeterminate;
            }
            else if ( this.images.over )
            {
                img = this.images.over;
            }
        }
        if ( this._state & BTS_DOWN )
        {
            className += " img_button_down";
            if ( (this._state & BTS_CHECKED) && this.images.downchecked )
            {
                img = this.images.downchecked;
            }
            else if ( (this._state & BTS_INDETERMINATE) && this.images.downindeterminate )
            {
                img = this.images.downindeterminate;
            }
            else if ( this.images.down )
            {
                img = this.images.down;
            }
        }
    }
    if ( this.element.className != className )
    {
        this.element.className = className;
    }
    this._image(img);
}

////////////////////////////////////////////////////////////////////////////
// width
////////////////////////////////////////////////////////////////////////////
ImgButton.prototype.width = function()
{
    return AP.offsetWidth(this.element);
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
ImgButton.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && ImgButton.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// onEvent
////////////////////////////////////////////////////////////////////////////
ImgButton.onEvent = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.currentTarget);
    var btn = ImgButton.getInstance(obj);
    if ( !btn )
        return true;

    if ( btn.enable() ==  false )
    {
        return true;
    }
    
    if ( ev.type == "mouseover" )
    {
        btn.state( btn.state() | BTS_OVER );
    }
    else if ( ev.type == "mouseout" )
    {
        btn.state( btn.state() & ~(BTS_OVER | BTS_DOWN) );
    }
    else if ( ev.type == "mousedown" )
    {
        btn.state( btn.state() | BTS_DOWN );        
    }
    else if ( ev.type == "mouseup" )
    {
        btn.state( btn.state() & ~BTS_DOWN );
    }
    else if ( ev.type == "click" )
    {
        if ( btn.flags & IMGBTN_FLAGS_AUTOSTATE )
        {
            btn.check( (btn.state() & BTS_CHECKED) ? 0 : BTS_CHECKED );
        }
        btn.fireNotify(BTN_CLICKED);
    }
    else if ( ev.type == "keypress" )
    {
        var keyInfo = {
            code: ev.charCode || ev.keyCode,
            ctrl: ev.ctrlKey,
            shift: ev.shiftKey,
            alt: ev.altKey
        }
        if ( btn.fireNotify(BTN_KEYPRESSED, keyInfo) != -1 )
        {
            if ( keyInfo.code == KEY_CODE_SPACE )
            {
                if ( btn.flags & IMGBTN_FLAGS_AUTOSTATE )
                {
                    btn.check( (btn.state() & BTS_CHECKED) ? 0 : BTS_CHECKED );
                }
                btn.fireNotify(BTN_CLICKED);
            }
        }
    }

    return true;    
}

////////////////////////////////////////////////////////////////////////////
// class ToolBar extends AP
////////////////////////////////////////////////////////////////////////////
// ToolBar DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="tool_bar">
    <div class="tb_move_left">
    </div>
    <div class="tb_work_area">
        <div class="tb_buttons_container">
            ImgButton
            ......
        </div>
    </div>
    <div class="tb_move_right">
    </div>
</div>
*/
var TB_FLAGS_SCROLL       = 0x01;
var TB_FLAGS_LINEBUTTONS  = 0x02;
////////////////////////////////////////////////////////////////////////////
// ToolBar constructor
////////////////////////////////////////////////////////////////////////////
ToolBar = function()
{
    this.subclass = AP;
    this.subclass();
    
    this.className = "ToolBar";
}
ToolBar.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.create = function(elParent, flags, tabIndex)
{
    this.subclass = AP.prototype.create;
    this.subclass(elParent, "tool_bar");

    this.flags = AP.is_set(flags, 0);

    if ( this.flags & TB_FLAGS_SCROLL )
    {
        this.moveLeft = AP.createElement("div", this.element, "tb_move_left");
    }

    this.divWork = AP.createElement("div", this.element, "tb_work_area");
    this.divBtnContainer = AP.createElement("div", this.divWork, "tb_buttons_container");

    if ( this.flags & TB_FLAGS_SCROLL )
    {
        this.moveRight = AP.createElement("div", this.element, "tb_move_right");
    }

    this.buttons = new Array();

    this.tabIndex( AP.is_set(tabIndex, 0) );
}

////////////////////////////////////////////////////////////////////////////
// get/set tabIndex
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.tabIndex = function(tabIndex)
{
    if ( AP.is_set(tabIndex) !== false )
    {
        var nextTabIndex = tabIndex;

        this.element.tabIndex = nextTabIndex && nextTabIndex++;
        if ( AP.is_set(this.moveLeft) )
        {
            this.moveLeft.tabIndex = nextTabIndex && nextTabIndex++;
        }
        
        for ( var loop = 0; loop < this.buttons.length; loop++  )
        {
            this.buttons[loop].tabIndex( nextTabIndex && nextTabIndex++ );
        }
        
        if ( AP.is_set(this.moveLeft) )
        {
            this.moveRight.tabIndex = nextTabIndex && nextTabIndex++;
        }
    }
    
    return AP.is_set(this.element.tabIndex, 0);
}

////////////////////////////////////////////////////////////////////////////
// appendButton
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.appendButton = function(btn)
{
    btn.index = this.getButtonCount();
    return this.insertButton(btn);
}

////////////////////////////////////////////////////////////////////////////
// insertButton
////////////////////////////////////////////////////////////////////////////
/*
var btn = {
    index: integer,
    text: string,
    img: string,
    width: integer,
    flags: integer,
    param: user value
}
*/
ToolBar.prototype.insertButton = function(btn)
{
    // index, text, img, width, flags
    btn.text = AP.is_set(btn.text, "");
    btn.width = AP.is_set(btn.width, 80);
    
    var btnFlags = AP.is_set(btn.flags, 0);
    
    if ( this.flags & TB_FLAGS_LINEBUTTONS )
    {
        btnFlags |= IMGBTN_FLAGS_IMAGELINE;
    }
    else
    {
        btnFlags &= ~IMGBTN_FLAGS_IMAGELINE;
    }
    var button = new ImgButton();
    button.usrParam = btn.param;
    button.create(null, btnFlags);
    
    if ( btn.index >= this.getButtonCount() )
    {
        this.buttons.push(button);
        this.divBtnContainer.appendChild(button.element);
        btn.index = this.getButtonCount() - 1;
    }
    else
    {
        var arrTmp = new Array();
        var btnCount = this.getButtonCount();
        for ( var loop = 0; loop < btnCount; loop++ )
        {
            if ( loop == btn.index )
            {
                arrTmp.push(button);
                this.divBtnContainer.insertBefore(button.element, this.buttons[loop]);
            }
            arrTmp.push(this.buttons[loop]);
        }
        this.buttons = arrTmp;
    }

    button.setPosition({ cx: btn.width });
    if ( AP.is_set(btn.img) )
    {
        button.image(btn.img);
    }
    button.text(btn.text);

    this.tabIndex(this.tabIndex());
    this._calcButtonsPos(btn.index);
    button.addNotifyHandler( [ToolBar.onBtnNotify, this] );

    this.fireNotify(TBN_BUTTON_INSERTED, btn.index);
}

////////////////////////////////////////////////////////////////////////////
// deleteButton
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.deleteButton = function(index)
{
    var btn = this.getButton(index);
    AP.destroyInstance(btn);
    this.buttons.splice(index, 1);
    
    this.tabIndex(this.tabIndex());
    this._calcButtonsPos(index);
    
    this.fireNotify(TBN_BUTTON_DELETED, index);
}

////////////////////////////////////////////////////////////////////////////
// deleteAllButtons
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.deleteAllButtons = function()
{
    while ( this.getButtonCount() )
    {
        this.deleteButton( this.getButtonCount() - 1 );
    }
}

////////////////////////////////////////////////////////////////////////////
// getButton
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.getButton = function(index)
{
    return this.buttons[index];
}

////////////////////////////////////////////////////////////////////////////
// btnState
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.btnState = function(index, state)
{
    var btn = this.getButton(index);
    return btn.state(state);
}

////////////////////////////////////////////////////////////////////////////
// btnCheck
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.btnCheck = function(index, check)
{
    var btn = this.getButton(index);
    return btn.check(check);
}

////////////////////////////////////////////////////////////////////////////
// btnChecked
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.btnChecked = function(index)
{
    var btn = this.getButton(index);
    return btn.checked();
}

////////////////////////////////////////////////////////////////////////////
// getButtonCount
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.getButtonCount = function()
{
    return this.buttons.length;
}

////////////////////////////////////////////////////////////////////////////
// _calcButtonsPos
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype._calcButtonsPos = function(fromIndex)
{
    var btnWidth = 0;
    
    var st = AP.getStyle(this.divBtnContainer);
    var padding = parseInt(st.paddingLeft);
    fromIndex = AP.is_set(fromIndex, 0);
    
    var btnHeight = AP.clientHeight(this.divBtnContainer);
    for ( var loop = 0/*fromIndex*/; loop < this.getButtonCount(); loop++ )
    {
        var button = this.getButton(loop);
        button.setPosition({ x: btnWidth, cy: btnHeight });
        btnWidth += button.width() + padding;
    }
    
    AP.width(this.divBtnContainer, Math.max(10, btnWidth));

    return Math.max( 0, AP.offsetWidth(this.divBtnContainer) - AP.clientWidth(this.element) );
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
ToolBar.prototype.setPosition = function(pos)
{
    this.subclass = AP.prototype.setPosition;
    this.subclass(pos);
    
    var szChanged = false;
    if ( (AP.is_set(pos.cx) !== false) || (AP.is_set(pos.cy) !== false) )
    {
        szChanged = true;
    }
    
    if ( szChanged )
    {
        var overflow = this._calcButtonsPos();
        if ( this.flags & TB_FLAGS_SCROLL )
        {
            if ( overflow )
            {
                AP.show(this.moveLeft, true);
                AP.show(this.moveRight, true);
            }
            else
            {
                AP.show(this.moveLeft, false);
                AP.show(this.moveRight, false);
            }
        }
        
        this.divWork.style.left = "0px"; // Scrolling TODO
        this.divWork.style.width = AP.clientWidth(this.element) + "px"; // Scrolling TODO
    }
    
    if ( AP.is_set(pos.scrollX) !== false )
    {
        // TODO
    }
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
ToolBar.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && ToolBar.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// onBtnNotify
////////////////////////////////////////////////////////////////////////////
ToolBar.onBtnNotify = function( param )
{
    var index = -1;
    for ( var loop = 0; loop < param.subscriber.getButtonCount(); loop++ )
    {
        if ( param.subscriber.getButton(loop) == param.producer )
        {
            index = loop;
            break;
        }
    }
    
    if ( index != -1 )
    {
        if ( param.code == BTN_CLICKED
             || (param.code == BTN_KEYPRESSED && param.param.code == KEY_CODE_ENTER) )
        {
            param.subscriber.fireNotify(TBN_BUTTON_CLICKED, index);
        }
        else if ( param.code == APN_STATE_CHANGED )
        {
            param.param.index = index;
            param.subscriber.fireNotify(APN_STATE_CHANGED, param.param);
        }
    }
    return 0;
}

////////////////////////////////////////////////////////////////////////////
// class Splitter extends AP
////////////////////////////////////////////////////////////////////////////
// Splitter DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="splitter">
</div>
*/
var SPLT_HORIZONTAL      = 0;
var SPLT_VERTICAL        = 1;
var SPLT_MOVE            = 2;
var SPLT_RESIZE          = 3;
////////////////////////////////////////////////////////////////////////////
// Splitter constructor
////////////////////////////////////////////////////////////////////////////
Splitter = function()
{
    if ( Splitter.element )
        throw ("Splitter already exist");
    Splitter.element = this;

    this.subclass = AP;
    this.subclass();

    this.className = "Splitter";
}
Splitter.prototype = new AP;
Splitter.g_Instance = null;

////////////////////////////////////////////////////////////////////////////
// createInstance
////////////////////////////////////////////////////////////////////////////
Splitter.getGInstance = function()
{
    if ( !Splitter.g_Instance )
    {
        Splitter.g_Instance = new Splitter();
        Splitter.g_Instance.create();
    }
    return Splitter.g_Instance;
}

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
Splitter.prototype.create = function()
{
    this.subclass = AP.prototype.create;
    this.subclass(document.body, "splitter"); // see className in start Drag

    AP.show(this.element, false);

    if ( AP.isIE() )
    {
        AP.addEvent(document.body, "mousemove", Splitter.onDragging);
        AP.addEvent(document.body, "mouseup", Splitter.onEndDrag);
        AP.addEvent(document.body, "mouseover", Splitter.stopEvent);
        AP.addEvent(document.body, "mouseout", Splitter.stopEvent);
    }
    else
    {
        AP.addEvent(window, "mousemove", Splitter.onDragging);
        AP.addEvent(window, "mouseup", Splitter.onEndDrag);
        AP.addEvent(window, "mouseover", Splitter.stopEvent);
        AP.addEvent(window, "mouseout", Splitter.stopEvent);
    }
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
Splitter.prototype.setPosition = function(pos)
{
    this.subclass = AP.prototype.setPosition;
    this.subclass(pos);
}

////////////////////////////////////////////////////////////////////////////
// startDrag
////////////////////////////////////////////////////////////////////////////
/*
Struct startDrag {
    x: integer,                --|- start position
    y: integer,                --|
    cx: integer,               --|
    cy: integer,               --|
    type: one of next values (SPLT_HORIZONTAL, SPLT_VERTICAL,
                              SPLT_MOVE, SPLT_RESIZE) -- splitter type
    className: string          -- css class postfix
    dtLeft: integer,           --|- max dragging delta
    dtRight: integer,          --|
    dtTop: integer,            --|
    dtBottom: integer,         --|
    clientX: ev.clientX,       --|- mouse position (for internal use)
    clientY: ev.clientY,       --|
    notifyHandler:             -- callBackProc same as for std notifyHandler
    param: user data           -- user data
}
*/
Splitter.prototype.startDrag = function(start_drag)
{
    if ( this.dragging )
        return;
    
    this.dragging = true;
    
    this.type = start_drag.type;
    this.element.className = "ap " + AP.is_set(start_drag.className, "splitter");
    this.dtLeft = AP.is_set(start_drag.dtLeft, 0);
    this.dtRight = AP.is_set(start_drag.dtRight, 0);
    this.dtTop = AP.is_set(start_drag.dtTop, 0);
    this.dtBottom = AP.is_set(start_drag.dtBottom, 0);
    this.param = AP.is_set(start_drag.param, 0);
    
    var spltPos = {
        x: start_drag.x,
        y: start_drag.y,
        cx: start_drag.cx,
        cy: start_drag.cy
    };
    this.setPosition( spltPos );

    if ( AP.isIE() )
    {
        document.body.setCapture();
    }
    else
    {
        window.captureEvents(Event.MOUSEDOWN | Event.MOUSEMOVE | Event.MOUSEUP);
    }
    
    this.old_X = start_drag.x;
    this.old_Y = start_drag.y;
    this.old_CX = start_drag.cx;
    this.old_CY = start_drag.cy;
    this.old_clientX = start_drag.clientX;
    this.old_clientY = start_drag.clientY;

    if ( this.type == SPLT_HORIZONTAL )
    {
        document.body.style.cursor = "col-resize";
    }
    else if ( this.type == SPLT_VERTICAL )
    {
        document.body.style.cursor = "row-resize";
    }
    else if ( this.type == SPLT_MOVE )
    {
        document.body.style.cursor = "move";
    }
    else if ( this.type == SPLT_RESIZE )
    {
        document.body.style.cursor = "se-resize";
    }

    this.notifyHandlers.splice(0, this.notifyHandlers.length); // remove All Handlers
    if ( AP.is_set(start_drag.notifyHandler) )
    {
        this.addNotifyHandler(start_drag.notifyHandler);
    }

    AP.show(this.element, true);
}

////////////////////////////////////////////////////////////////////////////
// doDrag
////////////////////////////////////////////////////////////////////////////
Splitter.prototype.doDrag = function(clientX, clientY)
{
    var dtX = clientX - this.old_clientX;
    var dtY = clientY - this.old_clientY;

    if ( this.type == SPLT_HORIZONTAL )
    {
        if ( dtX >= 0 )
        {
            dtX = Math.min(dtX, this.dtRight);
        }
        else
        {
            dtX = -Math.min(Math.abs(dtX), this.dtLeft);
        }
        this.setPosition({
            x: (this.old_X + dtX),
            y: this.old_Y
        });
    }
    else if ( this.type == SPLT_VERTICAL )
    {
        if ( dtY >= 0 )
        {
            dtY = Math.min(dtY, this.dtRight);
        }
        else
        {
            dtY = -Math.min(Math.abs(dtY), this.dtLeft);
        }
        this.setPosition({
            x: this.old_X,
            y: (this.old_Y + dtY)
        });
    }
    else if ( this.type == SPLT_MOVE
              || this.type == SPLT_RESIZE )
    {
        if ( dtX >= 0 )
        {
            dtX = Math.min(dtX, this.dtRight);
        }
        else
        {
            dtX = -Math.min(Math.abs(dtX), this.dtLeft);
        }
        if ( dtY >= 0 )
        {
            dtY = Math.min(dtY, this.dtBottom);
        }
        else
        {
            dtY = -Math.min(Math.abs(dtY), this.dtTop);
        }
        if ( this.type == SPLT_MOVE )
        {
            this.setPosition({
                x: (this.old_X + dtX),
                y: (this.old_Y + dtY)
            });
        }
        else if ( this.type == SPLT_RESIZE )
        {
            this.setPosition({
                cx: (this.old_CX + dtX),
                cy: (this.old_CY + dtY)
            });
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// endDrag
////////////////////////////////////////////////////////////////////////////
Splitter.prototype.endDrag = function()
{
    if ( AP.isIE() )
    {
        document.body.releaseCapture();
    }
    else
    {
        window.releaseEvents(Event.MOUSEDOWN | Event.MOUSEMOVE | Event.MOUSEUP);
    }

    // calculate offset
    var delta = { x: 0, y: 0, cx: 0, cy: 0 };
    var st = AP.getStyle(this.element);
    delta.x = parseInt(st.left) - this.old_X;
    delta.y = parseInt(st.top) - this.old_Y;
    delta.cx = AP.offsetWidth(this.element) - this.old_CX;
    delta.cy = AP.offsetHeight(this.element) - this.old_CY;
    
    // hide splitter
    document.body.style.cursor = "auto";
    AP.show(this.element, false);
    this.dragging = false;
    
    // notify
    this.fireNotify(SPLTN_POS_CHANGED, delta);
}

////////////////////////////////////////////////////////////////////////////
// onDragging
////////////////////////////////////////////////////////////////////////////
Splitter.onDragging = function(ev)
{
    ev || (ev = window.event);
    var splt = Splitter.element;
    if ( !splt || !splt.dragging )
    {
        return true;
    }
    
    splt.doDrag(ev.clientX, ev.clientY);
    
    return AP.stopEvent(ev);
}

////////////////////////////////////////////////////////////////////////////
// stopEvent
////////////////////////////////////////////////////////////////////////////
Splitter.stopEvent = function(ev)
{
    ev || (ev = window.event);
    var splt = Splitter.element;
    if ( !splt || !splt.dragging )
    {
        return true;
    }
    return AP.stopEvent(ev);
}

////////////////////////////////////////////////////////////////////////////
// onEndDrag
////////////////////////////////////////////////////////////////////////////
Splitter.onEndDrag = function(ev)
{
    ev || (ev = window.event);
    var splt = Splitter.element;
    if ( !splt || !splt.dragging )
    {
        return true;
    }

    splt.endDrag();

    return AP.stopEvent(ev);
}

//-- Example
//var splt = new Splitter();
//function startDrag(ev)
//{
//    ev || (ev = window.event);
//    
//    var start_drag = {
//        x: 150,
//        y: 0,
//        cx: 3,
//        cy: 300,
//        type: SPLT_HORIZONTAL,
//        dtLeft: 50,
//        dtRight: 100,
//        clientX: ev.clientX,
//        clientY: ev.clientY,
//        param: null
//    };
//    splt.startDrag(start_drag);
//    return true;
//}
//function onEnd(splt, param, delta)
//{
//    if ( param == SPLTN_POS_CHANGED )
//    {
//        DebugLog.write(delta.x + " - " + delta.y);
//    }
//}
//
//splt.create();
//splt.addNotifyHandler(onEnd);
//AP.addEvent(document.body, "mousedown", startDrag);

////////////////////////////////////////////////////////////////////////////
// class WndBorder extends AP
////////////////////////////////////////////////////////////////////////////
// WndBorder DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="wnd_border + (modal_wnd_border)">
    <div class="wnd">
        <div class="wb_caption">
            <div class="wb_btn_container" style="float:right;">
                <!-- BUTTONS BEGIN -->
                <div class="wb_button" style="float:left;">
                    _
                </div>
                <div class="wb_button" style="float:left;">
                    X
                </div>
                <!-- BUTTONS END -->
            </div>
            <div class="wb_text" style="float:left;">
                #CAPTION#
            </div>
        </div>
        <div class="wb_child_container">
            #TEXT#
        </div>
        <div class="rb_resize_corner">
        </div>
    </div>
<//div>
*/
var WND_FLAGS_POPUP        = 0x00;
var WND_FLAGS_CAPTION      = 0x01;
var WND_FLAGS_BORDER       = 0x02; // not used
var WND_FLAGS_MOVE         = 0x04;
var WND_FLAGS_VRESIZE      = 0x08;
var WND_FLAGS_HRESIZE      = 0x10;
var WND_FLAGS_HIDDEN       = 0x20;
var WND_FLAGS_MODAL        = 0x80;
// state
var WND_STATE_MODAL        = 0x10;
////////////////////////////////////////////////////////////////////////////
// WndBorder constructor
////////////////////////////////////////////////////////////////////////////
WndBorder = function()
{
    this.subclass = AP;
    this.subclass();
    
    this.divModalFrame = null;
    this.divWnd = null;
    this.splitter = null;

    this.buttons = new Array();
    
    this.className = "WndBorder";
    
    this.minSize = {
        cx: 20,
        cy: 20
    };
}
WndBorder.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.create = function(elParent, captionText, flags)
{
    this.divModalFrame = AP.createElement("div", elParent, "modal_frame");
    if ( !(this.flags & WND_FLAGS_MODAL) )
    {
        AP.show(this.divModalFrame, false);
    }

    this.subclass = AP.prototype.create;
    this.subclass(elParent, "wnd_border"); // see className in start Drag

    this.flags = AP.is_set(flags, WND_FLAGS_CAPTION);
    
    this.divWnd = AP.createElement("div", this.element, "wnd");

    this.divCaption = AP.createElement("div", this.divWnd, "wb_caption");
    AP.show(this.divCaption, this.flags & WND_FLAGS_CAPTION);
    
    this.divBtns = AP.createElement("div", this.divCaption, "wb_btn_container");
    
    this.divText = AP.createElement("div", this.divCaption, "wb_text");
    this.caption(captionText);

    this.divChild = AP.createElement("div", this.divWnd, "wb_child_container");
    
    this.rbReziseCorner = null;
    if ( this.flags & (WND_FLAGS_HRESIZE | WND_FLAGS_VRESIZE) )
    {
        if ( this.flags & WND_FLAGS_HRESIZE
             && this.flags & WND_FLAGS_VRESIZE )
        {
            this.rbReziseCorner = AP.createElement("div", this.divWnd, "rb_resize_corner");;
        }
        AP.addEvent(this.rbReziseCorner, "mousedown", WndBorder.onDoResize);
    }

    this.splitter = null;
    if ( this.flags & (WND_FLAGS_MOVE | WND_FLAGS_VRESIZE | WND_FLAGS_HRESIZE) )
    {
        this.splitter = Splitter.getGInstance();
    }
    // Event Handlres
    if ( this.flags & WND_FLAGS_MOVE )
    {
        AP.addEvent(this.divWnd, "mousedown", WndBorder.onDoResize);
    }

    if ( this.flags & WND_FLAGS_MODAL )
    {
        this.element.className += " modal_wnd_border";
        this._state |= WND_STATE_MODAL;
    }

    if ( this.flags & (WND_FLAGS_HIDDEN | WND_FLAGS_MODAL) )
    {
        this.hide();
    }
}

////////////////////////////////////////////////////////////////////////////
// assignChild
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.assignChild = function(child)
{
    if ( AP.firstChild(this.divChild) )
        throw ("[WNDBORDER::ASSIGNCHILD] Already Assigned");
    var el = ( child.nodeName ? child : child.element );
    this.divChild.appendChild(el);
}

////////////////////////////////////////////////////////////////////////////
// _modalState
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype._modalState = function(modal)
{
    if ( AP.is_set(modal) && (this._modalState() ^ modal) )
    {
        var el = modal ? this.element : this.divWnd;
        var st = AP.getStyle(el);
        var pos = {
            x: parseInt(st.left),
            y: parseInt(st.top)
        };
        if ( !st.width || (st.width == "auto") )
        {
            pos.cx = "auto";
        }
        else
        {
            pos.cx = parseInt(st.width);
        }
        if ( !st.height || (st.height == "auto") )
        {
            pos.cy = "auto";
        }
        else
        {
            pos.cy = parseInt(st.height);
        }
        el.style.left = null;
        el.style.top = null;
        el.style.width = null;
        el.style.height = null;
        
        if ( modal )
        {
            AP.appendClassName(this.element, "modal_wnd_border");
            this._state |= WND_STATE_MODAL;
        }
        else
        {
            AP.removeClassName(this.element, "modal_wnd_border");    
            this._state &= ~WND_STATE_MODAL;
        }
        
        this.setPosition(pos);
    }

    return ( (this._state & WND_STATE_MODAL) != 0 );
}

////////////////////////////////////////////////////////////////////////////
// show
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.show = function()
{
    AP.visible(this.divWnd, false);
    AP.show(this.element, true);
    
    this._modalState(false);
    
    AP.visible(this.divWnd, true);
}

////////////////////////////////////////////////////////////////////////////
// showModal
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.showModal = function(cx, cy, x, y)
{
    AP.visible(this.divWnd, false);
    AP.show(this.divModalFrame, true);
    AP.show(this.element, true);
    
    this._modalState(true);
    
    //cx = AP.is_set(cx, "auto");
    //cy = AP.is_set(cy, "auto");
    x = AP.is_set(x, "center");
    y = AP.is_set(y, "middle");
    
    this.setPosition({
        x: x,
        y: y,
        cx: cx,
        cy: cy
    });

    AP.visible(this.divWnd, true);
}

////////////////////////////////////////////////////////////////////////////
// popup
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.popup = function(x, y, flags)
{
    this._modalState(true);
    var param = {
        x: x,
        y: y,
        self: this.divWnd,
        parent: this.element,
        flags: flags
    }
    AP.popup(param);
}

////////////////////////////////////////////////////////////////////////////
// dropdown
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.dropdown = function(parent, flags)
{
    AP.visible(this.divWnd, false);
    AP.show(this.element, true);

    var param = {
        self: this.divWnd,
        parent: this.element,
        offsetParent: parent,
        flags: flags
    }
    
    //AP.width(this.divWnd, "auto");
    //AP.height(this.divWnd, "auto");

    var ddPos = AP.dropdown(param);
    this.setPosition(ddPos);
    
    AP.visible(this.divWnd, true);
}

////////////////////////////////////////////////////////////////////////////
// hide
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.hide = function()
{
    AP.show(this.divModalFrame, false);
    AP.show(this.element, false);
}

////////////////////////////////////////////////////////////////////////////
// appendButtons
////////////////////////////////////////////////////////////////////////////
/*
var buttons =   [
                    btnInfo (see insertButton),
                    ...
                ];
*/
WndBorder.prototype.appendButtons = function(buttons)
{
    for ( var loop = 0; loop < buttons.length; loop++ )
    {
        this.insertButton(this.getButtonCount(), buttons[loop]);
    }
}

////////////////////////////////////////////////////////////////////////////
// insertButton
////////////////////////////////////////////////////////////////////////////
/*
var btnInfo = {
    text: htmText,
    id: actionId
};
*/
WndBorder.prototype.insertButton = function(index, btnInfo)
{
    var button = AP.createElement("div", null, "wb_button");
    
    if ( index >= this.getButtonCount() )
    {
        this.buttons.push({
            element: button
        });
        this.divBtns.appendChild(button);
        index = this.getButtonCount() - 1;
    }
    else
    {
        var arrTmp = new Array();
        var btnCount = this.getButtonCount();
        for ( var loop = 0; loop < btnCount; loop++ )
        {
            if ( loop == index )
            {
                arrTmp.push({
                    element: button
                });
                this.divBtns.insertBefore(button, this.buttons[loop].element);
            }
            arrTmp.push(this.buttons[loop]);
        }
        this.buttons = arrTmp;
    }
    
    this.setButton(index, btnInfo, false);

    AP.addEvent(button, "click", WndBorder.onBtnClick);
    
    this.fireNotify(WNDBN_BUTTON_INSERTED, index);
    
    return index;
}

////////////////////////////////////////////////////////////////////////////
// getButtonCount
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.getButtonCount = function()
{
    return this.buttons.length;
}

////////////////////////////////////////////////////////////////////////////
// getButton
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.getButton = function(index)
{
    return this.buttons[index];
}

////////////////////////////////////////////////////////////////////////////
// setButton
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.setButton = function(index, btnInfo, useId)
{
    useId = AP.is_set(useId, false);
    
    var idx = index;
    if ( useId )
    {
        idx = -1;
        for ( var loop = 0; loop < this.buttons.length; loop++ )
        {
            if ( this.buttons[loop].id == index )
            {
                idx = loop;
                break;
            }
        }
        if ( idx == -1 )
        {
            return false;
        }
    }
    
    var btn = this.buttons[idx];
    if ( AP.is_set(btnInfo.text) )
    {
        btn.element.innerHTML = btnInfo.text;
    }
    if ( btnInfo.id )
    {
        btn.id = btnInfo.id;
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// getButtonElement
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.getButtonElement = function(index)
{
    return this.buttons[index].element;
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.setPosition = function(pos)
{
    var wnd = this.element;
    if ( this._modalState() )
    {
        wnd = this.divWnd;
    }

    // Size
    if ( AP.is_set(pos.cx) !== false )
    {
        if ( typeof pos.cx == "string" && pos.cx.toLowerCase() == "auto" )
        {
            wnd.style.width = "auto";
            this.divChild.style.width = "auto";
        }
        else
        {
            AP.width(wnd, pos.cx);
            var childCX = Math.max(0, AP.clientWidth(wnd));
            AP.width(this.divChild, childCX);
        }
    }
    if ( AP.is_set(pos.cy) !== false )
    {
        if ( typeof pos.cy == "string" && pos.cy.toLowerCase() == "auto" )
        {
            wnd.style.height = "auto";
            this.divChild.style.height = "auto";
        }
        else
        {
            AP.height(wnd, pos.cy);
            
            var childCY = AP.clientHeight(wnd);
            (this.flags & WND_FLAGS_CAPTION) &&  (childCY -= AP.offsetHeight(this.divCaption));
            childCY = Math.max(0, childCY);
            AP.height(this.divChild, childCY);
        }
    }
    
    // Resize Corners
    if ( this.rbReziseCorner )
    {
        var posRBCorner = {
            x: AP.clientWidth(this.divWnd) - 8,
            y: AP.clientHeight(this.divWnd) - 8
        };
        AP.setPosition(this.rbReziseCorner, posRBCorner);
    }
    
    // Position
    if ( this._modalState() )
    {
        if ( typeof pos.x == "string" && pos.x.toLowerCase() == "center" )
        {
            pos.x = Math.round(AP.clientWidth(this.element) - AP.offsetWidth(this.divWnd)) / 2;
        }
        if ( typeof pos.y == "string" && pos.y.toLowerCase() == "middle" )
        {
            pos.y = Math.round(AP.clientHeight(this.element) - AP.offsetHeight(this.divWnd)) / 2;
        }
    }
    if ( AP.is_set(pos.x) !== false )
    {
        wnd.style.left = pos.x + "px";
    }
    if ( AP.is_set(pos.y) !== false )
    {
        wnd.style.top = pos.y + "px";
    }
}

////////////////////////////////////////////////////////////////////////////
// setClientSize
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.setClientSize = function(size)
{
    var offsetEl = this._modalState() ? this.divWnd : this.element;
    
    var pos = {};
    
    if ( AP.is_set(size.cx) !== false )
    {
        var cxDelta = AP.offsetWidth(offsetEl) - AP.clientWidth(this.divWnd);
        pos.cx = size.cx + cxDelta;
    }
    
    if ( AP.is_set(size.cy) !== false )
    {
        var cyCaption = (this.flags & WND_FLAGS_CAPTION) ? AP.offsetHeight(this.divCaption) : 0;
        var cyDelta = AP.offsetHeight(offsetEl) - AP.clientHeight(this.divWnd);
        pos.cy = size.cy + cyCaption + cyDelta;
    }
    
    this.setPosition(pos);
}

////////////////////////////////////////////////////////////////////////////
// childArea
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.childArea = function()
{
    return this.divChild;
}

////////////////////////////////////////////////////////////////////////////
// caption
////////////////////////////////////////////////////////////////////////////
WndBorder.prototype.caption = function(captionText)
{
    if ( AP.is_set(captionText) )
    {
        this.divText.innerHTML = captionText;
    }
    return this.divText.innerHTML;
}

////////////////////////////////////////////////////////////////////////////
// onBtnNotify
////////////////////////////////////////////////////////////////////////////
WndBorder.onBtnClick = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.currentTarget);
    var btn = WndBorder.getBtnInstance(obj);
    if ( !btn )
        return true;
    var wndBorder = WndBorder.getInstance(btn);
    if ( !wndBorder )
        return true;

    // notify
    for ( var loop = 0; loop < wndBorder.getButtonCount(); loop++ )
    {
        if ( wndBorder.buttons[loop].element == btn )
        {
            wndBorder.fireNotify(DLGN_BTN_CLICKED, wndBorder.buttons[loop].id);
            break;
        }
    }

    return true;
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
WndBorder.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && WndBorder.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// getBtnInstance
////////////////////////////////////////////////////////////////////////////
WndBorder.getBtnInstance = function(obj)
{
    for ( var o = obj; o && !AP.isSetClassName(o, "wb_button"); o = o.parentNode );
    return o;
}

////////////////////////////////////////////////////////////////////////////
// onDoResize
////////////////////////////////////////////////////////////////////////////
WndBorder.onDoResize = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var pThis = WndBorder.getInstance(obj);
    if ( !pThis )
    {
        return true;
    }
    if ( obj.className == pThis.divCaption.className
         || ( pThis.rbReziseCorner
            && obj.className == pThis.rbReziseCorner.className) )
    {
    }
    else
    {
        return true;
    }
    
    var offsetWnd = pThis.element;
    var parentWnd = pThis.element.parentNode;
    if ( pThis._modalState() )
    {
        offsetWnd = pThis.divWnd;
    }
    if ( pThis._modalState() )
    {
        parentWnd = pThis.element;
    }

    var pos = AP.getAbsolutePos(offsetWnd);
    var prntWidth = (parentWnd ? AP.clientWidth(parentWnd) : 0);
    var prntHeight = (parentWnd ? AP.clientHeight(parentWnd) : 0);

    var start_drag = {
        x: pos.x,
        y: pos.y,
        cx: AP.offsetWidth(offsetWnd),
        cy: AP.offsetHeight(offsetWnd),
        clientX: ev.clientX,
        clientY: ev.clientY,
        notifyHandler: [WndBorder.onSplitted, pThis],
        param: pThis
    };
    
    if ( obj.className == pThis.divCaption.className )
    {
        var dtLeft = AP.offsetLeft(offsetWnd);
        var dtTop = AP.offsetTop(offsetWnd);
        
        start_drag.type = SPLT_MOVE;
        start_drag.className = "ap move_splitter";
        start_drag.dtLeft = Math.max(0, dtLeft);
        start_drag.dtRight = Math.max(0, prntWidth - AP.offsetWidth(offsetWnd) - dtLeft);
        start_drag.dtTop = Math.max(0, dtTop);
        start_drag.dtBottom = Math.max(0, prntHeight - AP.offsetHeight(offsetWnd) - dtTop);
    }
    else if ( pThis.rbReziseCorner && obj.className == pThis.rbReziseCorner.className )
    {
        var dtLeft = Math.max(0, AP.offsetWidth(offsetWnd) - pThis.minSize.cx);
        var dtTop = Math.max(0, AP.offsetHeight(offsetWnd) - pThis.minSize.cy);
        var dtRight = Math.max(0, prntWidth - AP.offsetWidth(offsetWnd) - AP.offsetLeft(offsetWnd));
        var dtBottom = Math.max(0, prntHeight - AP.offsetHeight(offsetWnd) - AP.offsetTop(offsetWnd));
        
        start_drag.type = SPLT_RESIZE;
        start_drag.className = "ap resize_splitter";
        start_drag.dtLeft = dtLeft;
        start_drag.dtRight = dtRight;
        start_drag.dtTop = dtTop;
        start_drag.dtBottom = dtBottom;
    }
    
    pThis.splitter.startDrag(start_drag);
    
    return AP.stopEvent(ev);
}

////////////////////////////////////////////////////////////////////////////
// onSplitted
////////////////////////////////////////////////////////////////////////////
WndBorder.onSplitted = function(param)
{
    if ( param.code != SPLTN_POS_CHANGED )
    {
        return 0;
    }
    
    var wnd_border = param.producer.param;
    if ( !wnd_border )
    {
        return 0;
    }

    var offsetWnd = wnd_border.element;
    if ( wnd_border._modalState() )
    {
        offsetWnd = wnd_border.divWnd;
    }
    
    var delta = param.param;
    var st = AP.getStyle(offsetWnd);
    var x = parseInt(st.left);
    var y = parseInt(st.top);
    var cx = AP.offsetWidth(offsetWnd);
    var cy = AP.offsetHeight(offsetWnd);
    wnd_border.setPosition({
        x: (x + delta.x),
        y: (y + delta.y),
        cx: (cx + delta.cx),
        cy: (cy + delta.cy)
    });

    return 0;
}

////////////////////////////////////////////////////////////////////////////
// class ImgInput extends AP
////////////////////////////////////////////////////////////////////////////
// ImgInput DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="img_input">
    <table cellpadding="0" cellspacing="0" class="img_input_table">
        <tr>
            <td valign="middle" align="center" class="img_input_img_td">
                <div><img src="img/page_num.gif"></div>
            </td>
            <td valign="middle" class="img_input_text_td">
                <div class="img_input_input_container"><input class="img_input_input" type="text" value="test"></div>
            </td>
            <td valign="middle" align="center" class="img_input_img_td">
                <div><img src="img/menu/search.gif"></div>
            </td>
        </tr>
    </table>    
</div>
*/
var IMGIN_FLAGS_TEXT                = 0x00;
var IMGIN_FLAGS_NUMBER              = 0x00;
var IMGIN_FLAGS_PASSWORD            = 0x01;
var IMGIN_FLAGS_READONLY            = 0x02;
var IMGIN_FLAGS_AUTOCOMPLETE        = 0x04;
var IMGIN_FLAGS_DISABLED            = 0x08;
// states
var IMGINS_OVER                     = 0x01;
var IMGINS_DISABLED                 = 0x02;
////////////////////////////////////////////////////////////////////////////
// constructor
////////////////////////////////////////////////////////////////////////////
ImgInput = function()
{
    this.subclass = AP;
    this.subclass();
    
    this.className = "ImgInput";
}
ImgInput.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
ImgInput.prototype.create = function(elParent, name, flags, tabIndex)
{
    this.subclass = AP.prototype.create;
    this.subclass(elParent, "img_input");
    AP.addEvent(this.element, "mouseover", ImgInput.onMouseEvent);
    AP.addEvent(this.element, "mouseout", ImgInput.onMouseEvent);
    
    this.flags = AP.is_set(flags, 0);
    
    var cells = [[[
                    null,
                    [
                        ["img_input_img_td", null, null, "center", "middle"],
                        ["img_input_text_td", null, null, "left", "middle"],
                        ["img_input_img_td", null, null, "center", "middle"]
                    ]
                ]]];
    var tbl = AP.createTable(0, 0, cells, this.element, "img_input_table");
    
    // Img Left
    this.tdImgLeft = tbl.tBodies[0].rows[0].cells[0];
    AP.show(this.tdImgLeft, false);
    AP.addEvent(this.tdImgLeft, "click", ImgInput.onBtnClick);
    var divTmp = AP.createElement("div", this.tdImgLeft);
    this.imgLeft = AP.createElement("img", divTmp);
    //this.imgLeft = AP.createElement("img", this.tdImgLeft);

    // Input Element    
    this.tdText = tbl.tBodies[0].rows[0].cells[1];
    var divTmp = AP.createElement("div", this.tdText, "img_input_input_container");
    this.input = AP.createNamedElement({
        tagName: "input",
        name: name,
        parent: divTmp,
        className: "img_input_input",
        type: ((this.flags & IMGIN_FLAGS_PASSWORD) ? "password" : "text")
    });
    this.input.autocomplete = ((this.flags & IMGIN_FLAGS_AUTOCOMPLETE) ? "on" : "off");
    if ( this.flags & IMGIN_FLAGS_READONLY )
    {
        this.input.readOnly = 1;
    }

    // Img Right
    this.tdImgRight = tbl.tBodies[0].rows[0].cells[2];
    AP.show(this.tdImgRight, false);
    AP.addEvent(this.tdImgRight, "click", ImgInput.onBtnClick);
    var divTmp = AP.createElement("div", this.tdImgRight);
    this.imgRight = AP.createElement("img", divTmp);

    if ( this.flags & IMGIN_FLAGS_DISABLED )
    {
        this.enable(false);
    }

    // Input Event Handles
    AP.addEvent(this.input, "keypress", ImgInput.onKeyPress);
    if ( AP.isIE() )
    {
        AP.addEvent(this.input, "propertychange", ImgInput.onChange);
    }
    else
    {
        AP.addEvent(this.input, "change", ImgInput.onChange);
        AP.addEvent(this.input, "keypress", ImgInput.onChange);
    }
    
    // Tab Index
    this.tabIndex( AP.is_set(tabIndex, 0) );
}

////////////////////////////////////////////////////////////////////////////
// getName
////////////////////////////////////////////////////////////////////////////
ImgInput.prototype.getName = function()
{
    return ( (this.input.name && this.input.name.length) ? this.input.name : null );
}

////////////////////////////////////////////////////////////////////////////
// value (get/set value)
////////////////////////////////////////////////////////////////////////////
ImgInput.prototype.value = function(value)
{
    if ( AP.is_set(value) !== false )
    {
        if ( this.input.value != value )
        {
            this.input.value = value;
            this._onChanged();
        }
    }
    return this.input.value;
}

////////////////////////////////////////////////////////////////////////////
// focus
////////////////////////////////////////////////////////////////////////////
ImgInput.prototype.focus = function()
{
    this.input.focus();
}

////////////////////////////////////////////////////////////////////////////
// select text
////////////////////////////////////////////////////////////////////////////
ImgInput.prototype.select = function()
{
    this.input.select();
}

////////////////////////////////////////////////////////////////////////////
// tabIndex get / set tabIndex
////////////////////////////////////////////////////////////////////////////
ImgInput.prototype.tabIndex = function(tabIndex)
{
    if ( AP.is_set(tabIndex) !== false )
    {
        this.input.tabIndex = tabIndex;
    }
    return this.input.tabIndex;
}

////////////////////////////////////////////////////////////////////////////
// image
////////////////////////////////////////////////////////////////////////////
/*
var img = {
    src: value,
    left: value,
    cx: value,
    cy: value
};
*/
ImgInput.prototype.image = function(img)
{
    if ( AP.is_set(img) )
    {
        img.src = AP.is_set(img.src, "");
        img.left = AP.is_set(img.left, 1);
        
        if ( img.left )
        {
            AP.show( this.tdImgLeft, (img.src != "") );
            
            this.imgLeft.src = img.src;
            
            AP.width(this.imgLeft, img.cx);
            AP.height(this.imgLeft, img.cy);
        }
        else
        {
            AP.show( this.tdImgRight, (img.src != "") );
            
            this.imgRight.src = img.src;
            
            AP.width(this.imgRight, img.cx);
            AP.height(this.imgRight, img.cy);
        }
    }
    
    return null;
}

////////////////////////////////////////////////////////////////////////////
// enable
////////////////////////////////////////////////////////////////////////////
ImgInput.prototype.enable = function(doEnable)
{
    if ( AP.is_set(doEnable) )
    {
        if ( doEnable )
        {
            this.flags &= ~IMGIN_FLAGS_DISABLED;
            if ( !(this.flags & IMGIN_FLAGS_READONLY) )
            {
                this.input.readOnly = 0;
            }
            this.state( this.state() & ~IMGINS_DISABLED );
        }
        else
        {
            this.flags |= IMGIN_FLAGS_DISABLED;
            this.input.readOnly = 1;
            this.state( this.state() | IMGINS_DISABLED );
        }
    }
    
    return (this.state() & IMGINS_DISABLED) ? false : true;
}

////////////////////////////////////////////////////////////////////////////
// _stateToClassName
////////////////////////////////////////////////////////////////////////////
ImgInput.prototype._stateToClassName = function()
{
    var className = "ap img_input";
    if ( this._state & IMGINS_DISABLED )
    {
        className += " img_input_disabled";
    }
    else
    {
        if ( this._state & IMGINS_OVER )
        {
            className += " img_input_over";
        }
    }
    if ( this.element.className != className )
    {
        this.element.className = className;
    }
}

////////////////////////////////////////////////////////////////////////////
// _onChanged
////////////////////////////////////////////////////////////////////////////
ImgInput.prototype._onChanged = function()
{
    this.fireNotify(IMGINN_ONCHANGED, this.value());
    return 0;
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
ImgInput.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && ImgInput.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// onBtnClick
////////////////////////////////////////////////////////////////////////////
ImgInput.onBtnClick = function(ev)
{
    ev || (ev = window.event);
    var obj = null;
    if ( AP.isIE() )
    {
        for (
              obj = ev.srcElement;
              obj && !AP.isSetClassName(obj, "img_input_img_td");
              obj = obj.parentNode
            );
    }
    else
    {
        obj = ev.currentTarget;
    }
    
    var imgin = ImgInput.getInstance(obj);
    if ( !imgin )
    {
        return true;
    }
    if ( imgin.enable() == false )
    {
        return true;
    }
    
    if ( obj == imgin.tdImgLeft )
    {
        imgin.fireNotify(IMGINN_ONBTNCLICKED, 0);
    }
    else if ( obj == imgin.tdImgRight )
    {
        imgin.fireNotify(IMGINN_ONBTNCLICKED, 1);
    }

    return true;
}

////////////////////////////////////////////////////////////////////////////
// _onChanging
////////////////////////////////////////////////////////////////////////////
ImgInput._onChanging = function(id, oldValue)
{
    var obj = AP.getElement(id);
    if ( !obj )
    {
        return -1;
    }
    
    var imgin = ImgInput.getInstance(obj);
    if ( !imgin )
    {
        return -1;
    }
    
    if ( imgin.value() != unescape(oldValue) )
    {
        return imgin._onChanged();
    }

    return 0;
}

////////////////////////////////////////////////////////////////////////////
// onChange
////////////////////////////////////////////////////////////////////////////
ImgInput.onChange = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var imgin = ImgInput.getInstance(obj);
    if ( !imgin )
    {
        return true;
    }
    
    if ( ev.type == "change"
         || ev.type == "propertychange" )
    {
        imgin._onChanged();
    }
    else
    {   // keypress, paste, cut
        imgin.input.id = AP.uniqueId("on_change_");
        setTimeout("ImgInput._onChanging('" + imgin.input.id + "', '" + escape(imgin.value()) + "');", 200);
    }

    return true;    
}

////////////////////////////////////////////////////////////////////////////
// onKeyPress
////////////////////////////////////////////////////////////////////////////
ImgInput.onKeyPress = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var imgin = ImgInput.getInstance(obj);
    if ( !imgin )
    {
        return true;
    }
        
    if ( imgin.enable() == false )
    {
        return true;
    }
    
    if ( !(imgin.flags & IMGIN_FLAGS_DISABLED) )
    {
        var keyInfo = {
            code: ev.charCode || ev.keyCode,
            ctrl: ev.ctrlKey,
            shift: ev.shiftKey,
            alt: ev.altKey
        }
        imgin.fireNotify(IMGINN_ONKEYPRESSED, keyInfo);
    }

    return true;    
}

////////////////////////////////////////////////////////////////////////////
// onMouseEvent
////////////////////////////////////////////////////////////////////////////
ImgInput.onMouseEvent = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.currentTarget);
    var imgin = ImgInput.getInstance(obj);
    if ( !imgin )
        return true;
    
    if ( imgin.flags & IMGIN_FLAGS_DISABLED )
    {
        return true;
    }
    
    if ( ev.type == "mouseover" )
    {
        imgin.state( imgin.state() | IMGINS_OVER );
    }
    else if ( ev.type == "mouseout" )
    {
        imgin.state( imgin.state() & ~IMGINS_OVER );
    }
    
    return true;
}


////////////////////////////////////////////////////////////////////////////
// class TextArea extends AP
////////////////////////////////////////////////////////////////////////////
// TextArea DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="text_area">
    <textarea class="text_area_textarea"></textarea>
</div>
*/
var TEXTAREA_FLAGS_READONLY         = 0x01;
// states
var TEXTAREA_OVER                   = 0x01;
////////////////////////////////////////////////////////////////////////////
// constructor
////////////////////////////////////////////////////////////////////////////
TextArea = function()
{
    this.subclass = AP;
    this.subclass();
    
    this.className = "TextArea";
}
TextArea.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
TextArea.prototype.create = function(elParent, name, flags, tabIndex)
{
    this.subclass = AP.prototype.create;
    this.subclass(elParent, "text_area");
    
    this.input = AP.createNamedElement({
        tagName: "textarea",
        name: name,
        parent: this.element,
        className: "text_area_textarea"
    });
    
    // Input Event Handles
    AP.addEvent(this.input, "change", TextArea.onChange);
    AP.addEvent(this.input, "keypress", TextArea.onChange);
    if ( AP.isIE() )
    {
        AP.addEvent(this.input, "paste", TextArea.onChange);
        AP.addEvent(this.input, "cut", TextArea.onChange);
    }
    
    this.setPosition({ flags: POS_FLAGS_RECALCULATE });
}

////////////////////////////////////////////////////////////////////////////
// getName
////////////////////////////////////////////////////////////////////////////
TextArea.prototype.getName = function()
{
    return ( (this.input.name && this.input.name.length) ? this.input.name : null );
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
TextArea.prototype.setPosition = function(pos)
{
    pos.flags = AP.is_set(pos.flags, 0);
    this.subclass = AP.prototype.setPosition;
    this.subclass(pos);
    
    if ( AP.is_set(pos.cx) !== false || (pos.flags & POS_FLAGS_RECALCULATE) )
    {
        AP.width( this.input, AP.clientWidth(this.element) );
    }
    if ( AP.is_set(pos.cy) !== false || (pos.flags & POS_FLAGS_RECALCULATE) )
    {
        AP.height( this.input, AP.clientHeight(this.element) );
    }
}

////////////////////////////////////////////////////////////////////////////
// value (get/set value)
////////////////////////////////////////////////////////////////////////////
TextArea.prototype.value = function(value)
{
    if ( AP.is_set(value) !== false )
    {
        this.input.value = value;
    }
    return this.input.value;
}

////////////////////////////////////////////////////////////////////////////
// _onChanged
////////////////////////////////////////////////////////////////////////////
TextArea.prototype._onChanged = function()
{
    this.fireNotify(TEXTAREAN_ONCHANGED, this.value());
    return 0;
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
TextArea.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && TextArea.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// _onChanging
////////////////////////////////////////////////////////////////////////////
TextArea._onChanging = function(id, oldValue)
{
    var obj = AP.getElement(id);
    if ( !obj )
    {
        return -1;
    }
    
    var ta = TextArea.getInstance(obj);
    if ( !ta )
    {
        return -1;
    }
    
    if ( ta.value() != unescape(oldValue) )
    {
        return ta._onChanged();
    }

    return 0;
}

////////////////////////////////////////////////////////////////////////////
// onChange
////////////////////////////////////////////////////////////////////////////
TextArea.onChange = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var ta = TextArea.getInstance(obj);
    if ( !ta )
    {
        return true;
    }
    
    if ( ev.type == "change" )
    {
        ta._onChanged();
    }
    else
    {   // keypress, paste, cut
        ta.input.id = AP.uniqueId("on_change_");
        setTimeout("TextArea._onChanging('" + ta.input.id + "', '" + escape(ta.value()) + "');", 200);
    }

    return true;    
}

////////////////////////////////////////////////////////////////////////////
// TreeViewMemory extends AP
////////////////////////////////////////////////////////////////////////////
//
var TVIS_MASK                = 0xFFFFFFFF;

var TVIS_EXPANDED            = 0x00000001;
var TVIS_FOCUSED             = 0x00000002;
var TVIS_SELECTED            = 0x00000004;
var TVIS_HIDDEN              = 0x00000008;
var TVIS_DISABLED            = 0x00000010;
var TVIS_OVER                = 0x00000020;

var TVIS_SERIALIZED          = 0x00000100;
var TVIS_MODIFIED            = 0x0000f000;
var TVIS_MODIFIED_TEXT       = 0x00001000;
var TVIS_MODIFIED_ICON       = 0x00002000;

var TVIS_CHECKED             = 0x00010000;
var TVIS_INDETERMINATE       = 0x00020000;

var TV_INVALID_ITEM_ID       = null;
var TV_ROOT_ITEM_ID          = "tv_root";

var TV_IMGLIST_CHECK = {
    base: "check_",
    checked: "check_v",
    indeterminate: "check_x",
    
    disabled: "checkdis_",
    checkeddisabled: "checkdis_v",
    indeterminatedisabled: "checkdis_x"
};
////////////////////////////////////////////////////////////////////////////
// constructor
////////////////////////////////////////////////////////////////////////////
TreeViewMemory = function(imgList)
{
    this.subclass = AP;
    this.subclass();
    
    this.imgList = AP.is_set(imgList, {
        base_dir: ROOT_PATH + "img/",
        img_ext: ".gif",
        img_line: "line.gif",
        img_check: TV_IMGLIST_CHECK,
        img_list: {
            base: "page.gif",
            expanded: "folderopen.gif",
            collapsed: "folder.gif"
        }
    });
    this.className = "TreeViewMemory";
    
    this.items = new Object();
    this.items[TV_ROOT_ITEM_ID] = {
        id: TV_ROOT_ITEM_ID,
        _parentId: null,
        _prevId: null,
        _nextId: null,
        _prevSiblingId: null,
        _nextSiblingId: null,
        _prevSortedId: null,
        _nextSortedId: null,
        _prevSortedSiblingId: null,
        _nextSortedSiblingId: null,
        flags: 0,
        level: -1
    };
    this.lastItemId = TV_ROOT_ITEM_ID;
    this.lastSortedItemId = TV_ROOT_ITEM_ID;
    
    this.colCount = 0;
    this.maxLevel = 0;
    this.itemCount = 0;
    
    this.focusedId = TV_ROOT_ITEM_ID;
    this.selectedItems = {};
    this.checkedItems = {};
    this._sort_data = null;
}
TreeViewMemory.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// insertItem
////////////////////////////////////////////////////////////////////////////
// insert flags
var TVI_INSERT_STATE        = 0x0f;
var TVI_INSERT_PREV_SIBLING = 0x00;
var TVI_INSERT_NEXT_SIBLING = 0x01;
var TVI_INSERT_FIRST_CHILD  = 0x02;
var TVI_INSERT_LAST_CHILD   = 0x03;
var TVI_INSERT_EXPANDED     = 0x10;
var TVI_INSERT_SORTED       = 0x20;
/*
var tvItem = {
    parent: id,
    img_list: {
        base: src,
        focused: src,
        selected: src,
        expanded: src,
        collapsed: src
    },
    columns: {
        column_idx: {
            text: html text,
            data: any user data,
            flags: int value
        }
    },
    text: html text,
    data: any data,
    flags: int value
}
*/
TreeViewMemory.prototype.insertItem = function(tvItem)
{
    var bFirstChild = false;

    var item = {
        id: AP.uniqueId("tv_item_"),
        _parentId: null,
        _prevId: null,
        _nextId: null,
        _prevSiblingId: null,
        _nextSiblingId: null,
        _prevSortedId: null,
        _nextSortedId: null,
        _prevSortedSiblingId: null,
        _nextSortedSiblingId: null,
        img_list: TreeViewMemory._defImgList(null, this.imgList.img_list),
        text: null,
        data: null,
        columns: {},
        flags: 0,
        level: 0
    };

    // Check For Incorrect Input
    tvItem.flags = AP.is_set(tvItem.flags, 0);
    tvItem.parent = AP.is_set(tvItem.parent, TV_ROOT_ITEM_ID);

    if ( tvItem.flags & TVI_INSERT_SORTED )
    {
        DebugLog.write("TODO:// TVI_INSERT_SORTED");
    }
    
    var parentItem = this.items[tvItem.parent];
    if ( !AP.is_set(parentItem) )
    {
        throw("TreeViewMemory InsertItem: Parent Not Found");
    }
    
    var parentLevel = parentItem.level;
    if ( tvItem.parent == TV_ROOT_ITEM_ID )
    {
        if ( (tvItem.flags & TVI_INSERT_STATE) == TVI_INSERT_NEXT_SIBLING )
        {
            tvItem.flags &= ~TVI_INSERT_STATE;
            tvItem.flags |= TVI_INSERT_LAST_CHILD;
        }
        else if ( (tvItem.flags & TVI_INSERT_STATE) == TVI_INSERT_PREV_SIBLING )
        {
            tvItem.flags &= ~TVI_INSERT_STATE;
            tvItem.flags |= TVI_INSERT_FIRST_CHILD;
        }
    }
    
    // Insert Item in TreeView Hierarchy
    //
    // real order
    //
    if ( (tvItem.flags & TVI_INSERT_STATE) == TVI_INSERT_FIRST_CHILD )
    {
        item.level = parentLevel + 1;
        item._parentId = parentItem.id;
        item._prevId = parentItem.id;
        item._nextId = parentItem._nextId;
        item._prevSiblingId = null;
        item._nextSiblingId = null;
        
        parentItem._nextId = item.id;
        var nextItem = this.items[item._nextId];
        if ( AP.is_set(nextItem) )
        {
            nextItem._prevId = item.id;
            if ( nextItem.level != parentLevel + 1 )
            {
                bFirstChild = true;
            }
            else
            {
                nextItem._prevSiblingId = item.id;
                item._nextSiblingId = nextItem.id;
            }
        }
        else
        {
            bFirstChild = true;
        }
    }
    else if ( (tvItem.flags & TVI_INSERT_STATE) == TVI_INSERT_LAST_CHILD )
    {
        item.level = parentLevel + 1;
        item._parentId = parentItem.id;
        item._prevId = null;
        item._nextId = null;
        item._prevSiblingId = null;
        item._nextSiblingId = null;
        
        // Find next item
        var nextId = this.nextItem(parentItem.id, TVI_FIND_NEXT_SIBLING);
        if ( !nextId )
        {
            nextId = this.nextItem(parentItem.id, TVI_FIND_NEXT_PARENT);
        }
        if ( nextId )
        {
            var nextItem = this.items[nextId];
            item._prevId = nextItem._prevId;
            this.items[item._prevId]._nextId = item.id;
            
            item._nextId = nextItem.id;
            nextItem._prevId = item.id;
        }
        else
        {
            item._prevId = this.lastItemId;
            this.items[item._prevId]._nextId = item.id;
        }
        
        // Find previous sibling item
        for ( var prevSiblingId = item._prevId;
              prevSiblingId;
              prevSiblingId = this.nextItem(prevSiblingId, TVI_FIND_PARENT) )
        {
            var prevSiblingItem = this.items[prevSiblingId];
            if ( prevSiblingItem.level == item.level )
            {
                item._prevSiblingId = prevSiblingItem.id;
                prevSiblingItem._nextSiblingId = item.id;
                break;
            }
            else if ( prevSiblingItem.level < item.level )
            {
                bFirstChild = true;
                break;
            }
        }
    }
    else if ( (tvItem.flags & TVI_INSERT_STATE) == TVI_INSERT_PREV_SIBLING )
    {
        item.level = parentLevel;
        item._parentId = parentItem._parentId;
        item._prevId = parentItem._prevId;
        item._nextId = parentItem.id;
        item._prevSiblingId = parentItem._prevSiblingId;
        item._nextSiblingId = parentItem.id;
        
        if ( item._prevId )
        {
            this.items[item._prevId]._nextId = item.id;
        }
        if ( item._prevSiblingId )
        {
            this.items[item._prevSiblingId]._nextSiblingId = item.id;
        }
        parentItem._prevId = item.id;
        parentItem._prevSiblingId = item.id;
    }
    else if ( (tvItem.flags & TVI_INSERT_STATE) == TVI_INSERT_NEXT_SIBLING )
    {
        item.level = parentLevel;
        item._parentId = parentItem._parentId;
        item._prevId = null;
        item._nextId = null;
        item._prevSiblingId = parentItem.id;
        item._nextSiblingId = parentItem._nextSiblingId;
        
        // Find next item
        var nextId = this.nextItem(parentItem.id, TVI_FIND_NEXT_SIBLING);
        if ( !nextId )
        {
            nextId = this.nextItem(parentItem.id, TVI_FIND_NEXT_PARENT);
        }
        if ( nextId )
        {
            var nextItem = this.items[nextId];
            item._prevId = nextItem._prevId;
            this.items[item._prevId]._nextId = item.id;
            
            item._nextId = nextItem.id;
            nextItem._prevId = item.id;
        }
        else
        {
            item._prevId = this.lastItemId;
            this.items[item._prevId]._nextId = item.id;
        }
        
        parentItem._nextSiblingId = item.id;
        if ( item._nextSiblingId )
        {
            this.items[item._nextSiblingId]._prevSiblingId = item.id;
        }
    }
    //
    // sort order
    //
    if ( this._sort_data )
    {   // Always Insert as Last Child if Child Exists and TreeView sorted
        item._prevSortedId = null;
        item._nextSortedId = null;
        item._prevSortedSiblingId = null;
        item._nextSortedSiblingId = null;
        
        if ( (tvItem.flags & TVI_INSERT_STATE) == TVI_INSERT_NEXT_SIBLING
             || (tvItem.flags & TVI_INSERT_STATE) == TVI_INSERT_PREV_SIBLING )
        {
            parentItem = this.items[parentItem._parentId];
        }
        
        // Find next sorted item
        var nextId = this.nextItem(parentItem.id, TVI_FIND_NEXT_SIBLING | TVI_FIND_SORTED);
        if ( !nextId )
        {
            nextId = this.nextItem(parentItem.id, TVI_FIND_NEXT_PARENT | TVI_FIND_SORTED);
        }
        if ( nextId )
        {
            var nextItem = this.items[nextId];
            item._prevSortedId = nextItem._prevSortedId;
            this.items[item._prevSortedId]._nextSortedId = item.id;
            
            item._nextSortedId = nextItem.id;
            nextItem._prevSortedId = item.id;
        }
        else
        {
            item._prevSortedId = this.lastSortedItemId;
            this.items[item._prevSortedId]._nextSortedId = item.id;
        }
        
        // Find previous sibling item
        for ( var prevSiblingId = item._prevSortedId;
              prevSiblingId;
              prevSiblingId = this.nextItem(prevSiblingId, TVI_FIND_PARENT) )
        {
            var prevSiblingItem = this.items[prevSiblingId];
            if ( prevSiblingItem.level == item.level )
            {
                item._prevSortedSiblingId = prevSiblingItem.id;
                prevSiblingItem._nextSortedSiblingId = item.id;
                break;
            }
            else if ( prevSiblingItem.level < item.level )
            {
                break;
            }
        }
    }
    else
    {
        item._prevSortedId = item._prevId;
        item._nextSortedId = item._nextId;
        item._prevSortedSiblingId = item._prevSiblingId;
        item._nextSortedSiblingId = item._nextSiblingId;
    
        if ( item._prevSortedId )
        {
            this.items[item._prevSortedId]._nextSortedId = item.id;
        }
        if ( item._nextSortedId )
        {
            this.items[item._nextSortedId]._prevSortedId = item.id;
        }
        if ( item._prevSortedSiblingId )
        {
            this.items[item._prevSortedSiblingId]._nextSortedSiblingId = item.id;
        }
        if ( item._nextSortedSiblingId )
        {
            this.items[item._nextSortedSiblingId]._prevSortedSiblingId = item.id;
        }
    }
    
    // Check For/Store Last Item Id
    if ( !item._nextId )
    {
        this.lastItemId = item.id;
    }
    // Check For/Store Last Sorted Item Id
    if ( !item._nextSortedId )
    {
        this.lastSortedItemId = item.id;
    }
    
    if ( this.flags & TVS_COLUMNHEADER )
    {
        for ( var loopCol = 0; loopCol < this.colCount; loopCol++ )
        {
            item.columns[loopCol] = {
                text: null,
                data: null,
                flags: 0
            };
            if ( !AP.is_set(tvItem.columns)
                 || !AP.is_set(tvItem.columns[loopCol]) )
            {
                continue;
            }
            if ( AP.is_set(tvItem.columns[loopCol].text) )
            {
                item.columns[loopCol].text = tvItem.columns[loopCol].text;
            }
            if ( AP.is_set(tvItem.columns[loopCol].data) )
            {
                item.columns[loopCol].data = tvItem.columns[loopCol].data;
            }
        }
    }
    if ( AP.is_set(tvItem.text) )
    {
        item.text = tvItem.text;
    }
    if ( AP.is_set(tvItem.data) )
    {
        item.data = tvItem.data;
    }
    if ( AP.is_set(tvItem.img_list) )
    {
        item.img_list = tvItem.img_list;
    }
    
    this.items[item.id] = item;
    this.itemCount++;
    
    this._changeMaxLevel(item.level);
            
    this._serialize_virt(item.id, item._nextId);
    
    this.fireNotify(TVN_ITEM_INSERTED, item.id);
    
    if ( tvItem.flags & TVI_INSERT_EXPANDED )
    {
        this._dovisible(item.id);
    }
    else if ( bFirstChild && item._parentId != TV_ROOT_ITEM_ID )
    {
        // Refresh State Icon For Parent
        this._assignItemIcon_virt(item._parentId);
    }
    
    return item.id;
}

////////////////////////////////////////////////////////////////////////////
// getItemsCount
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.getItemsCount = function()
{
    return this.itemCount;
}

////////////////////////////////////////////////////////////////////////////
// itemText  - get/set item Text
// var textInfo = {
//     itemId: item Id,
//     columnId: columnId / column index,
//     text: htmlText
// }
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.itemText = function(textInfo)//itemId, text
{
    var item = this.items[textInfo.itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    var oldText = "";
    if ( this.flags & TVS_COLUMNHEADER )
    {
        var colIndex = textInfo.columnId;
        if ( !AP.is_set(item.columns[colIndex]) )
        {
            return null;
        }
        oldText = item.columns[colIndex].text;
        if ( AP.is_set(textInfo.text) && oldText != textInfo.text )
        {
            item.columns[colIndex].text = textInfo.text;
            this._chageItemText_virt(item.id);
            this.fireNotify(TVN_ITEMTEXT_CHANGED, {
                itemId: item.id,
                columnId: textInfo.columnId
            });
        }
    }
    else
    {
        oldText = item.text;
        if ( AP.is_set(textInfo.text) && oldText != textInfo.text )
        {
            item.text = textInfo.text;
            this._chageItemText_virt(item.id);
            this.fireNotify(TVN_ITEMTEXT_CHANGED, {
                itemId: item.id,
                columnId: 0
            });
        }
    }
    
    return oldText;
}

////////////////////////////////////////////////////////////////////////////
// itemData - get/set item Data
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.itemData = function(itemId, itemData, destroyOld)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    if ( AP.is_set(itemData) )
    {
        destroyOld = AP.is_set(destroyOld, true);
        var fireNotify = this._compareItemsData_virt(item.data, itemData);
        if ( destroyOld && AP.is_set(item.data) )
        {
            delete item.data;
            item.data = null;
        }
        item.data = itemData;
        if ( fireNotify )
        {
            this.fireNotify(TVN_ITEMDATA_CHANGED, item.id);
        }
    }
    
    return item.data;
}

////////////////////////////////////////////////////////////////////////////
// itemImage - get/set item Images
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.itemImage = function(itemId, imgList)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    if ( AP.is_set(imgList) )
    {
        var changed = false;
        // Base
        if ( AP.is_set(imgList.base) )
        {
            if ( item.img_list.base != imgList.base )
            {
                item.img_list.base = imgList.base;
                changed = true;
            }
        }
        // Focused
        if ( AP.is_set(imgList.focused) )
        {
            if ( item.img_list.focused != imgList.focused )
            {
                item.img_list.focused = imgList.focused;
                changed = true;
            }
        }
        // Selected
        if ( AP.is_set(imgList.selected) )
        {
            if ( item.img_list.selected != imgList.selected )
            {
                item.img_list.selected = imgList.selected;
                changed = true;
            }
        }
        // Expanded
        if ( AP.is_set(imgList.expanded) )
        {
            if ( item.img_list.expanded != imgList.expanded )
            {
                item.img_list.expanded = imgList.expanded;
                changed = true;
            }
        }
        // Collapsed
        if ( AP.is_set(imgList.collapsed) )
        {
            if ( item.img_list.collapsed != imgList.collapsed )
            {
                item.img_list.collapsed = imgList.collapsed;
                changed = true;
            }
        }
        
        if ( changed )
        {
            this._assignItemIcon_virt(itemId);
        }
        
        return changed;
    }
    
    return item.img_list;
}

////////////////////////////////////////////////////////////////////////////
// focus
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.focus = function(itemId)
{
    var item = this.items[itemId];
    if ( this.focused(itemId) )
    {
        return false;
    }

    if ( this.focusedId != TV_ROOT_ITEM_ID )
    {
        var focusedItem = this.items[this.focusedId];
        focusedItem.flags &= ~TVIS_FOCUSED;
        this.focusedId = TV_ROOT_ITEM_ID;
        this._focusItem_virt(focusedItem.id, false);
        this.fireNotify(TVN_ITEM_FOCUSED, focusedItem.id);
    }
    
    if ( AP.is_set(item) )
    {
        this.focusedId = item.id;
        item.flags |= TVIS_FOCUSED;
        if ( itemId != TV_ROOT_ITEM_ID )
        {
            this._focusItem_virt(item.id, true);
            this.fireNotify(TVN_ITEM_FOCUSED, item.id);
            // Expand Focused Item
            this._dovisible(item.id);
        }
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// select
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.select = function(itemId, bSelect, bUnique)
{
    bSelect = AP.is_set(bSelect, true);
    bUnique = AP.is_set(bUnique, true);
    
    var item = this.items[itemId];
    var selListChanged = false;
    if ( !bSelect
         && AP.is_set(item)
         && (item.flags & TVIS_SELECTED) )
    {
        item.flags &= ~TVIS_SELECTED;
        delete this.selectedItems[item.id];
        this._selectItem_virt(item.id, false);
        selListChanged = true;
        this.fireNotify(TVN_ITEM_SELECTED, item.id);
    }
    else
    {
        if ( bSelect && bUnique )
        {
            for ( loopId in this.selectedItems )
            {
                if ( loopId != itemId )
                {
                    var loopItem = this.items[loopId];
                    loopItem.flags &= ~TVIS_SELECTED;
                    delete this.selectedItems[loopId];
                    this._selectItem_virt(loopId, false);
                    selListChanged = true;
                    this.fireNotify(TVN_ITEM_SELECTED, loopId);
                }
            }
        }
        
        if ( AP.is_set(item)
             && bSelect
             && !(item.flags & TVIS_SELECTED) )
        {
            item.flags |= TVIS_SELECTED;
            this.selectedItems[item.id] = true;
            this._selectItem_virt(item.id, true);
            selListChanged = true;
            this.fireNotify(TVN_ITEM_SELECTED, item.id);
        }
    }
    
    if ( selListChanged )
    {
        this.fireNotify(TVN_ITEM_SELLISTCHANGED, {
            reason: TVN_ITEM_SELECTED
        });
    }
    
    return selListChanged;
}

////////////////////////////////////////////////////////////////////////////
// itemVisible
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.itemVisible = function(itemId, doVisible)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    if ( AP.is_set(doVisible) )
    {
        var flags = item.flags;
        if ( doVisible )
        {
            flags &= ~TVIS_HIDDEN;
        }
        else
        {
            flags |= TVIS_HIDDEN;
        }
        
        if ( item.flags != flags )
        {
            item.flags = flags;
            
            if ( !doVisible )
            {
                // unselect and unfocus item
                if ( this.selected(item.id) )
                {
                    this.select(item.id, false, false);
                }
                if ( this.focused(item.id) )
                {
                    this.focus(TV_ROOT_ITEM_ID);
                }
                
                // unselect and unfocus all children
                var itemLevel = item.itemLevel;
                for ( var loopId = this.nextItem(itemId, TVI_FIND_FIRST_CHILD); loopId; loopId = this.nextItem(loopId, TVI_FIND_NEXT) )
                {
                    var loopItem = this.items[loopId];
                    if ( loopItem.itemLevel <= itemLevel )
                    {
                        break;
                    }
                    if ( this.selected(loopItem.id) )
                    {
                        this.select(loopItem.id, false, false);
                    }
                    if ( this.focused(loopItem.id) )
                    {
                        this.focus(TV_ROOT_ITEM_ID);
                    }
                }
            }

            this.fireNotify(TVN_ITEM_VISIBLE, item.id);
            this._showItem_virt(item.id, item.flags & TVIS_HIDDEN ? false : true);
            
            return true;
        }
    }
    
    return (item.flags & TVIS_HIDDEN ? false : true);
}

////////////////////////////////////////////////////////////////////////////
// itemEnable
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.itemEnable = function(itemId, doEnable)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    if ( AP.is_set(doEnable) )
    {
        var flags = item.flags;
        if ( doEnable )
        {
            flags &= ~TVIS_DISABLED;
        }
        else
        {
            flags |= TVIS_DISABLED;
        }
        
        if ( item.flags != flags )
        {
            item.flags = flags;
            
            if ( !doEnable )
            {
                if ( this.selected(item.id) )
                {
                    this.select(item.id, false, false);
                }
                if ( this.focused(item.id) )
                {
                    this.focus(TV_ROOT_ITEM_ID);
                }
            }

            this.fireNotify(TVN_ITEM_ENABLED, item.id);
            this._assignItemIcon_virt(item.id);
            
            return true;
        }
    }
    
    return (item.flags & TVIS_DISABLED ? false : true);
}

////////////////////////////////////////////////////////////////////////////
// check
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.check = function(itemId, state)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    var flags = item.flags;
    if ( state == TVIS_CHECKED )
    {
        flags &= ~TVIS_INDETERMINATE;
        flags |= TVIS_CHECKED;
        this.checkedItems[item.id] = TVIS_CHECKED;
    }
    else if ( state == TVIS_INDETERMINATE )
    {
        flags &= ~TVIS_CHECKED;
        flags |= TVIS_INDETERMINATE;
        if ( AP.is_set(this.checkedItems[item.id]) )
        {
            delete this.checkedItems[item.id];
        }
    }
    else
    {
        flags &= ~TVIS_CHECKED;
        flags &= ~TVIS_INDETERMINATE;
        if ( AP.is_set(this.checkedItems[item.id]) )
        {
            delete this.checkedItems[item.id];
        }
    }
    
    if ( item.flags != flags )
    {
        item.flags = flags;
        this.fireNotify(TVN_ITEM_CHECKED, item.id);
        this._assignItemIcon_virt(item.id);
        
        this.fireNotify(TVN_ITEM_CHECKLISTCHANGED, {
            reason: TVN_ITEM_CHECKED
        });
        
        return true;
    }
    
    return false;
}

////////////////////////////////////////////////////////////////////////////
// checked
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.checked = function(itemId)
{
    if ( AP.is_set(itemId) )
    {
        var item = this.items[itemId];
        if ( !AP.is_set(item) )
        {
            return null;
        }
        
        return item.flags & (TVIS_CHECKED | TVIS_INDETERMINATE);
    }
    
    var ret = [];
    
    for ( loopId in this.checkedItems )
    {
        if ( this.itemVisible(loopId) )
        {
            ret.push(loopId);
        }
    }
    
    return ret;
}


////////////////////////////////////////////////////////////////////////////
// focused
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.focused = function(itemId)
{
    if ( AP.is_set(itemId) )
    {
        return (this.focusedId === itemId);
    }
    
    return this.focusedId;
}

////////////////////////////////////////////////////////////////////////////
// selected
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.selected = function(itemId)
{
    if ( AP.is_set(itemId) )
    {
        return AP.is_set(this.selectedItems[itemId]);
    }
    
    var ret = [];
    
    for ( loopId in this.selectedItems )
    {
        if ( this.itemVisible(loopId) && this.itemEnable(loopId) )
        {
            ret.push(loopId);
        }
    }
    
    return ret;
}

////////////////////////////////////////////////////////////////////////////
// itemInState
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.itemInState = function(state, itemId)
{
    if ( AP.is_set(itemId) )
    {
        var item = this.items[itemId];
        if ( !AP.is_set(item) )
        {
            return null;
        }
       
        if ( (item.flags & state) == state )
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    var ret = [];
    
    for ( var loopId = this.nextItem(TV_ROOT_ITEM_ID, TVI_FIND_NEXT);
          loopId;
          loopId = this.nextItem(loopId, TVI_FIND_NEXT) )
    {
        var item = this.items[loopId];
        if ( (item.flags & state) == state )
        {
            ret.push(loopId);
        }
    }
    
    return ret;
}

////////////////////////////////////////////////////////////////////////////
// expand
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.expand = function(itemId)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    if ( this.expanded(item.id) === false )
    {
        item.flags |= TVIS_EXPANDED;
        
        // DEBUGINFO - var tt1 = AP.getTickCount();
        this._serialize_virt(item.id);
        // DEBUGINFO - var tt2 = AP.getTickCount();
        // DEBUGINFO - DebugLog.write("tv._serialize_virt", tt2 - tt1);

        this._expandItem_virt(item.id, true);
        this.fireNotify(TVN_ITEM_EXPANDED, item.id);
        
        return true;
    }

    return false;    
}

////////////////////////////////////////////////////////////////////////////
// expandAll
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.expandAll = function(fromId)
{
    fromId = AP.is_set(fromId, TV_ROOT_ITEM_ID);
    var toId = null;
    if ( fromId != TV_ROOT_ITEM_ID )
    {
        toId = this.nextItem(fromId, TVI_FIND_NEXT_SIBLING);
    }
    
    for ( var loopId = fromId; loopId != toId; loopId = this.nextItem(loopId, TVI_FIND_NEXT) )
    {
        this.expand(loopId);
    }
        
    return true;
}

////////////////////////////////////////////////////////////////////////////
// collapse
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.collapse = function(itemId)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    if ( this.expanded(item.id) === true )
    {
        item.flags &= ~TVIS_EXPANDED;
        this._expandItem_virt(item.id, false);
        this.fireNotify(TVN_ITEM_COLLAPSED, item.id);
        
        return true;
    }
    
    if ( this.expanded(item.id) === null )
    {
        item.flags &= ~TVIS_EXPANDED;
        this._expandItem_virt(item.id, false);
    }
    
    return false;
}

////////////////////////////////////////////////////////////////////////////
// collapseAll
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.collapseAll = function(fromId)
{
    fromId = AP.is_set(fromId, TV_ROOT_ITEM_ID);
    var toId = null;
    if ( fromId != TV_ROOT_ITEM_ID )
    {
        toId = this.nextItem(fromId, TVI_FIND_NEXT_SIBLING);
    }
    
    for ( var loopId = fromId; loopId != toId; loopId = this.nextItem(loopId, TVI_FIND_NEXT) )
    {
        this.collapse(loopId);
    }
        
    return true;
}

////////////////////////////////////////////////////////////////////////////
// smartSelect
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.smartSelect = function(itemId)
{
    this._dovisible(itemId);
    this.focus(itemId);
    this.select(itemId);
}

////////////////////////////////////////////////////////////////////////////
// switchExpandState
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.switchExpandState = function(itemId)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    if ( this.expanded(item.id) === true )
    {
        return this.collapse(item.id);
    }
    
    if ( this.expanded(item.id) === false )
    {
        return this.expand(item.id);
    }
    
    return false;
}

////////////////////////////////////////////////////////////////////////////
// switchCheckState
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.switchCheckState = function(itemId)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    var state = this.checked(item.id);
    var new_state = state;
    if ( state & (TVIS_CHECKED |TVIS_INDETERMINATE) )
    {
        new_state = 0;
    }
    else
    {
        new_state = TVIS_CHECKED;
    }
    this.check(item.id, new_state);
}

////////////////////////////////////////////////////////////////////////////
// expanded
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.expanded = function(itemId)
{
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    if ( !this.nextItem(item.id, TVI_FIND_FIRST_CHILD) )
    {
        return null;
    }
    return (item.flags & TVIS_EXPANDED) ? true : false;
}

////////////////////////////////////////////////////////////////////////////
// _visible
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._visible = function(itemId)
{
    var visible = true;
    
    for ( var loopId = this.nextItem(itemId, TVI_FIND_PARENT);
          loopId && loopId != TV_ROOT_ITEM_ID;
          loopId = this.nextItem(loopId, TVI_FIND_PARENT) )
    {
        if ( this.expanded(loopId) === false )
        {
            visible = false;
            break;
        }
    }
    
    return visible;
}

////////////////////////////////////////////////////////////////////////////
// _dovisible
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._dovisible = function(itemId)
{
    var toExpand = [];
    for ( var loopId = this.nextItem(itemId, TVI_FIND_PARENT);
          loopId && loopId != TV_ROOT_ITEM_ID;
          loopId = this.nextItem(loopId, TVI_FIND_PARENT) )
    {
        toExpand.push(loopId);
    }
    for ( var loop = toExpand.length - 1; loop >= 0; loop-- )
    {
        this.expand(toExpand[loop]);
    }
}

////////////////////////////////////////////////////////////////////////////
// _serialized
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._serialized = function(itemId, serialized)
{
    return TreeViewMemory._serialized(this.items, itemId, serialized);
}

////////////////////////////////////////////////////////////////////////////
// _modified_text
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._modified_text = function(itemId, modified)
{
    var item = this.items[itemId];
    if ( AP.is_set(modified) )
    {
        if ( modified )
        {
            item.flags |= TVIS_MODIFIED_TEXT;
        }
        else
        {
            item.flags &= ~TVIS_MODIFIED_TEXT;
        }
    }
    return (item.flags & TVIS_MODIFIED_TEXT) ? true : false;
}

////////////////////////////////////////////////////////////////////////////
// _changeMaxLevel
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._changeMaxLevel = function(level)
{
    if ( level > this.maxLevel )
    {
        this.maxLevel = level;
        return true;
    }
    
    return false;
}

////////////////////////////////////////////////////////////////////////////
// _modified_icon_state
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._modified_icon_state = function(itemId, modified)
{
    var item = this.items[itemId];
    if ( AP.is_set(modified) )
    {
        if ( modified )
        {
            item.flags |= TVIS_MODIFIED_ICON;
        }
        else
        {
            item.flags &= ~TVIS_MODIFIED_ICON;
        }
    }
    return (item.flags & TVIS_MODIFIED_ICON) ? true : false;
}

////////////////////////////////////////////////////////////////////////////
// _update_modified
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._update_modified = function(itemId)
{
    if ( this._modified_icon_state(itemId) )
    {
        this._assignItemIcon_virt(itemId);
    }
    
    if ( this._modified_text(itemId) )
    {
        this._chageItemText_virt(itemId);
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// deleteItem
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.deleteItem = function(itemId)
{
    //debugger;
    if ( itemId == TV_ROOT_ITEM_ID )
    {
        return false;
    }
    
    var item = this.items[itemId];
    if ( !AP.is_set(item) )
    {
        return false;
    }
    var retVal = item._nextId;
    
    var itemLevel = item.level;
    var toDelete = new Array();
    toDelete[0] = [itemId];
    
    // Get All Child
    for ( var childId = this.nextItem(itemId, TVI_FIND_FIRST_CHILD);
          childId;
          childId = this.nextItem(childId, TVI_FIND_NEXT) )
    {
        var childItem = this.items[childId];
        var level = childItem.level - itemLevel;
        if ( level > 0 )
        {
            if ( this.fireNotify(TVN_ITEM_DELETING, childId) == -1 )
            {
                return false;
            }
            
            if ( !AP.is_set(toDelete[level]) )
            {
                toDelete[level] = new Array();
            }
            toDelete[level].push(childId);
            retVal = childItem._nextId;
        }
        else
        {
            break;
        }
    }

    var parentId = this.nextItem(itemId, TVI_FIND_PARENT);
    
    // Really Delete
    for ( var level = toDelete.length - 1; level >= 0; level-- )
    {
        var levelItems = toDelete[level];
        for ( var loop = 0; loop < levelItems.length; loop++ )
        {
            var loopId = levelItems[loop];
            var item = this.items[loopId];
            
            // check for focused
            if ( this.focused(loopId) )
            {
                this.focusedId = TV_ROOT_ITEM_ID;
            }
            // check for selected
            if ( AP.is_set(this.selectedItems[loopId]) )
            {
                delete this.selectedItems[loopId];
            }
            // check for checked
            if ( AP.is_set(this.checkedItems[loopId]) )
            {
                delete this.checkedItems[loopId];
            }
            // Last Item Id
            if ( this.lastItemId == loopId )
            {
                this.lastItemId = item._prevId;
            }
            // Last Sorted Item Id
            if ( this.lastSortedItemId == loopId )
            {
                this.lastSortedItemId = item._prevSortedId;
            }
            // Delete Item From DOM
            if ( this._serialized(loopId) )
            {
                this._deleteItem_virt(loopId);
            }
            
            this.fireNotify(TVN_ITEM_DELETED, loopId);
            
            // Modify TreeView Hierarchy
            // real order
            if ( item._prevId )
            {
                this.items[item._prevId]._nextId = item._nextId;
            }
            if ( item._nextId )
            {
                this.items[item._nextId]._prevId = item._prevId;
            }
            if ( item._prevSiblingId )
            {
                this.items[item._prevSiblingId]._nextSiblingId = item._nextSiblingId;
            }
            if ( item._nextSiblingId )
            {
                this.items[item._nextSiblingId]._prevSiblingId = item._prevSiblingId;
            }
            // sort order
            if ( item._prevSortedId )
            {
                this.items[item._prevSortedId]._nextSortedId = item._nextSortedId;
            }
            if ( item._nextSortedId )
            {
                this.items[item._nextSortedId]._prevSortedId = item._prevSortedId;
            }
            if ( item._prevSortedSiblingId )
            {
                this.items[item._prevSortedSiblingId]._nextSortedSiblingId = item._nextSortedSiblingId;
            }
            if ( item._nextSortedSiblingId )
            {
                this.items[item._nextSortedSiblingId]._prevSortedSiblingId = item._prevSortedSiblingId;
            }
            // Delete Item From Memory
            delete this.items[loopId];
            this.itemCount--;
        }
    }
    
    // If no more childrens - collapse item
    if ( parentId != TV_ROOT_ITEM_ID
         && this.expanded(parentId) === null )
    {
        this.collapse(parentId);
    }
    
    return retVal;
}

////////////////////////////////////////////////////////////////////////////
// nextItem
////////////////////////////////////////////////////////////////////////////
var TVI_FIND_POSITION_MASK = 0x000F;
var TVI_FIND_NEXT          = 0x0000;
var TVI_FIND_PREV          = 0x0001;
var TVI_FIND_NEXT_SIBLING  = 0x0002;
var TVI_FIND_PREV_SIBLING  = 0x0003;
var TVI_FIND_PARENT        = 0x0004;
var TVI_FIND_FIRST_CHILD   = 0x0005;
var TVI_FIND_LAST_CHILD    = 0x0006;
var TVI_FIND_NEXT_PARENT   = 0x0007;
var TVI_FIND_OPTIONS_MASK  = 0x00F0;
var TVI_FIND_SORTED        = 0x0010;
var TVI_FIND_SKIPDISABLED  = 0x0020;
var TVI_FIND_SKIPINVISIBLE = 0x0040;
TreeViewMemory.nextItem = function(items, itemId, flags)
{
    //debugger;
    if ( !itemId )
    {
        return null;
    }
    var item = items[itemId];
    if ( !AP.is_set(item) )
    {
        return null;
    }
    
    var itemLevel = item.level;
    var position = flags & TVI_FIND_POSITION_MASK;
    var option = flags & TVI_FIND_OPTIONS_MASK;
    var sorted = flags & TVI_FIND_SORTED;

    var retId = null;
    // part 1
    if ( position == TVI_FIND_NEXT )
    {
        retId = sorted ? item._nextSortedId : item._nextId;
    }
    else if ( position == TVI_FIND_PREV )
    {
        retId = sorted ? item._prevSortedId : item._prevId;
    }
    else if ( position == TVI_FIND_NEXT_SIBLING )
    {
        retId = sorted ? item._nextSortedSiblingId : item._nextSiblingId;
    }
    else if ( position == TVI_FIND_PREV_SIBLING )
    {
        retId = sorted ? item._prevSortedSiblingId : item._prevSiblingId;
    }
    else if ( position == TVI_FIND_PARENT )
    {
        retId = item._parentId;
    }
    // part 2
    else if ( position == TVI_FIND_FIRST_CHILD )
    {
        var nextId = TreeViewMemory.nextItem(items, itemId, TVI_FIND_NEXT | option);
        if ( nextId && AP.is_set(items[nextId]) && items[nextId].level == itemLevel + 1 )
        {
            retId = nextId;
        }
    }
    else if ( position == TVI_FIND_LAST_CHILD )
    {
        for ( var childId = TreeViewMemory.nextItem(items, itemId, TVI_FIND_FIRST_CHILD | option);
              childId;
              childId = TreeViewMemory.nextItem(items, childId, TVI_FIND_NEXT_SIBLING | option) )
        {
            retId = childId;
        }
    }
    else if ( position == TVI_FIND_NEXT_PARENT )
    {
        for ( var loopId = TreeViewMemory.nextItem(items, itemId, TVI_FIND_NEXT | option);
              loopId;
              loopId = TreeViewMemory.nextItem(items, loopId, TVI_FIND_NEXT | option) )
        {
            if ( AP.is_set(items[loopId]) && items[loopId].level < itemLevel )
            {
                retId = loopId;
                break;
            }
            else
            {
                continue;
            }
        }
    }

    if ( option & (TVI_FIND_SKIPINVISIBLE | TVI_FIND_SKIPDISABLED) )
    {
        var skipPosition = position;
        var retItem = retId ? items[retId] : null;
        while ( AP.is_set(retItem)
                && (option & TVI_FIND_SKIPINVISIBLE && retItem.flags & TVIS_HIDDEN
                    || option & TVI_FIND_SKIPDISABLED && retItem.flags & TVIS_DISABLED) )
        {
            if ( position == TVI_FIND_FIRST_CHILD
                 || position == TVI_FIND_LAST_CHILD
                 || position == TVI_FIND_NEXT_PARENT )
            {
                throw("TreeViewMemory.nextItem engine error");
            }
            retId = TreeViewMemory.nextItem(items, retId, skipPosition | sorted);
            retItem = retId ? items[retId] : null;
        }
    }
    
    return retId;
}

////////////////////////////////////////////////////////////////////////////
// def_find_text_func
// var params = {
//     tv: TreeView Instance,
//     itemId: ...,
//     findWhat: ...,
//     flags: not used
// }
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.def_find_text_func = function(params)
{
    var item = typeof params.itemId == "string" ? params.tv.items[params.itemId] : params.itemId;
    
    var fText = params.findWhat ? params.findWhat.toLowerCase() : "";
    var arrText = fText.split(/[ ;,\|\t]+/);
    
    var found = false;
    if ( params.tv.flags & TVS_COLUMNHEADER )
    {
        if ( arrText.length )
        {
            for ( var colIndex = 0; colIndex < params.tv.colCount; colIndex++ )
            {
                for ( var loop = 0; loop < arrText.length; loop++ )
                {
                    if ( item.columns[colIndex].text
                         && item.columns[colIndex].text.toLowerCase().indexOf(arrText[loop]) != -1 )
                    {
                        found = true;
                        break;
                    }
                    if ( found )
                    {
                        break;
                    }
                }
            }
        }
    }
    else
    {
        if ( item.text && arrText.length )
        {
            for ( var loop = 0; loop < arrText.length; loop++ )
            {
                if ( item.text.toLowerCase().indexOf(arrText[loop]) != -1 )
                {
                    found = true;
                    break;
                }
            }
        }
    }
    
    return found;
}

////////////////////////////////////////////////////////////////////////////
// def_sort_cmp_func
// var params = {
//     tv: TreeView Instance,
//     leftId: ...,
//     rightId: ...,
//     columnId: column Id,
//     direction: 1/-1,
//     udata: user data
// }
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.def_sort_cmp_func = function(params)
{
    var pThis = params.tv;
    var itemLeft = pThis.items[params.leftId];
    var itemRight = pThis.items[params.rightId];
    var textLeft = null;
    var textRight = null;
    if ( pThis.flags & TVS_COLUMNHEADER )
    {
        var colIndex = params.columnId;
        textLeft = AP.is_set(itemLeft.columns[colIndex].text, "");
        textRight = AP.is_set(itemRight.columns[colIndex].text, "");
    }
    else
    {
        textLeft = AP.is_set(itemLeft.text, "");
        textRight = AP.is_set(itemRight.text, "");
    }
    var l_textLeft = textLeft.toLowerCase();
    var l_textRight = textRight.toLowerCase();
    
    var retCode = 0;
    if ( l_textLeft > l_textRight )
    {
        retCode = 1*params.direction;
    }
    else if ( l_textLeft < l_textRight )
    {
        retCode = -1*params.direction;
    }

//    if ( retCode > 0 )
//    {
//        var itemElLeft = AP.getElement(params.leftId);
//        var itemElRight = AP.getElement(params.rightId);
//        itemElLeft.id = params.rightId;
//        itemElRight.id = params.leftId;
//        alert(params.rightId + " - " + params.leftId);
//    }
    
    return retCode;
}

////////////////////////////////////////////////////////////////////////////
// _sort_func
////////////////////////////////////////////////////////////////////////////
TreeViewMemory._sorted_tv = null;
TreeViewMemory._sort_func = function(leftId, rightId)
{
    return TreeViewMemory._sorted_tv._sort_data.cmp_func({
        tv: TreeViewMemory._sorted_tv,
        leftId: leftId,
        rightId: rightId,
        columnId: TreeViewMemory._sorted_tv._sort_data.columnId,
        direction: TreeViewMemory._sorted_tv._sort_data.direction,
        udata: TreeViewMemory._sorted_tv._sort_data.udata
    });
}

////////////////////////////////////////////////////////////////////////////
// _serialized
////////////////////////////////////////////////////////////////////////////
TreeViewMemory._serialized = function(items, itemId, serialized)
{
    if ( AP.is_set(serialized) )
    {
        items[itemId].flags |= TVIS_SERIALIZED;
    }
    return (items[itemId].flags & TVIS_SERIALIZED) ? true : false;
}

////////////////////////////////////////////////////////////////////////////
// _defImgList
////////////////////////////////////////////////////////////////////////////
TreeViewMemory._defImgList = function(img_list, def_img_list)
{
    return AP.is_set(img_list, AP.duplicateObject(def_img_list));
}

////////////////////////////////////////////////////////////////////////////
// nextItem
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.nextItem = function(itemId, flags)
{
    return TreeViewMemory.nextItem(this.items, itemId, flags);
}

////////////////////////////////////////////////////////////////////////////
// sort
// var params = {
//     cmp_func: function
//     columnId: column Id
//     flags: int,
//     udata: some value,
// }
////////////////////////////////////////////////////////////////////////////
var TV_SORT_BY_LEVEL = 0x01;
var TV_SORT_FORWARD  = 0x02;
var TV_SORT_BACKWARD = 0x04;
TreeViewMemory.prototype.sort = function(params)
{
    if ( !this.getItemsCount() )
    {
        return true;
    }
    params = AP.is_set(params, {});
    params.cmp_func = AP.is_set(params.cmp_func, TreeViewMemory.def_sort_cmp_func);
    params.columnId = AP.is_set(params.columnId, 0);
    params.flags = AP.is_set(params.flags, 0);
    params.direction = (params.flags & TV_SORT_BACKWARD ? -1 : 1);
    params.udata = AP.is_set(params.udata, 0);
    this._sort_data = params;
    
    var sortLevelHash = {};
    sortLevelHash[-1] = [TV_ROOT_ITEM_ID];
    
    for ( var level = -1; level < this.maxLevel; level++ )
    {
        var levelItems = sortLevelHash[level];
        sortLevelHash[level + 1] = [];
        for ( var loop = 0; loop < levelItems.length; loop++ )
        {
            var parentId = levelItems[loop];
            for ( var nextSiblingId = parentId;
                  nextSiblingId;
                  nextSiblingId = this.nextItem(nextSiblingId, TVI_FIND_NEXT_SIBLING) )
            {
                this._sortItem(nextSiblingId);
                var firstChild = this.nextItem(nextSiblingId, TVI_FIND_FIRST_CHILD);
                if ( firstChild )
                {
                    sortLevelHash[level + 1].push(firstChild);
                }
            }
        }
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// _sortItem
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._sortItem = function(parentId)
{
    var nextParentId = this.nextItem(parentId, TVI_FIND_NEXT_SIBLING | TVI_FIND_SORTED)
                       || this.nextItem(parentId, TVI_FIND_NEXT_PARENT | TVI_FIND_SORTED);
    var nextParentItem = nextParentId ? this.items[nextParentId] : null;
    
    var hashLastChild = {};
    var sortArr = [];
    var loopId = this.nextItem(parentId, TVI_FIND_FIRST_CHILD | TVI_FIND_SORTED);
    while ( loopId )
    {
        sortArr.push(loopId);
        var loopItem = this.items[loopId];
        var nextLoopId = this.nextItem(loopId, TVI_FIND_NEXT_SIBLING | TVI_FIND_SORTED);
        
        // Store Last Child for each item
        var nextLoopItem = null;
        if ( nextLoopId )
        {
            nextLoopItem = this.items[nextLoopId];
        }
        else if ( nextParentId )
        {
            nextLoopItem = this.items[nextParentId];
        }
        
        if ( nextLoopItem )
        {
            if ( loopId != nextLoopItem._prevSortedId )
            {
                hashLastChild[loopId] = nextLoopItem._prevSortedId;
            }
        }
        else
        {
            if ( loopId != this.lastSortedItemId )
            {
                hashLastChild[loopId] = this.lastSortedItemId;
            }
        }
        
        // next step
        loopId = nextLoopId;
    }
    
    if ( sortArr.length < 2 )
    {
        return true;
    }
    
    TreeViewMemory._sorted_tv = this;
    sortArr.sort(TreeViewMemory._sort_func);
    //AP.merge_sort(sortArr, TreeViewMemory._sort_func);
    TreeViewMemory._sorted_tv = null;
    
    for ( var loop = 0; loop < sortArr.length; loop++ )
    {
        var loopId = sortArr[loop];
        var loopItem = this.items[loopId];
        // prev
        if ( loop == 0 )
        {
            var parentItem = this.items[parentId];
            loopItem._prevSortedId = parentId;
            parentItem._nextSortedId = loopId;
        }
        // prev for next
        var lastChildId = AP.is_set(hashLastChild[sortArr[loop]]) ? hashLastChild[sortArr[loop]] : null;
        var nextLoopItemId = (loop <  sortArr.length - 1) ? sortArr[loop + 1] : nextParentId;
        var prevForNextLoopId = lastChildId ? lastChildId : loopId;
        if ( nextLoopItemId )
        {
            var nextLoopItem = this.items[nextLoopItemId];
            nextLoopItem._prevSortedId = prevForNextLoopId;
        }
        if ( lastChildId )
        {
            var lastChildItem = this.items[lastChildId];
            lastChildItem._nextSortedId = nextLoopItemId;
            if ( !lastChildItem._nextSortedId )
            {
                this.lastSortedItemId = lastChildId;
            }
        }
        // next
        var nextItem = loopItem._nextSortedId ? this.items[loopItem._nextSortedId] : null;
        if ( !nextItem || nextItem.level <= loopItem.level )
        {
            if ( loop < (sortArr.length - 1) )
            {
                loopItem._nextSortedId = sortArr[loop + 1];
            }
            else
            {
                loopItem._nextSortedId = nextParentId;
            }
            // Store as Last Sorted Item Id
            if ( !loopItem._nextSortedId )
            {
                this.lastSortedItemId = loopId;
            }
        }
        // prevSibling
        loopItem._prevSortedSiblingId = (loop > 0 ? sortArr[loop - 1] : null);
        // nextSibling            
        loopItem._nextSortedSiblingId = (loop < (sortArr.length - 1) ? sortArr[loop + 1] : null);
    }
    
    var loop = 0;
    var leftEl = null;
    if ( parentId == TV_ROOT_ITEM_ID )
    {
        leftEl = AP.firstChild(this.tvContainer);
    }
    else if ( parentId )
    {
        var parentEl = AP.getElement(parentId);
        var parentDOM = parentEl.instance;
        leftEl = AP.firstChild(parentDOM.divSubItems);
    }
    while ( loop < sortArr.length && leftEl )
    {
        var leftId = leftEl.id;
        var rightId = sortArr[loop];
        
        if ( leftId != rightId )
        {
            var rightEl = AP.getElement(rightId);
            AP.swapNodes(leftEl, rightEl);
            leftEl = AP.nextSibling(rightEl);
        }
        else
        {
            leftEl = AP.nextSibling(leftEl);
        }
        loop++;
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// filter
// var params = {
//     cmp_func: function
//     findWhat: item
//     flags: int
// }
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.filter = function(params)
{
    if ( this.maxLevel > 0 )
    {
        DebugLog.write("TODO:// TreeView filter currently not supported");
        return false;
    }
    if ( !this.getItemsCount() )
    {
        return true;
    }
    params = AP.is_set(params, {});
    params.cmp_func = AP.is_set(params.cmp_func, TreeViewMemory.def_find_text_func);
    
    for ( var loopId = this.nextItem(TV_ROOT_ITEM_ID, TVI_FIND_FIRST_CHILD); loopId; loopId = this.nextItem(loopId, TVI_FIND_NEXT) )
    {
        var found = params.cmp_func({
            tv: this,
            itemId: loopId,
            findWhat: params.findWhat
        });
        if ( found )
        {
            this.itemVisible(loopId, true);
        }
        else
        {
            this.itemVisible(loopId, false);
        }
    }
    
    this.fireNotify(TVN_FILTERED, null);
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// findItem
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.findItem = function(itemFrom, findWhat, compare_function)
{
    var resultId = null;
    itemFrom = AP.is_set(itemFrom, TV_ROOT_ITEM_ID);
    for ( var loopId = itemFrom; loopId; loopId = this.nextItem(loopId, TVI_FIND_NEXT) )
    {
        if ( AP.is_set(compare_function) )
        {
            if ( !compare_function(this.items[loopId], findWhat) )
            {
                resultId = loopId;
                break;
            }
        }
        else if ( this._compareItems_virt(this.items[loopId], findWhat) == 0 )
        {
            resultId = loopId;
            break;
        }
    }
    
    return resultId;
}

////////////////////////////////////////////////////////////////////////////
// findItemData
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.findItemData = function(itemFrom, itemData, compare_function)
{
    var resultId = null;
    itemFrom = AP.is_set(itemFrom, TV_ROOT_ITEM_ID);
    for ( var loopId = itemFrom; loopId; loopId = this.nextItem(loopId, TVI_FIND_NEXT) )
    {
        var item = this.items[loopId];
        if ( AP.is_set(compare_function) )
        {
            if ( !compare_function(item.data, itemData) )
            {
                resultId = loopId;
                break;
            }
        }
        else if ( !this._compareItemsData_virt(item.data, itemData) )
        {
            resultId = loopId;
            break;
        }
    }
    
    return resultId;
}

////////////////////////////////////////////////////////////////////////////
// _compareItems_virt
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._compareItems_virt = function(itemLeft, itemRight)
{
    return this._compareItemsData_virt(itemLeft.data, itemRight.data);
}

////////////////////////////////////////////////////////////////////////////
// _compareItemsData_virt
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._compareItemsData_virt = function(dataLeft, dataRight)
{
    return (dataLeft == dataRight ? 0 : 1);
}

////////////////////////////////////////////////////////////////////////////
// updateItem
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype.updateItem = function(itemId, tvItem)
{
    // Update Text
    if ( this.flags & TVS_COLUMNHEADER )
    {
        for ( var colIndex in tvItem.columns )
        {
            if ( AP.is_set(tvItem.columns[colIndex].text) )
            {
                this.itemText({
                    itemId: itemId,
                    columnId: colIndex,
                    text: tvItem.columns[colIndex].text
                });
            }
        }
    }
    else
    {
        if ( AP.is_set(tvItem.text) )
        {
            this.itemText({
                itemId: itemId,
                text: tvItem.text
            });
        }
    }
    // Check Data
    if ( AP.is_set(tvItem.data) )
    {
        this.itemData(itemId, tvItem.data);
    }
    // Check Image List
    if ( AP.is_set(tvItem.img_list) )
    {
        this.itemImage(itemId, tvItem.img_list);
    }
    // Check States
    if ( AP.is_set(tvItem.flags) )
    {
        var checkState = tvItem.flags & (TVIS_CHECKED | TVIS_INDETERMINATE);
        this.check(itemId, checkState);
        // ??? var disableState = params.tree[loopRight].flags & (TVIS_CHECKED | TVIS_INDETERMINATE);
    }    
}

////////////////////////////////////////////////////////////////////////////
// updateTree
////////////////////////////////////////////////////////////////////////////
var TV_UPDATE_TREE_NONOTIFICATION       = 0x01;
/*
var params = {
    parentId: Tree Node which needs to be updated,
    parentItem: TV Item (if needs to Update parentId)
    tree: treeItems struct such as in TreeViewMemory.insertItem,
    flags: value
}
*/
TreeViewMemory.prototype.updateTree = function(params)
{
    // DEBUGINFO - var tt0 = AP.getTickCount();
    var acceptFlagsMask = TVIS_CHECKED | TVIS_INDETERMINATE | TVIS_DISABLED;
    var column_header = (this.flags & TVS_COLUMNHEADER ? true : false);
    var maxLevel = this.maxLevel;
    
    //debugger;
    // DEBUGINFO - var tt1 = AP.getTickCount();
    // Prepare To Update
    var parentId = AP.is_set(params.parentId, TV_ROOT_ITEM_ID);
    var parentItem = this.items[parentId];
    if ( !AP.is_set(parentItem) )
    {
        throw("TreeViewMemory updateTree: Parent Not Found [" + parentId + "]");
    }
    var levelOffset = parentItem.level + 1;
    var leftId = this.nextItem(parentId, TVI_FIND_NEXT);
    var stopId = this.nextItem(parentId, TVI_FIND_NEXT_SIBLING);
    var loopRight = 0;
    
    // Finilize Update
    this.blockNotifyCodes(TVN_ITEM_INSERTED, TVN_ITEM_DELETED, TVN_ITEM_FOCUSED, TVN_ITEM_SELECTED);
    this._beforeUpdateTree_virt();
    if ( !(params.flags & TV_UPDATE_TREE_NONOTIFICATION) )
    {
        this.fireNotify(TVN_BEFORE_UPDATE_TREE, null);
    }
    
    // Start Update
    var parentIds = new Object();
    // Store level parent
    parentIds[parentItem.level - levelOffset] = parentId;
    // Start loop
    while ( leftId != stopId && loopRight < params.tree.length )
    {
        var leftItem = this.items[leftId];
        if ( (leftItem.level - levelOffset) < params.tree[loopRight].level )
        {
            // Insert From Right To Left
            // DEBUGINFO - var mm0 = AP.getTickCount();
            while ( loopRight < params.tree.length
                    && (leftItem.level - levelOffset) < params.tree[loopRight].level )
            {
                maxLevel = Math.max(maxLevel, params.tree[loopRight].level + levelOffset);
                var tvItem = {
                    parent: parentIds[params.tree[loopRight].level - 1],
                    img_list: TreeViewMemory._defImgList(params.tree[loopRight].img_list, this.imgList.img_list),
                    columns: params.tree[loopRight].columns,
                    text: params.tree[loopRight].text,
                    data: params.tree[loopRight].data,
                    flags: TVI_INSERT_LAST_CHILD
                };
                if ( column_header )
                {
                    for ( var colIndex = 0; colIndex < this.colCount; colIndex++ )
                    {
                        if ( AP.is_set(params.tree[loopRight].columns[colIndex]) )
                        {
                            tvItem.columns[colIndex] = params.tree[loopRight].columns[colIndex];
                        }
                    }
                }
                
                var parentId = this.insertItem(tvItem);
                parentIds[params.tree[loopRight].level] = parentId; // store level parent
                loopRight++;
            }
            // DEBUGINFO - kk0 += (AP.getTickCount() - mm0);
        }
        else if ( (leftItem.level - levelOffset) > params.tree[loopRight].level )
        {
            // Delete Left
            // DEBUGINFO - var mm0 = AP.getTickCount();
            leftId = this.deleteItem(leftId);
            // DEBUGINFO - kk1 += (AP.getTickCount() - mm0);
        }
        else
        {
            // Compare Item
            // DEBUGINFO - var mm0 = AP.getTickCount();
            if ( this._compareItems_virt(leftItem, params.tree[loopRight]) == 0 )
            {
                // Do Nothing, just...
                // Update item
                this.updateItem(leftId, params.tree[loopRight]);
                // Store parent                
                parentIds[leftItem.level - levelOffset] = leftId;
                leftId = this.nextItem(leftId, TVI_FIND_NEXT);
                loopRight++;
            }
            else
            {
                // Try To Find Left Item in RightSet
                var rightItemLevel = params.tree[loopRight].level;
                for ( var loop = loopRight + 1; loop < params.tree.length; loop++ )
                {
                    if ( params.tree[loop].level == rightItemLevel )
                    {
                        if ( this._compareItems_virt(leftItem, params.tree[loop]) == 0 )
                        {
                            // found
                            while ( loopRight < loop )
                            {
                                // Insert From Right to Left
                                var rightLevel = params.tree[loopRight].level + levelOffset;
                                maxLevel = Math.max(maxLevel, rightLevel);
                                var tvItem = {
                                    img_list: TreeViewMemory._defImgList(params.tree[loopRight].img_list, this.imgList.img_list),
                                    columns: params.tree[loopRight].columns,
                                    text: params.tree[loopRight].text,
                                    data: params.tree[loopRight].data
                                };
                                if ( rightLevel == leftItem.level )
                                {
                                    tvItem.parent = leftItem.id;
                                    tvItem.flags = TVI_INSERT_PREV_SIBLING;
                                }
                                else
                                {
                                    tvItem.parent = parentIds[params.tree[loopRight].level - 1];
                                    tvItem.flags = TVI_INSERT_LAST_CHILD;
                                }
                                if ( column_header )
                                {
                                    for ( var colIndex = 0; colIndex < this.colCount; colIndex++ )
                                    {
                                        if ( AP.is_set(params.tree[loopRight].columns[colIndex]) )
                                        {
                                            tvItem.columns[colIndex] = params.tree[loopRight].columns[colIndex];
                                        }
                                    }
                                }
                                var parentId = this.insertItem(tvItem);
                                parentIds[params.tree[loopRight].level] = parentId; // store level parent
                                loopRight++;
                            }
                            break;
                        }
                        else
                        {
                            // not found - try next item
                            continue;
                        }
                    }
                    else if ( params.tree[loop].level < rightItemLevel )
                    {
                        // not found - break loop
                        loop = params.tree.length;
                        break;
                    }
                    else if ( params.tree[loop].level > rightItemLevel )
                    {
                        // child Items - just continue loop
                        continue;
                    }
                }
                if ( loop == params.tree.length )
                {
                    // Delete Left
                    leftId = this.deleteItem(leftId);
                }
            }
            // DEBUGINFO - kk2 += (AP.getTickCount() - mm0);
        }
    }
    
    // DEBUGINFO - var tt2 = AP.getTickCount();
    // Finilize update
    // Delete All After loopLeft
    while ( leftId != stopId )
    {
        leftId = this.deleteItem(leftId);
    }
    // DEBUGINFO - var tt3 = AP.getTickCount();
    // Insert All From Right[loopRight] to Left
    while ( loopRight < params.tree.length )
    {
        maxLevel = Math.max(maxLevel, params.tree[loopRight].level + levelOffset);
        var tvItem = {
            parent: parentIds[params.tree[loopRight].level - 1],
            img_list: TreeViewMemory._defImgList(params.tree[loopRight].img_list, this.imgList.img_list),
            columns: params.tree[loopRight].columns,
            text: params.tree[loopRight].text,
            data: params.tree[loopRight].data,
            flags: TVI_INSERT_LAST_CHILD
        };
        var parentId = this.insertItem(tvItem);
        parentIds[params.tree[loopRight].level] = parentId; // store level parent
        loopRight++;
    }    

    // Recalculate Dynamic Styles
    this._changeMaxLevel(maxLevel);
    
    //debugger;
    if ( AP.is_set(params.parentItem) )
    {
        this.updateItem(params.parentId, params.parentItem);
    }
    
    // Finilize Update
    this.unBlockNotifyCodes(TVN_ITEM_INSERTED, TVN_ITEM_DELETED, TVN_ITEM_FOCUSED, TVN_ITEM_SELECTED);
    this._afterUpdateTree_virt();
    if ( !(params.flags & TV_UPDATE_TREE_NONOTIFICATION) )
    {
        this.fireNotify(TVN_AFTER_UPDATE_TREE, null);
    }

    // DEBUGINFO - var tt4 = AP.getTickCount();
    // DEBUGINFO - DebugLog.writeErr("tv.upadteTree", tt4 - tt0, tt1 - tt0, tt2 - tt1, tt3 - tt2, tt4 - tt3, " KK counters ", kk0, kk1, kk2);
}

////////////////////////////////////////////////////////////////////////////
// _getItem
////////////////////////////////////////////////////////////////////////////
TreeViewMemory.prototype._getItem = function(itemId)
{
    return this.items[itemId];
}

////////////////////////////////////////////////////////////////////////////
// class TreeViewEx extends TreeViewMemory
////////////////////////////////////////////////////////////////////////////
// TreeViewEx DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="ttree_view">
    <div class="ttv_search_contaniner">
        <table class="ttv_search_controls">
            <tr>
                <td class="ttv_sc_search"></td>
                <td class="ttv_sc_input"><input type=text></td>
                <td class="ttv_sc_close"></td>
            </tr>
        </table>
    </div>
    <div class="ttree_view_contaniner">
        <div class="ttv_item_container">
            <div class="ttv_item">
                <div class="ttv_subitem_manage">
                </div>
                <div class="ttv_subitem_icon">
                </div>
                <div class="ttv_subitem_text">
                    Item1
                </div>
            </div>
            <div class="ttv_sub_items_container">
                ...
                SubItems
                ...
            </div>
        </div>
    </div>
</div>
*/
// flags
var TVS_NO_LINES            = 0x00000001;
var TVS_CONFIRM_ON_DELETE   = 0x00000002;
var TVS_EDITABLE            = 0x00000004;
var TVS_MULTISELECT         = 0x00000008;
var TVS_MANAGEICON          = 0x00000010;
var TVS_CHECKBOX            = 0x00000020;
var TVS_ITEMICON            = 0x00000040;
var TVS_MOUSERESPONSIBLE    = 0x00000080;
var TVS_QUICKSEARCH         = 0x00000100;
var TVS_QUICKSEARCH_ON      = 0x00000200;
var TVS_QUICKFILTER         = 0x00000400;
var TVS_QUICKFILTER_ON      = 0x00000800;
var TVS_DND_NOTIFICATION    = 0x00001000;
var TVS_COLUMNHEADER        = 0x00002000;
var TVS_FULLROWSELECT       = 0x00004000;
// flags for editing mode
var TVS_EDITING_MODE        = 0xF0000000;
//var TVS_UNDO_ON_ESCAPE     = 0x1000;
//var TVS_CONFIRM_ON_ESCAPE  = 0x2000;

////////////////////////////////////////////////////////////////////////////
// TreeViewEx constructor
////////////////////////////////////////////////////////////////////////////
TreeViewEx = function(imgList)
{
    this.subclass = TreeViewMemory;
    this.subclass(imgList);
    
    this.className = "TreeViewEx";
    this.quickSearch = {
        element: null,
        tblControls: null,
        modeIcon: null,
        input: null,
        lastId: TV_ROOT_ITEM_ID
    };
    
    this._hdr = null;
    
    this._edtId = null; // !!! internal use only (for editing mode)
    this._overstate = {
        over_control: false,
        over_timer: null,
        drag_over: {
            item: null,
            state: null,
            effect: null,
            retValue: null,
            ret: null,
            timer: null
        }
    }; // !!! internal use only (for TVS_MOUSERESPONSIBLE style)
    
    this._styleSheet = null; // Style Sheet For Column Management
}
TreeViewEx.prototype = new TreeViewMemory;

////////////////////////////////////////////////////////////////////////////
// select
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.select = function(itemId, bSelect, bUnique)
{
    if ( !(this.flags & TVS_MULTISELECT) )
    {
        bUnique = true;
    }
    this.subclass = TreeViewMemory.prototype.select;
    return this.subclass(itemId, bSelect, bUnique);
}

////////////////////////////////////////////////////////////////////////////
// enable
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.enable = function(doEnable)
{
    if ( AP.is_set(doEnable) && !doEnable && this._edtId )
    {
        this.endItemTextEditing(this._edtId, false);
    }
    this.subclass = TreeViewMemory.prototype.enable;
    return this.subclass(doEnable);
}

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.create = function(elParent, flags, tabIndex)
{
    this.subclass = TreeViewMemory.prototype.create;
    this.subclass(elParent, "ttree_view");
    
    this.flags = AP.is_set(flags, 0);
    
    this.tabIndex( AP.is_set(tabIndex, 0) );
    
    if ( this.flags & (TVS_QUICKSEARCH | TVS_QUICKFILTER) )
    {
        this.quickSearch.element = AP.createElement("div", this.element, "ttv_search_contaniner");
        if ( !(this.flags & (TVS_QUICKSEARCH_ON | TVS_QUICKFILTER_ON)) )
        {
            AP.show(this.quickSearch.element, false);
        }
        
        var cells = [[[ null, [
            ["ttv_sc_mode", null, null, null, null],
            ["ttv_sc_input", null, null, null, null],
            ["ttv_sc_close", null, null, null, null]
        ]]]];
        this.quickSearch.tblControls = AP.createTable(0, 0, cells, this.quickSearch.element, "ttv_search_controls");
        this.quickSearch.modeIcon = this.quickSearch.tblControls.tBodies[0].rows[0].cells[0];
        var cellInput = this.quickSearch.tblControls.tBodies[0].rows[0].cells[1];
        this.quickSearch.input = AP.createElement("input", cellInput, "ttv_search_input");
        this.quickSearch.input.tabIndex = this.tabIndex();
        this.qsSwitchMode(this.flags & (TVS_QUICKSEARCH_ON | TVS_QUICKFILTER_ON));

        AP.addEvent(this.quickSearch.element, "click", TreeViewEx.onQuickSearchClick);
        AP.addEvent(this.quickSearch.element, "keydown", TreeViewEx.onQuickSearchKeyPress);
    }
    
    var hd = document.getElementsByTagName("HEAD")[0];
    this._styleSheet = document.createElement("STYLE");
    hd.appendChild(this._styleSheet);
    
    if ( this.flags & TVS_COLUMNHEADER )
    {
        this._hdr = new ColumnHeader();
        this._hdr.create(this.element);
        this._hdr.addNotifyHandler( [TreeViewEx.onColumnHeaderNotify, this] );
        this.colCount = this._hdr.getItemsCount();
    }
    else
    {
        this.colCount = 1;
    }
    
    this.tvContainer = AP.createElement("div", this.element, "ttv_tree_contaniner");
    this.tvContainer.tabIndex = this.tabIndex();
    AP.addEvent(this.tvContainer, "scroll", TreeViewEx.onScroll);
    
    AP.addEvent(this.tvContainer, "click", TreeViewEx.onMouseAction);
    if ( this.flags & TVS_MOUSERESPONSIBLE )
    {
        AP.addEvent(this.tvContainer, "mouseover", TreeViewEx.onMouseAction);
        AP.addEvent(this.tvContainer, "mouseout", TreeViewEx.onMouseAction);
    }
    AP.addEvent(this.tvContainer, "dblclick", TreeViewEx.onMouseAction);
    
    AP.addEvent(this.element, "contextmenu", TreeViewEx.onCommonDOMEvent);
    
    // capturing keypress for arrows doesn't work for IE, so keydown
    AP.addEvent(this.tvContainer, "keydown", TreeViewEx.onKeyPress);
    // TODO: tabIndex on tvContainer and TreeViewEx.onKeyPress
    // on tvContainer - not on this.element
    // AP.addEvent(this.element, "keypress", TreeViewEx.onKeyPress);
    
    // Prevent Text Selection in IE
    AP.addEvent(this.tvContainer, "selectstart", AP.onRestrictedEvent);
    
    if ( this.flags & TVS_DND_NOTIFICATION && AP.isIE() )
    {
        AP.addEvent(this.element, "dragover", TreeViewEx.onCommonDOMEvent);
        AP.addEvent(this.element, "drop", TreeViewEx.onCommonDOMEvent);
    }
    
    this._setSubItemPosition();
}

////////////////////////////////////////////////////////////////////////////
// insertColumn
// var hdrItem - see ColumnHeader.prototype.insertItem for details
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.insertColumn = function(hdrItem)
{
    if ( !(this.flags & TVS_COLUMNHEADER) )
    {
        throw ("TreeViewEx.prototype.insertColumn: not supported");
    }
    
    var retVal = this._hdr.insertItem(hdrItem);
    var hdrIndex = this._hdr._getItemIndex(retVal.id);
    this.colCount = this._hdr.getItemsCount();
    
    for ( var loopId in this.items )
    {
        var item = this.items[loopId];
        var serialized = this._serialized(loopId);
        var itemEl = null;
        var domItem = null;
        
        var tmpColumns = {
        };
        if ( serialized )
        {
            itemEl = AP.getElement(item.id);
            domItem = itemEl.instance;
        }
        var tmpDivCols = {
        };
        for ( var colIndex = 0; colIndex < this.colCount; colIndex++ )
        {
            if ( colIndex < hdrIndex )
            {
                tmpColumns[colIndex] = item.columns[colIndex];
                if ( serialized )
                {
                    tmpDivCols[colIndex] = domItem.divColumns[colIndex];
                }
            }
            else if ( colIndex == hdrIndex )
            {
                tmpColumns[colIndex] = {
                    text: null,
                    data: null,
                    flags: 0
                };
                if ( serialized )
                {
                    var className = "ttv_sitem ";
                    if ( this.flags & TVS_COLUMNHEADER )
                    {
                        className += "ellipsis ";
                    }
                    className += "show-on-hover ttv_sitem_" + item.level + "_" + colIndex;
                    tmpDivCols[colIndex] = AP.createElement("div", domItem.divItem, className);
                    tmpDivCols[colIndex].noWrap = true;
                    var htmText = "";
                    tmpDivCols[colIndex].innerHTML = "";
                }
            }
            else if ( colIndex > hdrIndex )
            {
                tmpColumns[colIndex] = item.columns[colIndex - 1];
                if ( serialized )
                {
                    var className = "ap ttv_sitem ";
                    if ( this.flags & TVS_COLUMNHEADER )
                    {
                        className += "ellipsis ";
                    }
                    className += "show-on-hover ttv_sitem_" + item.level + "_" + colIndex;
                    tmpDivCols[colIndex] = domItem.divColumns[colIndex - 1];
                    tmpDivCols[colIndex].className = className;
                }
            }
        }
        item.columns = tmpColumns;
        if ( serialized )
        {
            domItem.divColumns = tmpDivCols;
        }
    }
    
    this._setSubItemPosition();
    
    return retVal;
}

////////////////////////////////////////////////////////////////////////////
// setColumn
// var hdrItem - see ColumnHeader.prototype.setColumn for details
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.setColumn = function(hdrItem)
{
    if ( !(this.flags & TVS_COLUMNHEADER) )
    {
        throw ("TreeViewEx.prototype.insertColumn: not supported");
    }
    return this._hdr.setItem(hdrItem);
}

////////////////////////////////////////////////////////////////////////////
// deleteColumn
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.deleteColumn = function(itemId)
{
    if ( !(this.flags & TVS_COLUMNHEADER) )
    {
        throw ("TreeViewEx.prototype.insertColumn: not supported");
    }
    
    var hdrIndex = this._hdr._getItemIndex(itemId);
    
    for ( loopId in this.items )
    {
        var item = this.items[loopId];
        var serialized = this._serialized(itemIndex);
        var itemEl = null;
        var domItem = null;
        
        var tmpColumns = {
        };
        if ( serialized )
        {
            itemEl = AP.getElement(item.id);
            domItem = itemEl.instance;
        }
        var tmpDivCols = {
        };
        for ( var colIndex = 0; colIndex < this.colCount; colIndex++ )
        {
            if ( colIndex < hdrIndex )
            {
                tmpColumns[colIndex] = item.columns[colIndex];
                if ( serialized )
                {
                    tmpDivCols[colIndex] = domItem.divColumns[colIndex];
                }
            }
            else if ( colIndex == hdrIndex )
            {
                if ( serialized )
                {
                    domItem.divItem.removeChild(domItem.divColumns[colIndex]);
                }
            }
            else if ( colIndex > hdrIndex )
            {
                tmpColumns[colIndex - 1] = item.columns[colIndex];
                if ( serialized )
                {
                    var className = "ap ttv_sitem ";
                    if ( this.flags & TVS_COLUMNHEADER )
                    {
                        className += "ellipsis ";
                    }
                    className += "show-on-hover ttv_sitem_" + item.level + "_" + (colIndex - 1);
                    tmpDivCols[colIndex - 1] = domItem.divColumns[colIndex];
                    tmpDivCols[colIndex - 1].className = className;
                }
            }
        }
        item.columns = tmpColumns;
        if ( serialized )
        {
            domItem.divColumns = tmpDivCols;
        }
    }
    
    var retVal = this._hdr.deleteItem(itemId);
    this.colCount = this._hdr.getItemsCount();
    
    this._setSubItemPosition();
    
    return retVal;
}

////////////////////////////////////////////////////////////////////////////
// _changeMaxLevel
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._changeMaxLevel = function(level)
{
    this.subclass = TreeViewMemory.prototype._changeMaxLevel;
    if ( this.subclass(level) )
    {
        // Recalculate Dynamic Styles
        this._setSubItemPosition();
    }
}

////////////////////////////////////////////////////////////////////////////
// _setSubItemPosition
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._setSubItemPosition = function()
{
    var stText = "";
    var manageicon_offset = (this.flags & TVS_MANAGEICON) ? 20 : 0;
    var checkbox_offset = (this.flags & TVS_CHECKBOX) ? 17 : 0;
    var itemicon_offset = (this.flags & TVS_ITEMICON) ? 20 : 0;
    var column_header = (this.flags & TVS_COLUMNHEADER) ? true : false;
    
    if ( column_header )
    {
        for ( var loopLevel = 0; loopLevel <= this.maxLevel; loopLevel++ )
        {
            var levelOffset = 16*loopLevel;
            var columnOffset = 0;
            for ( var loopCol = 0; loopCol < this.colCount; loopCol++ )
            {
                var hdrItem = this._hdr.getItem(loopCol);
                var col_left = columnOffset;
                var col_width = hdrItem.width - 2; // "-2" - !!!!!!HACK!!!!!!
                
                var itemOffset = 0;
                if ( loopCol == 0 )
                {
                    itemOffset += manageicon_offset;
                    itemOffset += checkbox_offset;
                    itemOffset += itemicon_offset;
                    col_left += itemOffset;
                    col_width -= itemOffset;
                    col_width -= levelOffset;
                }
                else
                {
                    col_left -= levelOffset;
                }

                stText += ".ttree_view#" + this.element.id + " ";
                stText += ".ttv_sitem_" + loopLevel + "_" + loopCol + " {";
                if ( col_width > 0 )
                {
                    stText += "left: " + col_left + "px;"
                    stText += "width: " + col_width + "px;"
                }
                else
                {
                    stText += "display: none;";
                }
                stText += "}\r\n";
                
                columnOffset += hdrItem.width;
            }
        }
    }
    else
    {
        var left = 0;
        stText += ".ttree_view#" + this.element.id + " ";
        stText += ".ttv_sitem {";
        left += manageicon_offset;
        left += checkbox_offset;
        left += itemicon_offset;
        stText += "left: " + left + "px;"
        stText += "width: auto";
        stText += "}\r\n";
    }
    
    // DEBUGINFO - DebugLog.write(this.maxLevel, this.colCount, stText);
    if ( this._styleSheet.styleSheet )
    {
        this._styleSheet.styleSheet.cssText = stText;
    }
    else
    {
        this._styleSheet.innerHTML = stText;
    }
}

////////////////////////////////////////////////////////////////////////////
// setFocusOnTree
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.setFocusOnTree = function()
{
    this.tvContainer.focus();
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.setPosition = function(pos)
{
    pos.flags = AP.is_set(pos.flags, 0);
    this.subclass = TreeViewMemory.prototype.setPosition;
    this.subclass(pos);
    
    if ( AP.is_set(pos.cy) !== false
         || (pos.flags & POS_FLAGS_RECALCULATE) )
    {
        var y = 0;
        var cy = AP.clientHeight(this.element);
        if ( this.flags & TVS_COLUMNHEADER )
        {
            y += AP.offsetHeight(this._hdr.element);
            y += 2;
        }

        if ( this.flags & (TVS_QUICKSEARCH | TVS_QUICKFILTER)
             && this.qsShow() )
        {
            AP.setPosition(this.quickSearch.element, {
                y: y
            });
            y += AP.offsetHeight(this.quickSearch.element);
            y += 2;
        }
        
        cy -= y;
        AP.setPosition(this.tvContainer, {
            y: y,
            cy: cy
        });
    }
    
    if ( this.flags & TVS_COLUMNHEADER )
    {
        if ( AP.is_set(pos.cx) !== false
             || (pos.flags & POS_FLAGS_RECALCULATE) )
        {
            var left = 0;
            var width = AP.clientWidth(this.element);
            if ( this.tvContainer.scrollLeft )
            {
                left = -this.tvContainer.scrollLeft;
                width = this.tvContainer.scrollWidth;
            }
            this._hdr.setPosition({
                x: left,
                cx: width
            });
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// qsSwitchMode
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.qsSwitchMode = function(mode)
{
    if ( !AP.is_set(mode) )
    {
        mode = this.flags & TVS_QUICKSEARCH_ON ? TVS_QUICKFILTER_ON : TVS_QUICKSEARCH_ON;
    }
    if ( this.flags & TVS_QUICKSEARCH
         && this.flags & TVS_QUICKFILTER )
    {
        if ( mode & TVS_QUICKSEARCH_ON )
        {
            this.flags &= ~TVS_QUICKFILTER_ON;
            this.flags |= TVS_QUICKSEARCH_ON;
        }
        else
        {
            this.flags &= ~TVS_QUICKSEARCH_ON;
            this.flags |= TVS_QUICKFILTER_ON;
        }
    }
    else if ( this.flags & TVS_QUICKSEARCH )
    {
        this.flags &= ~TVS_QUICKFILTER_ON;
        this.flags |= TVS_QUICKSEARCH_ON;
    }
    else if ( this.flags & TVS_QUICKFILTER )
    {
        this.flags &= ~TVS_QUICKSEARCH_ON;
        this.flags |= TVS_QUICKFILTER_ON;
    }
    else
    {
        this.flags &= ~TVS_QUICKSEARCH_ON;
        this.flags &= ~TVS_QUICKFILTER_ON;
    }
    
    if ( this.flags & TVS_QUICKSEARCH_ON )
    {
        AP.removeClassName(this.quickSearch.modeIcon, "ttv_sc_filter");
        AP.appendClassName(this.quickSearch.modeIcon, "ttv_sc_search");
    }
    else if ( this.flags & TVS_QUICKFILTER_ON )
    {
        AP.removeClassName(this.quickSearch.modeIcon, "ttv_sc_search");
        AP.appendClassName(this.quickSearch.modeIcon, "ttv_sc_filter");
    }
    else
    {
        AP.removeClassName(this.quickSearch.modeIcon, "ttv_sc_search");
        AP.removeClassName(this.quickSearch.modeIcon, "ttv_sc_filter");
    }
}

////////////////////////////////////////////////////////////////////////////
// qsShow
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.qsShow = function(show, setFocus)
{
    if ( AP.is_set(show) )
    {
        if ( show )
        {
            AP.show(this.quickSearch.element, true);
            if ( setFocus )
            {
                this.quickSearch.input.focus();
                this.quickSearch.input.select();
            }
        }
        else
        {
            if ( this.flags & TVS_QUICKFILTER_ON )
            {
                // reset filter
                this.filter({});
            }
            if ( setFocus )
            {
                this.tvContainer.focus();
            }
            AP.show(this.quickSearch.element, false);
        }
        
        this.setPosition({flags: POS_FLAGS_RECALCULATE});
    }
    
    return AP.show(this.quickSearch.element);
}

////////////////////////////////////////////////////////////////////////////
// qsFilter
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.qsFilter = function()
{
    var findText = this.quickSearch.input.value;
    return this.filter({
        findWhat: findText
    });
}

////////////////////////////////////////////////////////////////////////////
// qsSearch
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.qsSearch = function()
{
    var findText = this.quickSearch.input.value;
    if ( !findText.length )
    {
        return;
    }
    
    if ( this.quickSearch._tmp_notFoundTimer )
    {
        clearTimeout(this.quickSearch._tmp_notFoundTimer);
        this.quickSearch._tmp_notFoundTimer = null;
        AP.removeClassName(this.quickSearch.element, "not_found");
    }
    
    if ( !AP.is_set(this.items[this.quickSearch.lastId]) )
    {
        this.quickSearch.lastId = TV_ROOT_ITEM_ID;
    }
    
    for ( this.quickSearch.lastId = this.nextItem(this.quickSearch.lastId, TVI_FIND_NEXT | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED);
          this.quickSearch.lastId;
          this.quickSearch.lastId = this.nextItem(this.quickSearch.lastId, TVI_FIND_NEXT | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED) )
    {
        var found = TreeViewMemory.def_find_text_func({
            tv: this,
            itemId: this.quickSearch.lastId,
            findWhat: findText
        });
        if ( found )
        {
            break;
        }
    }
    
    if ( this.quickSearch.lastId )
    {   // Found
        this.focus(this.quickSearch.lastId);
        this.select(this.quickSearch.lastId, true, true);
        this._dovisible(this.quickSearch.lastId);
        this.scrollItemIntoView(this.quickSearch.lastId);
    }
    else
    {   // Not Found
        this.quickSearch.lastId = TV_ROOT_ITEM_ID;
        AP.appendClassName(this.quickSearch.element, "not_found");
        var timerFunc = "var el=AP.getElement(\"" + this.element.id + "\");";
        timerFunc += "if(el)AP.removeClassName(el.instance.quickSearch.element, \"not_found\");";
        this.quickSearch._tmp_notFoundTimer = setTimeout(timerFunc, 1000);
    }
}

////////////////////////////////////////////////////////////////////////////
// _stateToClassName
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._stateToClassName = function()
{
    var className = "ap ttree_view";
    if ( this._state & APS_DISABLED )
    {
        className += " ttree_view_disabled";
    }
    if ( this.element.className != className )
    {
        this.element.className = className;
    }
    
    var selExist = false;
    for ( itemId in this.selectedItems )
    {
        selExist = true;
        break;
    }
    
    if ( this.focusedId != TV_ROOT_ITEM_ID || selExist )
    {
        for ( var loopId in this.items )
        {
            if ( !(this.items[loopId].flags & TVIS_SELECTED)
                 &&  this.items[loopId].id != this.focusedId )
            {
                continue;
            }

            this._assignItemIcon_virt(loopId);
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// _fix_IE_Paint_Bug
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._fix_IE_Paint_Bug = function()
{
    this.enable(false);
    this.enable(true);
}

////////////////////////////////////////////////////////////////////////////
// _beforeUpdateTree_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._beforeUpdateTree_virt = function()
{
}

////////////////////////////////////////////////////////////////////////////
// _afterUpdateTree_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._afterUpdateTree_virt = function()
{
    this._fix_IE_Paint_Bug();
}

////////////////////////////////////////////////////////////////////////////
// _insertSubItems_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._insertSubItems_virt = function(itemId, domItem)
{
    var item = this.items[itemId];
    for ( var colIndex = 0; colIndex < this.colCount; colIndex++ )
    {
        var className = "ttv_sitem ";
        if ( this.flags & TVS_COLUMNHEADER )
        {
            className += "ellipsis ";
        }
        className += "show-on-hover ttv_sitem_" + item.level + "_" + colIndex;
        domItem.divColumns[colIndex] = AP.createElement("div", domItem.divItem, className);
        domItem.divColumns[colIndex].noWrap = true;
        var htmText = "";
        if ( this.flags & TVS_COLUMNHEADER )
        {
            if ( AP.is_set(item.columns[colIndex].text) )
            {
                htmText = item.columns[colIndex].text
            }
        }
        else
        {
            htmText = item.text;
        }
        domItem.divColumns[colIndex].innerHTML = "<span>" + htmText + "</span>";
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// _insertItem_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._insertItem_virt = function(itemId, parentId, nextId)
{
    //DEBUGINFO - DebugLog.write("_insertItem_virt");
    if ( !this._visible(itemId) || this._serialized(itemId)
         || itemId == TV_ROOT_ITEM_ID )
    {
        return null;
    }
    
    var parent = null;
    var next = null;
    // Get Place For Insert
    // Parent
    if ( !AP.is_set(parentId) )
    {
        parentId = this.nextItem(itemId, TVI_FIND_PARENT);
    }
    if ( parentId && parentId != TV_ROOT_ITEM_ID )
    {
        var parentItemEl = AP.getElement(parentId);
        if ( !parentItemEl )
        {
            throw ("TreeViewEx.prototype._insertItem_virt: parent is not serialized");
        }
        var parentItem = parentItemEl.instance;
        parent = parentItem.divSubItems;
    }
    else
    {
        parent = this.tvContainer;
    }
    // Offset
    if ( !AP.is_set(nextId) )
    {
        nextId = this.nextItem(itemId, TVI_FIND_NEXT_SIBLING | TVI_FIND_SORTED);
    }
    if ( nextId )
    {
        next = AP.getElement(nextId);
        if ( !next )
        {
            throw ("TreeViewEx.prototype._insertItem_virt: next item is not serialized");
        }
    }
    
    // DOM
    var domItem = {
        divColumns: {}
    };
    // Item
    domItem.element = AP.createElement("div", null, "ttv_item_container");
    domItem.element.id = itemId;
    domItem.element.instance = domItem;
    domItem.divItem = AP.createElement("div", domItem.element, "ttv_item");
    domItem.divSubItems = AP.createElement("div", domItem.element, "ttv_sub_items_container");
    AP.show(domItem.divSubItems, false);
    
    var subParent = domItem.divItem;
    // Manage Icon
    var manageicon = this.flags & TVS_MANAGEICON;
    if ( manageicon )
    {
        domItem.divManage = AP.createElement("div", subParent, "ttv_subitem_manage");
    }
    // Check Box
    var checkbox = (this.flags & TVS_CHECKBOX) ? true : false;
    if ( checkbox )
    {
        var className = "ttv_subitem_checkbox";
        if ( manageicon )
        {
            className += " ttv_subitem_checkbox_manage";
        }
        domItem.divCheckBox = AP.createElement("div", subParent, className);
    }
    // Item Icon
    if ( this.flags & TVS_ITEMICON )
    {
        var className = "ttv_subitem_icon";
        if ( manageicon || checkbox )
        {
            className += " ttv_subitem_icon";
            if ( manageicon )
            {
                className += "_manage";
            }
            if ( checkbox )
            {
                className += "_check";
            }
        }
        domItem.divIcon = AP.createElement("div", subParent, className);
    }
    // Item Text
    this._insertSubItems_virt(itemId, domItem);
    
    // Insert DOM (Serialize)
    if ( next )
    {
        parent.insertBefore(domItem.element, next);
    }
    else
    {
        parent.appendChild(domItem.element);
    }
    this._serialized(itemId, true);
    
    // Draw Item
    this._assignItemIcon_virt(itemId);
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// _assignItemIcon_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._assignItemIcon_virt = function(first, last)
{
    if ( typeof last == "undefined" )
    {
        last = this.nextItem(first, TVI_FIND_NEXT);
    }
    
    //debugger;
    for ( var loopId = first; loopId != last; loopId = this.nextItem(loopId, TVI_FIND_NEXT) )
    {
        if ( !this._visible(loopId) || !this._serialized(loopId) )
        {
            this._modified_icon_state(loopId, true);
        }
        else
        {
            var item = this.items[loopId];
            var itemEl = AP.getElement(item.id);
            if ( !itemEl )
            {
                continue;
            }
            var domItem = itemEl.instance;
            
            // Item Style
            if ( this.selected(item.id) )
            {
                AP.appendClassName(domItem.divItem, "ttv_item_selected");
            }
            else
            {
                AP.removeClassName(domItem.divItem, "ttv_item_selected");
            }
            if ( this.focused(item.id) )
            {
                AP.appendClassName(domItem.divItem, "ttv_item_focused");
            }
            else
            {
                AP.removeClassName(domItem.divItem, "ttv_item_focused");
            }
            if ( !this.itemEnable(item.id) )
            {
                AP.appendClassName(domItem.divItem, "ttv_item_disabled");
            }
            else
            {
                AP.removeClassName(domItem.divItem, "ttv_item_disabled");
            }
            
            // Manage Icon
            if ( this.flags & TVS_MANAGEICON )
            {
                var src = this.imgList.base_dir;
                if ( this.flags & TVS_NO_LINES )
                {
                    if ( this.expanded(item.id) === true )
                    {
                        src += "minusroot";
                    }
                    else if ( this.expanded(item.id) === false )
                    {
                        src += "plusroot";
                    }
                    else
                    {
                        src += "spacer";
                    }
                }
                else
                {
                    if ( this.expanded(item.id) === true )
                    {
                        src += "minus";
                    }
                    else if ( this.expanded(item.id) === false )
                    {
                        src += "plus";
                    }
                    else
                    {
                        src += "join";
                    }
                    if ( item.id == TV_ROOT_ITEM_ID )
                    {   // root (first) Item
                        src += "root";
                    }
                    
                    if ( this.nextItem(item.id, TVI_FIND_NEXT_SIBLING) )
                    {
                        src += "bottom";
                        // lines
                        if ( !domItem.element.style.background.length )
                        {
                            var line_src = this.imgList.base_dir + this.imgList.img_line;
                            domItem.element.style.background = "url(" + line_src + ") repeat-y";
                        }
                    }
                    else
                    {
                        // lines
                        if ( domItem.element.style.background.length )
                        {
                            domItem.element.style.background = "";
                        }
                    }
                }
                src += this.imgList.img_ext;
                if ( domItem.divManage.style.backgroundImage.indexOf(src) == -1 )
                {
                    domItem.divManage.style.backgroundImage = "url("+src+")";
                }
            }
            
            // Check Box Icon
            if ( this.flags & TVS_CHECKBOX )
            {
                var src = this.imgList.base_dir;
                if ( this.checked(item.id) == TVIS_CHECKED )
                {
                    if ( this.itemEnable(item.id) )
                    {
                        src += this.imgList.img_check.checked;
                    }
                    else
                    {
                        src += this.imgList.img_check.checkeddisabled;
                    }
                }
                else if ( this.checked(item.id) == TVIS_INDETERMINATE )
                {
                    if ( this.itemEnable(item.id) )
                    {
                        src += this.imgList.img_check.indeterminate;
                    }
                    else
                    {
                        src += this.imgList.img_check.indeterminatedisabled;
                    }
                }
                else
                {
                    if ( this.itemEnable(item.id) )
                    {
                        src += this.imgList.img_check.base;
                    }
                    else
                    {
                        src += this.imgList.img_check.disabled;
                    }
                }
                src += this.imgList.img_ext;
                if ( domItem.divCheckBox.style.backgroundImage.indexOf(src) == -1 )
                {
                    domItem.divCheckBox.style.backgroundImage = "url("+src+")";
                }
            }
            
            // Item Icon
            if ( this.flags & TVS_ITEMICON )
            {
                if ( AP.is_set(item.img_list) )
                {
                    if ( this.focused(item.id) &&
                         AP.is_set(item.img_list.focused) )
                    {
                        var src = this.imgList.base_dir + item.img_list.focused;
                        if ( domItem.divIcon.style.backgroundImage.indexOf(src) == -1 )
                        {
                            domItem.divIcon.style.backgroundImage = "url("+src+")";
                        }
                    }
                    else if ( this.selected(item.id) &&
                              AP.is_set(item.img_list.selected) )
                    {
                        var src = this.imgList.base_dir + item.img_list.selected;
                        if ( domItem.divIcon.style.backgroundImage.indexOf(src) == -1 )
                        {
                            domItem.divIcon.style.backgroundImage = "url("+src+")";
                        }
                    }
                    else
                    {
                        if ( (this.expanded(item.id) === true) &&
                             AP.is_set(item.img_list.expanded) )
                        {
                            var src = this.imgList.base_dir + item.img_list.expanded;
                            if ( domItem.divIcon.style.backgroundImage.indexOf(src) == -1 )
                            {
                                domItem.divIcon.style.backgroundImage = "url("+src+")";
                            }
                        }
                        else if ( (this.expanded(item.id) === false) &&
                                   AP.is_set(item.img_list.collapsed)  )
                        {
                            var src = this.imgList.base_dir + item.img_list.collapsed;
                            if ( domItem.divIcon.style.backgroundImage.indexOf(src) == -1 )
                            {
                                domItem.divIcon.style.backgroundImage = "url("+src+")";
                            }
                        }
                        else
                        {
                            var src = this.imgList.base_dir + item.img_list.base;
                            if ( domItem.divIcon.style.backgroundImage.indexOf(src) == -1 )
                            {
                                domItem.divIcon.style.backgroundImage = "url("+src+")";
                            }
                        }
                    }
            
                    AP.show(domItem.divIcon, true);
                }
                else
                {
                    AP.show(domItem.divIcon, false);
                }
            
                this._modified_icon_state(item.id, false);
            }
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// _deleteItem_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._deleteItem_virt = function(itemId)
{
    if ( this._edtId == itemId )
    {
        this.endItemTextEditing(this._edtId, false);
    }
    var itemEl = AP.getElement(itemId);
    if ( itemEl )
    {
        var parent = itemEl.parentNode;
        parent.removeChild(itemEl);
    }
}

////////////////////////////////////////////////////////////////////////////
// _chageItemText_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._chageItemText_virt = function(itemId)
{
    if ( !this._visible(itemId) || !this._serialized(itemId) )
    {
        this._modified_text(itemId, true);
    }
    else
    {
        var item = this.items[itemId];
        var itemEl = AP.getElement(itemId);
        if ( itemEl )
        {
            var domItem = itemEl.instance;
            for ( var colIndex = 0; colIndex < this.colCount; colIndex++ )
            {
                var htmText = "";
                if ( this.flags & TVS_COLUMNHEADER )
                {
                    if ( AP.is_set(item.columns[colIndex].text) )
                    {
                        htmText = item.columns[colIndex].text
                    }
                }
                else
                {
                    htmText = item.text;
                }
                domItem.divColumns[colIndex].innerHTML = "<span>" + htmText + "</span>";
            }

            this._modified_text(itemId, false);
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// _showItem_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._showItem_virt = function(itemId, showItem)
{
    AP.show(itemId, showItem);
}

////////////////////////////////////////////////////////////////////////////
// _focusItem_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._focusItem_virt = function(itemId, focusItem)
{
    this._assignItemIcon_virt(itemId);
}

////////////////////////////////////////////////////////////////////////////
// _selectItem_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._selectItem_virt = function(itemId, selectItem)
{
    this._assignItemIcon_virt(itemId);
}

////////////////////////////////////////////////////////////////////////////
// scrollItemIntoView
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.scrollItemIntoView = function(itemId)
{
    // DEBUGINFO - var tt0 = AP.getTickCount();
    if ( this._visible(itemId) && this._serialized(itemId) )
    {
        var itemEl = AP.getElement(itemId);
        if ( itemEl )
        {
            var domItem = itemEl.instance;
            
            var posTv = AP.getAbsolutePos(this.tvContainer);
            var tvHeight = AP.clientHeight(this.tvContainer);
            
            var posItem = AP.getAbsolutePos(domItem.divItem);
            var itemTop = posItem.y - posTv.y;
            var itemBottom = itemTop + AP.offsetHeight(domItem.divItem);
            
            var deltaTop = itemTop - this.tvContainer.scrollTop;
            var deltaBottom = itemBottom - this.tvContainer.scrollTop - tvHeight;
// DEBUGINFO
//            DebugLog.write("posItem.y", posItem.y,
//                           "tvHeight", tvHeight,
//                           "itemTop", itemTop,
//                           "this.tvContainer.scrollTop", this.tvContainer.scrollTop,
//                           "deltaTop", deltaTop,
//                           "deltaBottom", deltaBottom);
            if ( deltaTop < 0 )
            {
                this.tvContainer.scrollTop += deltaTop;
            }
            else if ( deltaBottom > 0 )
            {
                this.tvContainer.scrollTop += deltaBottom;
            }
        }
    }
    // DEBUGINFO - var tt1 = AP.getTickCount();
    // DEBUGINFO - DebugLog.write("scrollItemIntoView", tt1 - tt0);
}

////////////////////////////////////////////////////////////////////////////
// startItemTextEditing
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.startItemTextEditing = function(itemId, flags)
{
    // prepare for editing
    flags = AP.is_set(flags, this.flags & TVS_EDITING_MODE);
    
    this._dovisible(itemId);
    
    this.scrollItemIntoView(itemId);
    
    var itemEl = AP.getElement(itemId);
    var domItem = itemEl.instance;
    
    // editing mode
// KKKK
//    this._edtId = itemId;
//    var width = AP.offsetWidth(domItem.divText);
//    width += 10;
//    width = Math.max(50, width);
//    //DebugLog.write("width", width);
//    //domItem.divText.innerHTML = "";
//    AP.visible(domItem.divText, false);
//    
//    var text = this.items[itemIndex].text;
//    var className = "ttv_subitem_text_edit";
//    if ( this.flags & TVS_CHECKBOX )
//    {
//        className += "_check";
//    }
//    domItem.divInput = AP.createElement("div", domItem.divItem, className);
//    domItem.inValue = AP.createNamedElement({
//        tagName: "input",
//        name: "",
//        parent: domItem.divInput,
//        type: "text",
//        className: "ttv_subitem_text_input"
//    });
//    AP.width(domItem.inValue, width);
//    domItem.inValue.value = text;
//    domItem.inValue.select();
}

////////////////////////////////////////////////////////////////////////////
// endItemTextEditing
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype.endItemTextEditing = function(itemId, apply, text)
{
    if ( !this._edtId )
    {
        return false;
    }
    
    var itemEl = AP.getElement(this._edtId);
    if ( !itemEl )
    {
        DebugLog.write("TreeViewEx.endItemTextEditing [WARNING] _edtId not null, but itemEl is null");
        this._edtId = null;
        return false;
    }
    
    var domItem = itemEl.instance;
    text = AP.is_set(text, domItem.inValue.value);
    
    // Remove DOM
    domItem.divInput.removeChild(domItem.inValue);
    domItem.inValue = null;
    domItem.divItem.removeChild(domItem.divInput);
    domItem.divInput = null;
    
    //domItem.divText.innerHTML = this.items[itemIndex].text;
// KKKK
//    AP.visible(domItem.divText, true);
//    if ( apply )
//    {
//        this.itemText(itemIndex, text);
//    }
    
    // Reset _edtId
    this._edtId = null;
    
    // return focus to TreeView element
    setTimeout("var el = AP.getElement('" + this.element.id + "'); if (el) { el.focus(); }", 50);
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// _expandItem_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._expandItem_virt = function(itemId, expand)
{
    var itemElement = AP.getElement(itemId);
    if ( itemElement )
    {
        var domItem = itemElement.instance;
        if ( domItem )
        {
            if ( !(this.flags & TVS_NO_LINES) )
            {
                AP.show(this.tvContainer, false);
            }
            AP.show(domItem.divSubItems, expand);
            if ( !(this.flags & TVS_NO_LINES) )
            {
                AP.show(this.tvContainer, true);
            }
        }
    }
    
    this._assignItemIcon_virt(itemId);
    
    this._fix_IE_Paint_Bug();
}

////////////////////////////////////////////////////////////////////////////
// _serialize_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._serialize_virt = function(first, last)
{
    var first = AP.is_set(first, TV_ROOT_ITEM_ID);
    if ( typeof last == "undefined" )
    {
        last = this.nextItem(last, TVI_FIND_NEXT_SIBLING | TVI_FIND_SORTED);
    }
    
    var insertedAsBefore = {};
    for ( var loopId = first; loopId && loopId != last; )
    {
        if ( !AP.is_set(insertedAsBefore[loopId]) && this._visible(loopId) )
        {
            if ( this._serialized(loopId) )
            {
                // check for modifications (update icon, text, etc.)
                this._update_modified(loopId);
            }
            else
            {
                var beforeItemsList = new Array();
                var beforeId = this.nextItem(loopId, TVI_FIND_NEXT_SIBLING | TVI_FIND_SORTED);
                while ( beforeId && !this._serialized(beforeId) )
                {
                    beforeItemsList.push(beforeId);
                    beforeId = this.nextItem(beforeId, TVI_FIND_NEXT_SIBLING | TVI_FIND_SORTED);
                }
                for ( var beforeLoop = beforeItemsList.length - 1; beforeLoop >= 0; beforeLoop-- )
                {
                    insertedAsBefore[beforeItemsList[beforeLoop]] = true;
                    this._insertItem_virt(beforeItemsList[beforeLoop]);
                }
                this._insertItem_virt(loopId);
            }
        }
        
        if ( this.expanded(loopId) === true )
        {
            loopId = this.nextItem(loopId, TVI_FIND_NEXT | TVI_FIND_SORTED);
        }
        else
        {
            var nextId = this.nextItem(loopId, TVI_FIND_NEXT_SIBLING | TVI_FIND_SORTED);
            if ( !nextId )
            {
                loopId = this.nextItem(loopId, TVI_FIND_NEXT_PARENT);
            }
            else
            {
                loopId = nextId;
            }
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// _onDragOver
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._onDragOver = function(ev, notifyCode, itemId)
{
    if ( this._overstate.drag_over.item != itemId )
    {
        this.smartSelect(itemId);
        this._overstate.drag_over.item = itemId;
        if ( this._overstate.drag_over.timer )
        {
            clearTimeout(this._overstate.drag_over.timer);
            this._overstate.drag_over.timer = null;
        }
        if ( this._overstate.drag_over.item != TV_ROOT_ITEM_ID
             && this.expanded(itemId) === false )
        {
            var sFunc = "var pThis = AP.getElement(\"" + this.element.id + "\");";
            sFunc += "if ( pThis ) {pThis = pThis.instance;};";
            sFunc += "pThis._overstate.drag_over.timer=null;";
            sFunc += "pThis.expand(\"" + itemId + "\");";
            this._overstate.drag_over.timer = setTimeout(sFunc, 1000);
        }
    }
    
    return this.fireNotify(notifyCode, {event: ev, item: itemId});
}

////////////////////////////////////////////////////////////////////////////
// _onClick_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._onClick_virt = function(item, obj, type, ctrlState, shiftState)
{
    if ( this._edtId ) // in edit item text mode
    {
        var itemEl = AP.getElement(this._edtId);
        if ( !itemEl || !item || itemEl.id != item.id )
        {
            var domItem = (itemEl ? itemEl.instance : null);
            var text = (domItem ? domItem.inValue.value : "");
            if ( this.fireNotify(TVN_ITEM_EDIT_ESCAPE, {item: this._edtId, text: text}) == -1 )
            {
                return false;
            }
            this.endItemTextEditing(this._edtId, false);
            return false;
        }
    }
    else if ( obj && item )
    {
        var notifyCode = 0;
        if ( AP.isSetClassName(obj, "ttv_sitem") )
        {
            notifyCode = (type == "click" ? TVN_ITEMTEXT_CLICKED : TVN_ITEMTEXT_DBLCLICKED);
        }
        else if ( obj.className.indexOf("_icon") != -1 )
        {
            notifyCode = (type == "click" ? TVN_ITEMICON_CLICKED : TVN_ITEMICON_DBLCLICKED);
        }
        else if ( obj.className.indexOf("_checkbox") != -1 )
        {
            notifyCode = (type == "click" ? TVN_ITEMCHECKBOX_CLICKED : TVN_ITEMCHECKBOX_DBLCLICKED);
        }
        else if ( obj.className.indexOf("_manage") != -1 )
        {
            notifyCode = (type == "click" ? TVN_ITEMMANAGEICON_CLICKED : TVN_ITEMMANAGEICON_DBLCLICKED);
        }
        else if ( AP.isSetClassName(obj, "ttv_item") )
        {
            notifyCode = (type == "click" ? TVN_ITEM_CLICKED : TVN_ITEM_DBLCLICKED);
        }
        if ( !notifyCode )
        {
            return false;
        }
        
        var param = {
            item: item.id,
            ctrl: ctrlState,
            shift: shiftState
        };
        if ( this.fireNotify(notifyCode, param) == -1 )
        {
            return true;
        }
        
        if ( notifyCode == TVN_ITEMMANAGEICON_CLICKED )
        {
            this.switchExpandState(item.id);
        }
        else if ( notifyCode == TVN_ITEMCHECKBOX_CLICKED )
        {
            if ( this.itemEnable(item.id) )
            {
                this.switchCheckState(item.id);
            }
        }
        else if ( notifyCode == TVN_ITEMICON_CLICKED
                  || notifyCode == TVN_ITEMTEXT_CLICKED )
        {
            var selListChanged = false;
            var oldFocusedId = this.focused();
            this.focus(item.id);
            //////////////////////////////
            // HACK
            // I don't know why, but async call works faster
            //this.scrollItemIntoView(item.id);
            var func = "var obj = AP.getElement(\"" + this.element.id + "\");";
            func += "if ( obj && obj.instance ) {";
            func += "obj.instance.scrollItemIntoView(\"" + item.id + "\");";
            func += "};";
            setTimeout(func, 1);
            // HACK
            //////////////////////////////
            if ( this.itemEnable(item.id) )
            {
                if ( ctrlState )
                {
                    if ( this.selected(item.id) )
                    {
                        selListChanged = this.select(item.id, false, false);
                    }
                    else
                    {
                        selListChanged = this.select(item.id, true, false);
                    }
                }
                else if ( shiftState && this.maxLevel == 0 )
                {
                    var select = this.selected(oldFocusedId);
                    var direction = TVI_FIND_NEXT;
                    // HACK - I do not know: how to detect direction using another method
                    var ofItem = AP.getElement(oldFocusedId);
                    if ( ofItem )
                    {
                        var ofDomItem = ofItem.instance;
                        var ofPosItem = AP.getAbsolutePos(ofDomItem.divItem);
                        var ofY = ofPosItem.y;
                        var cfItem = AP.getElement(item.id);
                        if ( cfItem )
                        {
                            var cfDomItem = cfItem.instance;
                            var cfPosItem = AP.getAbsolutePos(cfDomItem.divItem);
                            var cfY = cfPosItem.y;
                            if ( cfY < ofY )
                            {
                                direction = TVI_FIND_PREV;
                            }
                        }
                    }
                    // -- HACK
                    for ( var loopId = oldFocusedId; loopId; loopId = this.nextItem(loopId, direction | TVI_FIND_SKIPDISABLED | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED) )
                    {
                        this.select(loopId, select, false);
                        if ( loopId == item.id )
                        {
                            break;
                        }
                    }
                }
                else
                {
                    selListChanged = this.select(item.id, true, true);
                    
                    if ( notifyCode == TVN_ITEMICON_CLICKED )
                    {
                        this.switchExpandState(item.id);
                    }
                }
            }
            if ( selListChanged )
            {
                this.fireNotify(TVN_ITEM_SELLISTCHANGED, {
                    reason: notifyCode
                });
            }
        }
    }

    return false;
}

////////////////////////////////////////////////////////////////////////////
// _onKeyPress_virt
////////////////////////////////////////////////////////////////////////////
TreeViewEx.prototype._onKeyPress_virt = function(keyCode, ctrlState, shiftState)
{
    var param = {
        item: this.focusedId,
        key: keyCode,
        ctrl: ctrlState,
        shift: shiftState
    };
    if ( this.fireNotify(TVN_ITEM_KEYPRESS, param) == -1 )
    {
        return true;
    }
    
    if ( this._edtId )
    {
        var itemEl = AP.getElement(this._edtId);
        if ( !itemEl )
        {
            DebugLog.write("TreeViewEx._onKeyPress_virt [WARNING] _edtId not null, but itemEl is null");
            this._edtId = null;
            return this._onKeyPress_virt(keyCode, ctrlState, shiftState);
        }
        
        var domItem = itemEl.instance;
        var text = domItem.inValue.value;
        if ( keyCode == KEY_CODE_ENTER )
        {
            if ( this.fireNotify(TVN_ITEM_EDIT_CONFIRM, {item: this._edtId, text: text}) == -1 )
            {
                return true;
            }
            this.endItemTextEditing(this._edtId, true, text);
            return true;
        }
        
        if ( keyCode == KEY_CODE_ESCAPE )
        {
            if ( this.fireNotify(TVN_ITEM_EDIT_ESCAPE, {item: this._edtId, text: text}) == -1 )
            {
                return true;
            }
            this.endItemTextEditing(this._edtId, false);
            return true;
        }
        
        return false; // using default input handling
    }
    
    // DEBUGINFO - DebugLog.write("KeyCode", keyCode);
    // Enter
    if ( keyCode == KEY_CODE_ENTER )
    {
        if ( this.focusedId != TV_ROOT_ITEM_ID )
        {
            this.select(this.focusedId);
        }
    }
    // SpaceBar
    else if ( keyCode == KEY_CODE_SPACE )
    {
        if ( this.focusedId != TV_ROOT_ITEM_ID )
        {
            if ( this.flags & TVS_CHECKBOX )
            {
                if ( this.flags & TVS_MULTISELECT )
                {
                    this.blockNotifyCodes(TVN_ITEM_CHECKED, TVN_ITEM_CHECKLISTCHANGED);
                    var selected = this.selected();
                    this.switchCheckState(this.focusedId);
                    var checkState = this.checked(this.focusedId);
                    for ( var loop = 0; loop < selected.length; loop++ )
                    {
                        if ( selected[loop] == this.focusedId )
                        {
                            continue;
                        }
                        this.check(selected[loop], checkState);
                    }
                    this.unBlockNotifyCodes(TVN_ITEM_CHECKED, TVN_ITEM_CHECKLISTCHANGED);
                    this.fireNotify(TVN_ITEM_CHECKLISTCHANGED, {
                        reason: TVN_ITEM_KEYPRESS
                    });
                }
                else
                {
                    this.switchCheckState(this.focusedId);
                }
            }
            else if ( this.flags & TVS_MULTISELECT )
            {
                this.select(this.focusedId, !this.selected(this.focusedId), false);
            }
        }
    }
    // Left
    else if ( keyCode == KEY_CODE_LEFT_ARROW )
    {
        if ( this.focusedId == TV_ROOT_ITEM_ID )
        {
            return true;
        }
        
        // Collapse
        var firstChild = this.nextItem(this.focusedId, TVI_FIND_FIRST_CHILD);
        if ( firstChild && this.expanded(this.focusedId) )
        {
            if ( this.fireNotify(TVN_ITEM_DO_COLLAPSE, this.focusedId) == -1 )
            {
                return true;
            }
            this.collapse(this.focusedId);
            return true;
        }
        // Go to Parent
        var parentId = this.nextItem(this.focusedId, TVI_FIND_PARENT | TVI_FIND_SKIPDISABLED | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED);
        if ( parentId && parentId != TV_ROOT_ITEM_ID )
        {
            if ( this.fireNotify(TVN_ITEM_GO_PARENT, parentId) == -1 )
            {
                return true;
            }
            this.focus(parentId);
            if ( !ctrlState )
            {
                this.select(parentId);
            }
            this.scrollItemIntoView(parentId);
            return true;
        }
        // Go to Prev Sibling
        var prevSiblingId = this.nextItem(this.focusedId, TVI_FIND_PREV_SIBLING | TVI_FIND_SKIPDISABLED | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED);
        if ( prevSiblingId && prevSiblingId != TV_ROOT_ITEM_ID )
        {
            if ( this.fireNotify(TVN_ITEM_GO_PREV, prevSiblingId) == -1 )
            {
                return true;
            }
            this.focus(prevSiblingId);
            if ( !ctrlState )
            {
                this.select(prevSiblingId);
            }
            this.scrollItemIntoView(prevSiblingId);
            return true;
        }
    }
    // Up
    else if ( keyCode == KEY_CODE_UP_ARROW )
    {
        if ( this.focusedId == TV_ROOT_ITEM_ID )
        {
            return true;
        }

        // Go to prev visible
        for ( var prevId = this.nextItem(this.focusedId, TVI_FIND_PREV | TVI_FIND_SKIPDISABLED | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED);
              prevId && prevId != TV_ROOT_ITEM_ID;
              prevId = this.nextItem(prevId, TVI_FIND_PREV | TVI_FIND_SKIPDISABLED | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED) )
        {
            if ( this._visible(prevId) && this._serialized(prevId) )
            {
                break;
            }
        }
        if ( prevId && prevId != TV_ROOT_ITEM_ID )
        {
            if ( this.fireNotify(TVN_ITEM_GO_NEXT, prevId) == -1 )
            {
                return true;
            }
            this.focus(prevId);
            if ( !ctrlState )
            {
                this.select(prevId);
            }
            this.scrollItemIntoView(prevId);
            return true;
        }
    }
    // Right
    else if ( keyCode == KEY_CODE_RIGHT_ARROW )
    {
        if ( this.focusedId == TV_ROOT_ITEM_ID )
        {
            return true;
        }
        
        var firstChildId = this.nextItem(this.focusedId, TVI_FIND_FIRST_CHILD);
        if ( firstChildId && !this.expanded(this.focusedId) )
        {
            // Expand
            if ( this.fireNotify(TVN_ITEM_DO_EXPAND, this.focusedId) == -1 )
            {
                return true;
            }
            this.expand(this.focusedId);
            return true;
        }
        var firstChildId = this.nextItem(this.focusedId, TVI_FIND_FIRST_CHILD | TVI_FIND_SKIPDISABLED | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED);
        if ( firstChildId )
        {
            // Go to first Child
            if ( this.fireNotify(TVN_ITEM_GO_NEXT, firstChildId) == -1 )
            {
                return true;
            }
            this.focus(firstChildId);
            if ( !ctrlState )
            {
                this.select(firstChildId);
            }
            this.scrollItemIntoView(firstChildId);
            return true;
        }
        // Go to Next Sibling
        var nextSiblingId = this.nextItem(this.focusedId, TVI_FIND_NEXT_SIBLING | TVI_FIND_SKIPDISABLED | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED);
        if ( nextSiblingId )
        {
            if ( this.fireNotify(TVN_ITEM_GO_NEXT, nextSiblingId) == -1 )
            {
                return true;
            }
            this.focus(nextSiblingId);
            if ( !ctrlState )
            {
                this.select(nextSiblingId);
            }
            this.scrollItemIntoView(nextSiblingId);
            return true;
        }
    }
    // Down
    else if ( keyCode == KEY_CODE_DOWN_ARROW )
    {
        // Go to next visible
        for ( var nextId = this.nextItem(this.focusedId, TVI_FIND_NEXT | TVI_FIND_SKIPDISABLED | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED);
              nextId;
              nextId = this.nextItem(nextId, TVI_FIND_NEXT | TVI_FIND_SKIPDISABLED | TVI_FIND_SKIPINVISIBLE | TVI_FIND_SORTED) )
        {
            if ( this._visible(nextId) && this._serialized(nextId) )
            {
                break;
            }
        }
        if ( nextId )
        {
            if ( this.fireNotify(TVN_ITEM_GO_NEXT, nextId) == -1 )
            {
                return true;
            }
            this.focus(nextId);
            if ( !ctrlState )
            {
                this.select(nextId);
            }
            this.scrollItemIntoView(nextId);
            return true;
        }
    }
    // Insert && Text Modify
    else if ( keyCode == KEY_CODE_INSERT )
    {
        if ( !(this.flags & TVS_EDITABLE) )
        {
            return true;
        }
        
        if ( this.fireNotify(TVN_ITEM_DO_INSERT, this.focusedId) == -1 )
        {
            return true;
        }
        
        var itemId = this.insertItem({
            parent: this.focusedId,
            text: "new Item",
            flags: TVI_INSERT_FIRST_CHILD | TVI_INSERT_EXPANDED
        });
        
        if ( !itemId )
        {
            return true;
        }
        
        this.focus(itemId);
        
        this.startItemTextEditing(itemId);
    }
    // Modify
    else if ( keyCode == KEY_CODE_F2 )
    {
        if ( !(this.flags & TVS_EDITABLE) )
        {
            return true;
        }
        
        if ( this.focusedId == TV_ROOT_ITEM_ID )
        {
            return true;
        }
        
        if ( this.fireNotify(TVN_ITEM_DO_TEXTMODIFY, this.focusedId) == -1 )
        {
            return true;
        }

        this.startItemTextEditing(this.focusedId);
    }
    // Delete
    else if ( keyCode == KEY_CODE_DELETE )
    {
        if ( !(this.flags & TVS_EDITABLE) )
        {
            return true;
        }
        
        var selItems = this.selected();
        if ( !selItems.length )
        {
            return true;
        }
        
        if ( this.fireNotify(TVN_ITEM_DO_DELETE, selItems) == -1 )
        {
            return true;
        }
        
        if ( !(this.flags & TVS_CONFIRM_ON_DELETE) || confirm("Are you sure you want to delete?") )
        {
            for ( var loop = 0; loop < selItems.length; loop++ )
            {
                this.deleteItem(selItems[loop]);
            }
        }
    }
    // Quick Search Form - "F"
    else if ( this.flags & (TVS_QUICKSEARCH | TVS_QUICKFILTER) && keyCode == 70 )
    {
        this.qsShow(true, true);
        return true;
    }

    return false;
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
TreeViewEx.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && TreeViewEx.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// getItemContainerElement
////////////////////////////////////////////////////////////////////////////
TreeViewEx.getItemContainerElement = function(obj)
{
    for ( var o = obj;
          o && (o.className.indexOf("ttv_item_container") == -1);
          o = o.parentNode )
    {
        if ( o.instance && TreeViewEx.prototype.isPrototypeOf(o.instance) )
        {
            return null;
        }
    }
    return o;
}

////////////////////////////////////////////////////////////////////////////
// getItemElement
////////////////////////////////////////////////////////////////////////////
TreeViewEx.getItemElement = function(obj)
{
    for ( var o = obj;
          o && !AP.isSetClassName(o, "ttv_item");
          o = o.parentNode )
    {
        if ( AP.isSetClassName(o, "ttv_item_container") )
        {
            return null;
        }
        if ( o.instance && TreeViewEx.prototype.isPrototypeOf(o.instance) )
        {
            return null;
        }
    }
    return o;
}

////////////////////////////////////////////////////////////////////////////
// getSubItemElement
////////////////////////////////////////////////////////////////////////////
TreeViewEx.getSubItemElement = function(obj)
{
    for ( var o = obj; o; o = o.parentNode )
    {
        if ( AP.isSetClassName(o, "ttv_sitem")
             || o.className.indexOf("ttv_subitem_") != -1 )
        {
            break;
        }
        if ( AP.isSetClassName(o, "ttv_item") )
        {
            return null;
        }
        if ( o.instance && TreeViewEx.prototype.isPrototypeOf(o.instance) )
        {
            return null;
        }
    }
    return o;
}

////////////////////////////////////////////////////////////////////////////
// onColumnHeaderNotify
////////////////////////////////////////////////////////////////////////////
TreeViewEx.onColumnHeaderNotify = function(param)
{
    var pThis = param.subscriber;
    var hdr = param.producer;
    
    if ( param.code == HDRN_ITEM_SIZED )
    {
        pThis._setSubItemPosition();
    }
    else if ( param.code == HDRN_ITEM_GETSPLTHEIGHT )
    {
        return AP.clientHeight(pThis.element);
    }
    else if ( param.code == HDRN_ITEM_CLICKED )
    {
        var itemId = param.param.item;
        var sortState = hdr.itemState(itemId);
        if ( sortState & HDRIS_SORTEDBY_ASC )
        {
            sortState = HDRIS_SORTEDBY_DESC;
        }
        else
        {
            sortState = HDRIS_SORTEDBY_ASC;
        }
        pThis.sort({
            columnId: itemId,
            flags: (sortState == HDRIS_SORTEDBY_ASC) ? TV_SORT_FORWARD : TV_SORT_BACKWARD
        });
        hdr.sort(itemId, sortState);
    }
    
    return 0;
}

////////////////////////////////////////////////////////////////////////////
// onQuickSearchClick
////////////////////////////////////////////////////////////////////////////
TreeViewEx.onQuickSearchClick = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var pThis = TreeViewEx.getInstance(obj);
    if ( !pThis )
    {
        return true;
    }
    
    if ( AP.isSetClassName(obj, "ttv_sc_close") )
    {
        pThis.qsShow(false, false);
    }
    else if ( AP.isSetClassName(obj, "ttv_sc_mode") )
    {
        if ( pThis.flags & TVS_QUICKSEARCH
             && pThis.flags & TVS_QUICKFILTER )
        {
            pThis.qsSwitchMode();
        }
        else if ( pThis.flags & TVS_QUICKSEARCH_ON )
        {
            pThis.qsSearch();
        }
        else if ( pThis.flags & TVS_QUICKFILTER_ON )
        {
            pThis.qsFilter();
        }
    }
        
    return true;
}

////////////////////////////////////////////////////////////////////////////
// onQuickSearchKeyPress
////////////////////////////////////////////////////////////////////////////
TreeViewEx.onQuickSearchKeyPress = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var keyCode = ev.charCode || ev.keyCode;
    
    if ( !AP.isSetClassName(obj, "ttv_search_input") )
    {
        return true;
    }
    
    var pThis = TreeViewEx.getInstance(obj);
    if ( !pThis )
    {
        return true;
    }
    
    if ( keyCode == KEY_CODE_ENTER )
    {
        if ( pThis.flags & TVS_QUICKSEARCH_ON )
        {
            pThis.qsSearch();
        }
        else if ( pThis.flags & TVS_QUICKFILTER_ON )
        {
            pThis.qsFilter();
        }
        return AP.stopEvent(ev);
    }
    else if ( keyCode == KEY_CODE_ESCAPE )
    {
        pThis.qsShow(false, true);
        return AP.stopEvent(ev);
    }
    else
    {
        pThis.quickSearch.lastId = TV_ROOT_ITEM_ID;
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// onScroll
////////////////////////////////////////////////////////////////////////////
TreeViewEx.onScroll = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.currentTarget);
    var pThis = TreeViewEx.getInstance(obj);
    if ( !pThis || !(pThis.flags & TVS_COLUMNHEADER) )
    {
        return true;
    }
    
    pThis._hdr.setPosition({
        x: -pThis.tvContainer.scrollLeft,
        cx: pThis.tvContainer.scrollWidth
    });
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// onMouseAction
////////////////////////////////////////////////////////////////////////////
TreeViewEx.onMouseAction = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var sub_item = TreeViewEx.getSubItemElement(obj);
    var divItem = TreeViewEx.getItemElement(sub_item ? sub_item : obj);
    var item = TreeViewEx.getItemContainerElement(divItem ? divItem : obj);
    var pThis = TreeViewEx.getInstance(item ? item : obj);
    if ( !pThis || pThis._state & APS_DISABLED )
    {
        return true;
    }
    
    if ( ev.type == "click"
         || ev.type == "dblclick" )
    {
        // DEBUGINFO - var tt0 = AP.getTickCount();
        if ( pThis._onClick_virt(item, sub_item ? sub_item : divItem, ev.type, ev.ctrlKey, ev.shiftKey) )
        {
            return AP.stopEvent(ev);
        }
        // DEBUGINFO - var tt1 = AP.getTickCount();
        // DEBUGINFO - DebugLog.write(tt1 - tt0);
    }
    else if ( ev.type == "mouseover" )
    {
        if ( pThis._overstate.over_control )
        {
            if ( pThis._overstate.over_timer )
            {
                clearTimeout(pThis._overstate.over_timer);
                pThis._overstate.over_timer = null;
            }
        }
        else
        {
            // DEBUGINFO - DebugLog.write("over");
            pThis._overstate.over_control = true;
        }
        
        var old_item = pThis._overstate.over_item ? AP.getElement(pThis._overstate.over_item) : null;
        if ( divItem
             && item
             && item.instance )
        {
            if ( old_item != item )
            {
                if ( old_item && old_item.instance )
                {
                    // DEBUGINFO - DebugLog.write("out_item", old_item.id);
                    old_item.instance.flags &= ~TVIS_OVER;
                    AP.removeClassName(old_item.instance.divItem, "ttv_item_over");
                }
            
                // DEBUGINFO - DebugLog.write("over_item", item.id);
                pThis._overstate.over_item = item.id;
                item.instance.flags |= TVIS_OVER;
                AP.appendClassName(item.instance.divItem, "ttv_item_over");
            }
        }
        else
        {
            if ( old_item && old_item.instance )
            {
                // DEBUGINFO - DebugLog.write("out_item", old_item.id);
                old_item.instance.flags &= ~TVIS_OVER;
                AP.removeClassName(old_item.instance.divItem, "ttv_item_over");
            }
            pThis._overstate.over_item = null;
        }
    }
    else if ( ev.type == "mouseout" )
    {
        if ( pThis._overstate.over_control
             && !pThis._overstate.over_timer )
        {
            var func = "var el = AP.getElement(\"" + pThis.element.id + "\");";
            func += "var pThis = el ? el.instance : null;";
            func += "if ( pThis ) {";
            func += "   pThis._overstate.over_timer = null;";
            func += "   pThis._overstate.over_control = false;";
            func += "   var old_item = pThis._overstate.over_item ? AP.getElement(pThis._overstate.over_item) : null;";
            func += "   if ( old_item && old_item.instance ) {";
            // DEBUGINFO - func += "       DebugLog.write(\"out_item\", old_item.id);";
            func += "       old_item.instance.flags &= ~TVIS_OVER;";
            func += "       AP.removeClassName(old_item.instance.divItem, \"ttv_item_over\");";
            func += "   }";
            func += "   pThis._overstate.over_item = null;";
            // DEBUGINFO - func += "   DebugLog.write(\"out_timer\");";
            func += "};";
            pThis._overstate.over_timer = setTimeout(func, 50);
        }
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// onKeyPress
////////////////////////////////////////////////////////////////////////////
TreeViewEx.onKeyPress = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var keyCode = ev.charCode || ev.keyCode;
    
    var tv = TreeViewEx.getInstance(obj);
    if ( !tv )
    {
        return true;
    }
    
    if ( !(tv._state & APS_DISABLED) )
    {
        if ( tv._onKeyPress_virt(keyCode, ev.ctrlKey, ev.shiftKey) )
        {
            return AP.stopEvent(ev);
        }
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// onCommonDOMEvent
////////////////////////////////////////////////////////////////////////////
TreeViewEx.onCommonDOMEvent = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var sub_item = TreeViewEx.getSubItemElement(obj);
    var divItem = TreeViewEx.getItemElement(sub_item ? sub_item : obj);
    var item = TreeViewEx.getItemContainerElement(divItem ? divItem : obj);
    var pThis = TreeViewEx.getInstance(item ? item : obj);
    if ( !pThis || pThis._state & APS_DISABLED )
    {
        return true;
    }
    
    if ( pThis._edtId ) // in edit item text mode
    {
        var itemEl = AP.getElement(pThis._edtId);
        if ( !itemEl || !item || itemEl.id != item.id )
        {
            var domItem = (itemEl ? itemEl.instance : null);
            var text = (domItem ? domItem.inValue.value : "");
            if ( pThis.fireNotify(TVN_ITEM_EDIT_ESCAPE, {item: pThis._edtId, text: text}) == -1 )
            {
                return true;
            }
            pThis.endItemTextEditing(pThis._edtId, false);
            return true;
        }
    }
    else
    {
        var notifyCode = 0;
        if ( !item )
        {
            if ( ev.type == "dragover" )
            {
                notifyCode = TVN_DRAGOVER;
            }
            else if ( ev.type == "drop" )
            {
                notifyCode = TVN_DROP;
            }
            else if ( ev.type == "contextmenu" )
            {
                notifyCode = TVN_ONCONTEXTMENU;
            }
        }
        else
        {
            var tvObj = (sub_item ? sub_item : divItem);
            if ( AP.isSetClassName(tvObj, "ttv_sitem") )
            {
                if ( ev.type == "dragover" )
                {
                    notifyCode = TVN_ITEMTEXT_DRAGOVER;
                }
                else if ( ev.type == "drop" )
                {
                    notifyCode = TVN_ITEMTEXT_DROP;
                }
                else if ( ev.type == "contextmenu" )
                {
                    notifyCode = TVN_ITEMTEXT_ONCONTEXTMENU;
                }
            }
            else if ( tvObj.className.indexOf("_icon") != -1 )
            {
                if ( ev.type == "dragover" )
                {
                    notifyCode = TVN_ITEMICON_DRAGOVER;
                }
                else if ( ev.type == "drop" )
                {
                    notifyCode = TVN_ITEMICON_DROP;
                }
                else if ( ev.type == "contextmenu" )
                {
                    notifyCode = TVN_ITEMICON_ONCONTEXTMENU;
                }
            }
            else if ( tvObj.className.indexOf("_checkbox") != -1 )
            {
                if ( ev.type == "dragover" )
                {
                    notifyCode = TVN_ITEMCHECKBOX_DRAGOVER;
                }
                else if ( ev.type == "drop" )
                {
                    notifyCode = TVN_ITEMCHECKBOX_DROP;
                }
                else if ( ev.type == "contextmenu" )
                {
                    notifyCode = TVN_ITEMCHECKBOX_ONCONTEXTMENU;
                }
            }
            else if ( tvObj.className.indexOf("_manage") != -1 )
            {
                if ( ev.type == "dragover" )
                {
                    notifyCode = TVN_ITEMMANAGEICON_DRAGOVER;
                }
                else if ( ev.type == "drop" )
                {
                    notifyCode = TVN_ITEMMANAGEICON_DROP;
                }
                else if ( ev.type == "contextmenu" )
                {
                    notifyCode = TVN_ITEMMANAGEICON_ONCONTEXTMENU;
                }
            }
            else if ( AP.isSetClassName(tvObj, "ttv_item") )
            {
                if ( ev.type == "dragover" )
                {
                    notifyCode = TVN_ITEM_DRAGOVER;
                }
                else if ( ev.type == "drop" )
                {
                    notifyCode = TVN_ITEM_DROP;
                }
                else if ( ev.type == "contextmenu" )
                {
                    notifyCode = TVN_ITEM_ONCONTEXTMENU;
                }
            }
        }
        if ( notifyCode )
        {
            var itemId = item ? item.id : TV_ROOT_ITEM_ID;
            if ( ev.type == "dragover" )
            {
                return pThis._onDragOver(ev, notifyCode, itemId);
            }
            else
            {
                return pThis.fireNotify(notifyCode, {event: ev, item: itemId});
            }
        }
    }
    
    return AP.stopEvent(ev);
}

////////////////////////////////////////////////////////////////////////////
// class ComboBox extends AP
////////////////////////////////////////////////////////////////////////////
// ComboBox DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="combo_box">
    <input type=hidden name="%NAME%" value="">
    ImgInput
</div>
WndBorder {
        <table class="cb_items_container" cellpadding="0" cellspacing="0">
            <tr class="cb_item">
                <td class="cb_sub_item_left_img" align="center" valign="middle">
                    <div><img src="img/page_num.gif"></div>
                </td>
                <td class="cb_sub_item_value" valign="middle" nowrap>
                    <input type="hidden" value="%value%">
                    <span>kakoy-to tekst</span>
                </td>
                <td class="cb_sub_item_right_img" align="center" valign="middle">
                    <div><img src="img/page_num.gif"></div>
                </td>
            </tr>
        </table>
}
*/
var CB_FLAGS_DROPDOWNLIST           = 0x01;
var CB_FLAGS_AUTOCOMPLETE           = 0x02;
var CB_FLAGS_REPLACEIMAGE           = 0x04;
var CB_FLAGS_DISABLED               = 0x08;
// states
var CBS_OVER                        = 0x01;
var CBS_DROPDOWN                    = 0x02;
// item states
var CBIS_OVER                       = 0x01;
////////////////////////////////////////////////////////////////////////////
// constructor
////////////////////////////////////////////////////////////////////////////
ComboBox = function()
{
    this.subclass = AP;
    this.subclass();

    this.input = new ImgInput();
    this.popup = new WndBorder();
    this.items_container = null;
    this.items_bd = null;
    
    this.selectedIndex = -1;

    this.className = "ComboBox";
}
ComboBox.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.create = function(elParent, name, flags, tabIndex)
{
    this.subclass = AP.prototype.create;
    this.subclass(elParent, "combo_box");
    
    this.flags = AP.is_set(flags, 0);
    
    if ( !(this.flags & CB_FLAGS_DROPDOWNLIST) )
            throw ("Currently not supported");
    
    this.inValue = AP.createNamedElement({
        tagName: "input",
        name: name,
        parent: this.element,
        type: "hidden"
    });

    var imgin_flags = IMGIN_FLAGS_TEXT;
    if ( this.flags & CB_FLAGS_DROPDOWNLIST )
    {
        imgin_flags |= IMGIN_FLAGS_READONLY;
    }
    this.input.create(this.element, null, imgin_flags, tabIndex);
    this.input.addNotifyHandler( [ComboBox.onInputNotify, this] );
    this.input.image({
        src: ROOT_PATH + "img/cb/btn.gif",
        left: 0,
        cx: "16px",
        cy: "16px"
    });
    if ( this.flags & CB_FLAGS_DROPDOWNLIST )
    {
        AP.addEvent(this.input.element, "mousedown", ComboBox.onPressDropDown);
    }
    else
    {
        AP.addEvent(this.input.imgRight, "mousedown", ComboBox.onPressDropDown);
    }
    
    this.popup.create(document.body, null, WND_FLAGS_MODAL | WND_FLAGS_HIDDEN | WND_FLAGS_POPUP | WND_FLAGS_BORDER | WND_FLAGS_VRESIZE | WND_FLAGS_HRESIZE);
    this.popup.cb = this;
    this.popup.element.className += " combo_wnd";
    
    var cells = [[  ]];
    this.items_container = AP.createTable(0, 2, cells, null, "cb_items_container");
    this.items_bd = this.items_container.tBodies[0];
    this.popup.assignChild(this.items_container);
    
    this.tabIndex( AP.is_set(tabIndex, 0) );

    // hide ontimeout
    //this.timeOut = hideOnTimeOut;
    AP.addEvent(this.popup.element, "mousedown", ComboBox.onPopUpMouseAction);

    if ( this.flags & CB_FLAGS_DISABLED )
    {
        this.enable(false);
    }
}

////////////////////////////////////////////////////////////////////////////
// destroy
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.destroy = function()
{
    this.subclass = AP.prototype.destroy;
    this.subclass();
    //TODO:
}

////////////////////////////////////////////////////////////////////////////
// enable
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.enable = function(doEnable)
{
    this.input.enable(doEnable);

    this.subclass = AP.prototype.enable;
    return this.subclass(doEnable);
}

////////////////////////////////////////////////////////////////////////////
// getName
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.getName = function()
{
    return ( (this.inValue.name && this.inValue.name.length) ? this.inValue.name : null );
}

////////////////////////////////////////////////////////////////////////////
// appendItem
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.appendItem = function(itemParam)
{
    itemParam.index = this.getItemCount();
    return this.insertItem(itemParam);
}

////////////////////////////////////////////////////////////////////////////
// insertItem
////////////////////////////////////////////////////////////////////////////
/*
var itemParam = {
    index: -1,
    value: "docs",
    text: "Documents",
    img: "img/cb/docs.gif" / { src: "img/cb/docs.gif", cx: 16px, cy: 16px },
    img_r: "img/cb/warning.gif" / { src: "img/cb/warning.gif", cx: 16px, cy: 16px }
}
*/
ComboBox.prototype.insertItem = function(itemParam)
{
    // DOM
    // make table row
    var trItem = this.items_bd.insertRow(itemParam.index);
    trItem.className = "ap cb_item";
    trItem.instance = this;
    trItem._state = 0;
    var tdImgLeft = trItem.insertCell(trItem.cells.length);
    tdImgLeft.className = "ap cb_sub_item_limg";
    var tdValue = trItem.insertCell(trItem.cells.length);
    tdValue.className = "ap cb_sub_item_value";
    var tdImgRight = trItem.insertCell(trItem.cells.length);
    tdImgRight.className = "ap cb_sub_item_rimg";
    
    // left img
    trItem.divImgLeft = AP.createElement("div", tdImgLeft);
    AP.show(trItem.divImgLeft, false);
    trItem.imgLeft = AP.createElement("img", trItem.divImgLeft);
    
    // Value
    trItem.spanText = AP.createElement("span", tdValue);
    
    // right img
    trItem.divImgRight = AP.createElement("div", tdImgRight);
    AP.show(trItem.divImgRight, false);
    trItem.imgRight = AP.createElement("img", trItem.divImgRight);
    // -- DOM

    // Fill Item
    itemParam.img = AP.is_set(itemParam.img, "");
    itemParam.img_r = AP.is_set(itemParam.img_r, "");
    this.setItem(itemParam);
    
    AP.addEvent(trItem, "mouseover", ComboBox.onItemMouseAction);
    AP.addEvent(trItem, "mouseout", ComboBox.onItemMouseAction);
    AP.addEvent(trItem, "click", ComboBox.onItemMouseAction);

    this.tabIndex( this.tabIndex() );
    
    this.fireNotify(CBN_ITEM_INSERTED, itemParam.index);

    return 0;
}

////////////////////////////////////////////////////////////////////////////
// getItem
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.getItem = function(index)
{
    var itemParam = { index: index };
    var trItem = this._getItem(index);
    
    // Value
    if ( AP.is_set(trItem.value) )
    {
        itemParam.value = trItem.value;
    }
    
    // Text
    itemParam.text = trItem.spanText.innerHTML;
    
    // Left Image
    if ( trItem.imgLeft.src && trItem.imgLeft.src.length )
    {
        itemParam.img = {
            src: trItem.imgLeft.src
        };
        if ( AP.is_set(trItem.imgLeft.style.width) )
        {
            itemParam.img.cx = trItem.imgLeft.style.width;
        }
        if ( AP.is_set(trItem.imgLeft.style.height) )
        {
            itemParam.img.cy = trItem.imgLeft.style.height;
        }
    }
    
    // Right Image
    if ( trItem.imgRight.src && trItem.imgRight.src.length )
    {
        itemParam.img_r = {
            src: trItem.imgRight.src
        };
        if ( AP.is_set(trItem.imgRight.style.width) )
        {
            itemParam.img_r.cx = trItem.imgRight.style.width;
        }
        if ( AP.is_set(trItem.imgRight.style.height) )
        {
            itemParam.img_r.cy = trItem.imgRight.style.height;
        }
    }
    
    return itemParam;
}

////////////////////////////////////////////////////////////////////////////
// setItem
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.setItem = function(itemParam)
{
    if ( AP.is_set(itemParam.index) === false )
        return -1;
    
    var trItem = this._getItem(itemParam.index);
    var selected = (this.selIndex() == itemParam.index);
    
    // Value
    if ( AP.is_set(itemParam.value) !== false )
    {
        trItem.value = itemParam.value;
        if ( selected )
        {
            this.inValue.value = itemParam.value;
        }
    }

    // Text
    if ( AP.is_set(itemParam.text) !== false )
    {
        trItem.spanText.innerHTML = itemParam.text;
        if ( selected )
        {
            this.input.value(itemParam.text);
        }
    }

    // Left Image
    if ( itemParam.img )
    {
        if ( typeof itemParam.img != "string" )
        {
            trItem.imgLeft.src = itemParam.img.src;
            if ( itemParam.img.cx )
            {
                trItem.imgLeft.style.width = itemParam.img.cx;
            }
            if ( itemParam.img.cy )
            {
                trItem.imgLeft.style.width.height = itemParam.img.cy;
            }
        }
        else
        {
            trItem.imgLeft.style.width = "auto";
            trItem.imgLeft.style.height = "auto";
            trItem.imgLeft.src = itemParam.img;
        }
        AP.show( trItem.divImgLeft, (trItem.imgLeft.src != "") );
        
        if ( selected && (this.flags & CB_FLAGS_REPLACEIMAGE) && trItem.imgLeft.src )
        {
            this.input.image({
                src: trItem.imgLeft.src,
                left: true
            });
        }
    }
    
    // Right Image
    if ( itemParam.img_r )
    {
        if ( typeof itemParam.img_r != "string" )
        {
            trItem.imgRight.src = itemParam.img_r.src;
            if ( itemParam.img_r.cx )
            {
                trItem.imgRight.style.width = itemParam.img_r.cx;
            }
            if ( itemParam.img_r.cy )
            {
                trItem.imgRight.style.width.height = itemParam.img_r.cy;
            }
        }
        else
        {
            trItem.imgRight.style.width = "auto";
            trItem.imgRight.style.height = "auto";
            trItem.imgRight.src = itemParam.img_r;
        }
        AP.show( trItem.divImgRight, (trItem.imgRight.src != "") );
    }

    return 0;
}

////////////////////////////////////////////////////////////////////////////
// deleteAll
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.deleteAll = function()
{
    while ( this.getItemCount() )
    {
        this.deleteItem( this.getItemCount() - 1 );
    }   
}

////////////////////////////////////////////////////////////////////////////
// deleteItem
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.deleteItem = function(index)
{
    var trItem = this._getItem(index);
    
    if ( this.fireNotify(CBN_ITEM_DELETING, index) == -1 )
        return;

    AP.removeEvent(trItem, "mouseover", ComboBox.onItemMouseAction);
    AP.removeEvent(trItem, "mouseout", ComboBox.onItemMouseAction);
    AP.removeEvent(trItem, "click", ComboBox.onItemMouseAction);

    // detrsoy DOM
    // left img
    trItem.divImgLeft.removeChild(trItem.imgLeft);
    trItem.imgLeft = null;
    trItem.divImgLeft.parentNode.removeChild(trItem.divImgLeft);
    trItem.divImgLeft = null;
    // text 
    trItem.spanText.parentNode.removeChild(trItem.spanText);
    trItem.spanText = null;
    // right img
    trItem.divImgRight.removeChild(trItem.imgRight);
    trItem.imgRight = null;
    trItem.divImgRight.parentNode.removeChild(trItem.divImgRight);
    trItem.divImgRight = null;
    // -- detrsoy DOM

    trItem.instance = null;
    trItem._state = null;
    this.items_bd.deleteRow(index);
    
    if ( this.selectedIndex == index )
    {
        this.selIndex(-1);
    }
    else if ( index < this.selectedIndex )
    {
        this.selectedIndex -= 1;
    }

    this.fireNotify(CBN_ITEM_DELETED, index);
}

////////////////////////////////////////////////////////////////////////////
// getItemCount
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.getItemCount = function()
{
    return this.items_bd.rows.length;
}

////////////////////////////////////////////////////////////////////////////
// findItem
////////////////////////////////////////////////////////////////////////////
var CB_FIND_TEXT        = 0x00;
var CB_FIND_VALUE       = 0x01;
ComboBox.prototype.findItem = function(from, value, flags)
{
    // DEBUGINFO - DebugLog.write("cb_find 0 [" + from + ", " + value + ", " + flags + "]");
    var retValue = -1;
    for ( var loop = from; loop < this.getItemCount(); loop++ )
    {
        var trItem = this._getItem(loop);
        // DEBUGINFO - DebugLog.write("cb_find 1 [" + trItem.value + "]");
        if ( flags & CB_FIND_VALUE )
        {
            if ( trItem.value == value )
            {
                retValue = loop;
                // DEBUGINFO - DebugLog.write("cb_find 2 [" + loop + "]");
                break;
            }
        }
        else
        {
            if ( trItem.spanText.innerHTML == value )
            {
                retValue = loop;
                break;
            }
        }
    }
    return retValue;
}

////////////////////////////////////////////////////////////////////////////
// value - get/set selected value
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.value = function(value)
{
    if ( AP.is_set(value) )
    {
        var index = this.findItem(0, value, CB_FIND_VALUE);
        if ( index != -1 )
        {
            this.selIndex(index);
        }
        else
        {
            this.inValue.value = value;
            this.selectedIndex = -1;
            if ( this.flags & CB_FLAGS_DROPDOWNLIST )
            {
                this.input.value("");
                return null;
            }
            else
            {
                this.input.value(value);
                this.selectedIndex = -1;
            }
        }
    }
    
    return this.inValue.value;
}

////////////////////////////////////////////////////////////////////////////
// selIndex - get/set selected item index
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.selIndex = function(index)
{
    if ( AP.is_set(index) && (this.selIndex() != index) )
    {
        if ( index >= this.getItemCount() )
            throw ("[COMBOBOX::SELINDEX] Invalid index - " + index);
        
        if ( index == -1 )
        {
            this.inValue.value = "";
            this.input.value("");
            this.selectedIndex = index;
            
            if ( this.flags & CB_FLAGS_REPLACEIMAGE )
            {
                this.input.image({
                    src: "",
                    left: true
                });
            }
        }
        else
        {
            var trItem = this._getItem(index);
        
            this.inValue.value = trItem.value;
            this.input.value(trItem.spanText.innerHTML);
            this.selectedIndex = index;
            
            if ( (this.flags & CB_FLAGS_REPLACEIMAGE) )
            {
                this.input.image({
                    src: trItem.imgLeft.src,
                    left: true
                });
            }
        }
        
        this.fireNotify(CBN_ONCHANGED, index);
    }

    return this.selectedIndex;
}

////////////////////////////////////////////////////////////////////////////
// tabIndex get / set tabIndex
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.tabIndex = function(tabIndex)
{
    if ( AP.is_set(tabIndex) !== false )
    {
        this.input.tabIndex(tabIndex);
        for ( var loop = 0; loop < this.getItemCount(); loop++ )
        {
            var trItem = this._getItem(loop);
            trItem.spanText.tabIndex = tabIndex + loop + 1;
        }
    }
    return this.input.tabIndex();
}

////////////////////////////////////////////////////////////////////////////
// dropdown
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.dropdown = function(visible)
{
    if ( visible )
    {
        this.popup.dropdown(this.element, 0);
        this.state( this.state() | CBS_DROPDOWN );
        
        var divPopUpClientWidth = AP.clientWidth( this.popup.childArea() );
        if ( AP.offsetWidth(this.items_container) < divPopUpClientWidth )
        {
            AP.width(this.items_container, divPopUpClientWidth);
            if ( this.getItemCount() )
            {
                this.items_bd.rows[0].cells[1].style.width = "99%";
            }
        }
    }
    else
    {
        this.popup.hide();
        this.state( this.state() & ~CBS_DROPDOWN );
        
        AP.width(this.items_container, "auto");
        if ( this.getItemCount() )
        {
            this.items_bd.rows[0].cells[1].style.width = "auto";
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// _stateToClassName
////////////////////////////////////////////////////////////////////////////
/*
var img = {
    src: value,
    cx: value,
    cy: value
};
*/
ComboBox.prototype.image = function(img)
{
    return this.input.image({
        src: img.src,
        left: true,
        cx: img.cx,
        cy: img.cy
    });
}

////////////////////////////////////////////////////////////////////////////
// _stateToClassName
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype._stateToClassName = function()
{
    var className = "ap combo_box";
    if ( this._state & (CBS_OVER | CBS_DROPDOWN) )
    {
        className += " combo_box_over";
        this.input.image({
            src: ROOT_PATH + "img/cb/btn_over.gif",
            left: 0
        });
    }
    else
    {
        this.input.image({
            src: ROOT_PATH + "img/cb/btn.gif",
            left: 0
        });
    }
    if ( this.element.className != className )
    {
        this.element.className = className;
    }
}

////////////////////////////////////////////////////////////////////////////
// itemState
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype.itemState = function(index, state)
{
    var trItem = this._getItem(index);
    if ( !trItem )
    {
        DebugLog.write("trItem == null");
    }
    
    if ( AP.is_set(state) === false )
        return trItem._state;

    var stateOld = trItem._state;
    if ( stateOld == state )
        return null;
        
    trItem._state = state;
    ComboBox._itemStateToClassName(trItem);
    this.fireNotify(CBN_ITEM_STATE_CHANGED, index);

    return trItem._state;   
}

////////////////////////////////////////////////////////////////////////////
// _getItem
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype._getItem = function(index)
{
    return this.items_bd.rows[index];
}

////////////////////////////////////////////////////////////////////////////
// _getItemIndex
////////////////////////////////////////////////////////////////////////////
ComboBox.prototype._getItemIndex = function(item)
{
    var index = -1;
    
    for ( var loop = 0; loop < this.items_bd.rows.length; loop++ )
    {
        if ( this.items_bd.rows[loop] == item )
        {
            index = loop;
            break;
        }
    }
    
    return index;
}

////////////////////////////////////////////////////////////////////////////
// _itemStateToClassName
////////////////////////////////////////////////////////////////////////////
ComboBox._itemStateToClassName = function(trItem)
{
    var className = "ap cb_item";
    if ( trItem._state & CBIS_OVER )
    {
        className += " cb_item_over";
    }
    else
    {
    }
    trItem.className = className;
}

////////////////////////////////////////////////////////////////////////////
// onInputNotify
////////////////////////////////////////////////////////////////////////////
ComboBox.onInputNotify = function(param)
{
    var pThis = param.subscriber;
    if ( param.code == APN_STATE_CHANGED
         && (param.param.diff_state & IMGINS_OVER) )
    {
        if ( param.param.new_state & IMGINS_OVER )
        {
            pThis.state( pThis.state() | CBS_OVER );
        }
        else
        {
            pThis.state( pThis.state() & ~CBS_OVER );
        }
    }
    
    return 0;
}

////////////////////////////////////////////////////////////////////////////
// getItemElement
////////////////////////////////////////////////////////////////////////////
ComboBox.getItemElement = function(obj)
{
    for ( var o = obj; o && !AP.isSetClassName(o, "cb_item"); o = o.parentNode );
    return o;
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
ComboBox.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && ComboBox.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// onPressDropDown
////////////////////////////////////////////////////////////////////////////
ComboBox.onPressDropDown = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.currentTarget);
    var cb = ComboBox.getInstance(obj);
    if ( !cb )
        return true;
    
    if ( cb.enable() )
    {
        cb.dropdown(true);
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// onPopUpMouseAction
////////////////////////////////////////////////////////////////////////////
ComboBox.onPopUpMouseAction = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    if ( obj.instance && obj.instance.cb )
    {
        obj.instance.cb.dropdown(false);
    }
    return true;
}

////////////////////////////////////////////////////////////////////////////
// onItemMouseAction
////////////////////////////////////////////////////////////////////////////
ComboBox.onItemMouseAction = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.currentTarget);
    var trItem = ComboBox.getItemElement(obj);
    if ( !trItem )
        return true;

    var cb = trItem.instance;
    if ( !cb )
    {
        return true;
    }
    if ( cb.enable() == false )
    {
        return true;
    }
    
    
    var index = cb._getItemIndex(trItem);

    if ( ev.type == "mouseover" )
    {
        cb.itemState( index, cb.itemState(index) | CBIS_OVER );
    }
    else if ( ev.type == "mouseout" )
    {
        cb.itemState( index, cb.itemState(index) & ~CBIS_OVER );
    }
    else if ( ev.type == "click" )
    {
        cb.fireNotify(CBN_ONITEM_CLICKED, index);
        cb.selIndex(index);
        cb.dropdown(false);
    }

    return true;
}

////////////////////////////////////////////////////////////////////////////
// class ColumnHeader extends AP
////////////////////////////////////////////////////////////////////////////
// ColumnHeader DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="column_header">
    <div class="chdr_items_container">
        <div class="chdr_items">
            <div class="chdr_item">
                <img src="sort_icon">
                Column 1
            </div>
            ............
        </div>
    </div>
</div>
*/
// Globals
var HDR_ITEM_MINWIDTH = 10;

/////////////////////////////////////////////
// Item Flags
var HDR_ITEM_SIZABLE             = 0x01;
var HDR_ITEM_DISABLED            = 0x02;
var HDR_ITEM_ALIGNCENTER         = 0x00;
var HDR_ITEM_ALIGNLEFT           = 0x10;
var HDR_ITEM_ALIGNRIGHT          = 0x20;
// Item Flags
/////////////////////////////////////////////

/////////////////////////////////////////////
// Item State
var HDRIS_OVER          = 0x01;
var HDRIS_DOWN          = 0x02;
var HDRIS_SORTEDBY_ASC  = 0x04;
var HDRIS_SORTEDBY_DESC = 0x08;
var HDRIS_HIDDEN        = 0x10;
// Item State
/////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
// constructor
////////////////////////////////////////////////////////////////////////////
ColumnHeader = function()
{
    this.subclass = AP;
    this.subclass();

    this.splitter = null;
    this.items = new Array();

    this.className = "ColumnHeader";

    this._overstate = {
        over_control: false,
        over_timer: null
    }; // !!! internal use only for mouse over routine
}
ColumnHeader.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype.create = function(elParent, flags)
{
    this.subclass = AP.prototype.create;
    this.subclass(elParent, "column_header column_header_xp1");
    
    this.flags = AP.is_set(flags, 0);
    
    this.divItems = AP.createElement("div", this.element, "chdr_items");
    
    this.splitter = Splitter.getGInstance();
    
    AP.addEvent(this.element, "mousedown", ColumnHeader.onMouseAction);
    AP.addEvent(this.element, "mouseup", ColumnHeader.onMouseAction);
    AP.addEvent(this.element, "click", ColumnHeader.onMouseAction);
    AP.addEvent(this.element, "dblclick", ColumnHeader.onMouseAction);
    AP.addEvent(this.element, "mouseover", ColumnHeader.onMouseAction);
    AP.addEvent(this.element, "mouseout", ColumnHeader.onMouseAction);
    
    AP.addEvent(this.element, "contextmenu", ColumnHeader.onContextMenu);
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype.setPosition = function(pos)
{
    pos.flags = AP.is_set(pos.flags, 0);
    this.subclass = AP.prototype.setPosition;
    this.subclass(pos);

    if ( AP.is_set(pos.scrollX) !== false )
    {
        AP.setPosition(this.divItems, {
            x: pos.scrollX
        });
    }
    
    if ( AP.is_set(pos.flags & POS_FLAGS_RECALCULATE) )
    {
        this._calcItemsPos();
    }
}

////////////////////////////////////////////////////////////////////////////
// appendItem
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype.appendItem = function(hdrItem)
{
    hdrItem.index = this.getItemsCount();
    return this.insertItem(hdrItem);
}

////////////////////////////////////////////////////////////////////////////
// insertItem
////////////////////////////////////////////////////////////////////////////
/*
var hdrItem = {
    index: where
    text: htmlText,
    data: userData,
    width: int,
    min_width: int,
    max_width : int,
    flags: int
}
*/
ColumnHeader.prototype.insertItem = function(hdrItem)
{
    var item = {
        id: AP.uniqueId("hdr_item_"),
        text: AP.is_set(hdrItem.text, ""),
        data: AP.is_set(hdrItem.data, null),
        width: AP.is_set(hdrItem.width, 80),
        min_width: AP.is_set(hdrItem.min_width, 15),
        max_width: AP.is_set(hdrItem.max_width, 1000),
        flags: AP.is_set(hdrItem.flags, HDR_ITEM_SIZABLE | HDR_ITEM_ALIGNCENTER),
        state: AP.is_set(hdrItem.state & HDRIS_HIDDEN, 0) // just state "hidden" can be modifued by user
    };
    
    var resultIndex = -1;
    if ( hdrItem.index > -1
         && hdrItem.index < this.items.length )
    {
        var items_tmp = new Array();
        for ( var loop = 0; loop < this.items.length; loop++ )
        {
            if ( loop == hdrItem.index )
            {
                items_tmp.push(item);
                resultIndex = loop;
            }
            items_tmp.push(this.items[loop]);
        }
        this.items = items_tmp;
    }
    else
    {
        this.items.push(item);
        resultIndex = this.items.length - 1;
    }
    
    this._insertItem_virt(resultIndex);
    this._calcItemsPos(resultIndex);
    
    return item;
}

////////////////////////////////////////////////////////////////////////////
// itemState
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype.itemState = function(id, state)
{
    var itemIndex = this._getItemIndex(id);
    var item = this.items[itemIndex];
    if ( AP.is_set(state) && item.state != state )
    {
        item.state = state;
        this._itemStateToClassName(id);
    }
    return item.state;
}

////////////////////////////////////////////////////////////////////////////
// sort
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype.sort = function(id, sortState)
{
    var itemIndex = this._getItemIndex(id);
    for ( var loop = 0; loop < this.items.length; loop++ )
    {
        if ( loop == itemIndex )
        {
            continue;
        }
        var itemState = this.itemState(loop);
        itemState &= ~HDRIS_SORTEDBY_ASC;
        itemState &= ~HDRIS_SORTEDBY_DESC;
        this.itemState(loop, itemState);
    }
    var itemState = this.itemState(itemIndex);
    itemState &= ~HDRIS_SORTEDBY_ASC;
    itemState &= ~HDRIS_SORTEDBY_DESC;
    itemState |= sortState;
    this.itemState(itemIndex, itemState);
}

////////////////////////////////////////////////////////////////////////////
// setItem
////////////////////////////////////////////////////////////////////////////
/*
var hdrItem = {
    id: item Id,
    index: where
    text: htmlText,
    data: userData,
    width: int,
    min_width: int,
    max_width: int,
    flags: int,
    state: available states (HDRIS_HIDDEN)
}
*/
ColumnHeader.prototype.setItem = function(hdrItem)
{
    var itemIndex = this._getItemIndex(hdrItem.id);
    if ( itemIndex == -1 )
    {
        return false;
    }
    
    if ( AP.is_set(hdrItem.index) )
    {
        DebugLog.write("TODO: // Move Items");
    }
    if ( AP.is_set(hdrItem.text) )
    {
        this.items[itemIndex].text = hdrItem.text;
        this._changeItemText_virt(itemIndex);
    }
    if ( AP.is_set(hdrItem.data) )
    {
        this.items[itemIndex].data = hdrItem.data;
    }
    if ( AP.is_set(hdrItem.width) )
    {
        if ( this.items[itemIndex].width != hdrItem.width )
        {
            var old_width = this.items[itemIndex].width;
            this.items[itemIndex].width = hdrItem.width;
            this._calcItemsPos(itemIndex);
            this.fireNotify(HDRN_ITEM_SIZED, {
                item: this.items[itemIndex].id,
                old_width: old_width,
                new_width: hdrItem.width
            });
        }
    }
    if ( AP.is_set(hdrItem.flags) )
    {
    }
    if ( AP.is_set(hdrItem.state) )
    {
        DebugLog.write("TODO: // Checnge Item State");
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// getItem
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype.getItem = function(itemId)
{
    var itemIndex = this._getItemIndex(itemId);
    if ( itemIndex == -1 )
    {
        return false;
    }
    
    return this.items[itemIndex];
}

////////////////////////////////////////////////////////////////////////////
// deleteItem
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype.deleteItem = function(itemId)
{
    var itemIndex = this._getItemIndex(itemId);
    if ( itemIndex == -1 )
    {
        return false;
    }

    this._deleteItem_virt(itemIndex);
    this.items.splice(itemIndex, 1);
    this._calcItemsPos(itemIndex);
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// deleteAllItems
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype.deleteAllItems = function()
{
    while ( this.items.length )
    {
        this.deleteItem(this.items.length - 1);
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// onMouseAction
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype.getItemsCount = function()
{
    return this.items.length;
}

////////////////////////////////////////////////////////////////////////////
// _getItemIndex
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype._getItemIndex = function(id, start, end)
{
    if ( id === null )
    {
        return -1;
    }
    else if ( typeof id == "number" )
    {
        return id;
    }
    
    var index = -1;
    start = Math.max( AP.is_set(start, 0), 0 );
    end = Math.min( AP.is_set(end, this.items.length), this.items.length );
    for ( var loop = start; loop < end; loop++ )
    {
        if ( this.items[loop].id == id )
        {
            index = loop;
            break;
        }
    }
    
    return index;
}

////////////////////////////////////////////////////////////////////////////
// _insertItem_virt
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype._insertItem_virt = function(itemIndex)
{
    var align = "center";
    if ( this.items[itemIndex].flags & HDR_ITEM_ALIGNLEFT )
    {
        align = "left";
    }
    else if ( this.items[itemIndex].flags & HDR_ITEM_ALIGNRIGHT )
    {
        align = "right";
    }
    
    //var tds = [["text ellipsis", null, null, align, "middle"]];
    var tds = [["text", null, null, align, "middle"]];
    if ( this.items[itemIndex].flags & HDR_ITEM_SIZABLE )
    {
        tds.push(["sizing", null, null, null, null]);
    }
    var cells = [[[ null, tds ]]];
    
    var className = "chdr_item";
    if ( this.items[itemIndex].flags & HDR_ITEM_DISABLED )
    {
        className += " chdr_item_disabled";
    }
    var domItem = {
        element: AP.createTable(0, 0, cells, this.divItems, className)
    };
    domItem.element.id = this.items[itemIndex].id;
    
    this._changeItemText_virt(itemIndex);
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// _itemStateToClassName
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype._itemStateToClassName = function(id)
{
    var itemIndex = this._getItemIndex(id);
    this._changeItemText_virt(itemIndex);
}

////////////////////////////////////////////////////////////////////////////
// _deleteItem_virt
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype._deleteItem_virt = function(itemIndex)
{
    var item = this.items[itemIndex];
    var item_el = AP.getElement(item.id);
    if ( !item_el )
    {
        return false;
    }
    
    this.divItems.removeChild(item_el);
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// _changeItemText_virt
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype._changeItemText_virt = function(itemIndex)
{
    var item = this.items[itemIndex];
    var item_el = AP.getElement(item.id);
    if ( !item_el )
    {
        return false;
    }

    var text = item.text;
    
    // Add Sort State
    var strAsc = "<img src=\"" + ROOT_PATH + "img/hdr/sort_asc.gif\"/>";
    var strDesc = "<img src=\"" + ROOT_PATH + "img/hdr/sort_desc.gif\"/>";
    if ( item.state & HDRIS_SORTEDBY_ASC )
    {
        text = AP.appendSubString(text, strAsc, " ");
    }
    else if ( item.state & HDRIS_SORTEDBY_DESC )
    {
        text = AP.appendSubString(text, strDesc, " ");
    }
    
    // Apply Text to DOM
    item_el.tBodies[0].rows[0].cells[0].innerHTML = text;
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// _calcItemsPos
////////////////////////////////////////////////////////////////////////////
ColumnHeader.prototype._calcItemsPos = function(from, to)
{
    from = AP.is_set(from, 0);
    to = AP.is_set(to, this.items.length);
    var left = 0;
    for ( var loop = 0; loop < to; loop++ )
    {
        var item = this.items[loop];
        if ( item.state & HDRIS_HIDDEN )
        {
            continue;
        }
        
        if ( loop >= from )
        {
            var item_el = AP.getElement(item.id);
            if ( item_el )
            {
                AP.setPosition(item_el , {
                    x: left,
                    cx: item.width
                });
            }
        }
        
        left += item.width;
    }
    AP.setPosition(this.divItems, {
        cx: left
    });
}

////////////////////////////////////////////////////////////////////////////
// onMouseAction
////////////////////////////////////////////////////////////////////////////
ColumnHeader.onMouseAction = function(ev)
{
    ev || (ev = window.event);
    var obj = (AP.isIE() ? ev.srcElement : ev.target);
    var item = ColumnHeader.getItemElement(obj);
    var pThis = ColumnHeader.getInstance(item ? item : obj);
    if ( !pThis || pThis._state & APS_DISABLED )
    {
        return true;
    }

    var item_id = item ? item.id : null;
    var item_index = item_id ? pThis._getItemIndex(item_id) : -1;
    
    // Drop All Events If Item Disabled
    if ( item_index != -1
         && pThis.items[item_index].flags & HDR_ITEM_DISABLED
         && !AP.isSetClassName(obj, "sizing") )
    {
        return true;
    }
    
    if ( ev.type == "click"
         || ev.type == "dblclick" )
    {
        if ( item_index != -1 )
        {
            if ( AP.isSetClassName(obj, "sizing") )
            {
                DebugLog.write("TODO:// ColumnHeader on sizing " + ev.type, item_index);
            }
            else
            {
                pThis.fireNotify((ev.type == "click") ? HDRN_ITEM_CLICKED : HDRN_ITEM_DBLCLICKED, {
                    item: item_index
                });
            }
        }
    }
    else if ( ev.type == "mousedown" )
    {
        if ( item_index != -1 )
        {
            if ( AP.isSetClassName(obj, "sizing") )
            {
                var splt_cy = pThis.fireNotify(HDRN_ITEM_GETSPLTHEIGHT, item_index);
                if ( splt_cy < 0 )
                {
                    return true;
                }
                splt_cy = Math.max(splt_cy, AP.offsetHeight(pThis.element));
                
                // Start Sizing
                var pos = AP.getAbsolutePos(item);
                var itemWidth = pThis.items[item_index].width;
                var dtLeft = itemWidth - pThis.items[item_index].min_width;
                var dtRight = document.body.scrollWidth - pos.x - itemWidth;
                dtRight = Math.min(pThis.items[item_index].max_width - itemWidth, dtRight);
                var start_drag = {
                    x: (pos.x + itemWidth - 3),
                    y: pos.y,
                    cx: 3,
                    cy: splt_cy,
                    type: SPLT_HORIZONTAL,
                    className: "light_splitter",
                    dtLeft: dtLeft,
                    dtRight: dtRight,
                    clientX: ev.clientX,
                    clientY: ev.clientY,
                    notifyHandler: [ColumnHeader.onSplitted, {hdr: pThis.element.id, itemId: item_id}],
                    param: item
                };
    
                pThis.splitter.startDrag(start_drag);
                
                return AP.stopEvent(ev);
            }
            else
            {
                pThis.items[item_index].state |= HDRIS_DOWN;
                AP.appendClassName(item, "chdr_item_down");
            }
        }
    }
    else if ( ev.type == "mouseup" )
    {
        if ( item_index != -1
             && !AP.isSetClassName(obj, "sizing") )
        {
            pThis.items[item_index].state &= ~HDRIS_DOWN;
            AP.removeClassName(item, "chdr_item_down");
        }
    }
    else if ( ev.type == "mouseover" )
    {
        if ( pThis._overstate.over_control )
        {
            if ( pThis._overstate.over_timer )
            {
                clearTimeout(pThis._overstate.over_timer);
                pThis._overstate.over_timer = null;
            }
        }
        else
        {
            // DEBUGINFO - DebugLog.write("over");
            pThis._overstate.over_control = true;
        }
        
        var old_item_id = pThis._overstate.over_item;
        var old_item_index = old_item_id ? pThis._getItemIndex(old_item_id) : -1;
        
        if ( item_index != -1
             && !AP.isSetClassName(obj, "sizing") )
        {
            if ( item_id != old_item_id )
            {
                if ( old_item_index != -1 )
                {
                    pThis.items[old_item_index].state &= ~HDRIS_OVER;
                    pThis.items[old_item_index].state &= ~HDRIS_DOWN;
                    // DEBUGINFO - DebugLog.write("out_item", old_item_id);
                    var old_item = AP.getElement(old_item_id);
                    if ( old_item )
                    {
                        AP.removeClassName(old_item, "chdr_item_over");
                        AP.removeClassName(old_item, "chdr_item_down");
                    }
                }
            
                // DEBUGINFO - DebugLog.write("over_item", item.id);
                pThis._overstate.over_item = item_id;
                pThis.items[item_index].state |= HDRIS_OVER;
                AP.appendClassName(item, "chdr_item_over");
            }
        }
        else
        {
            if ( old_item_index != -1 )
            {
                pThis.items[old_item_index].state &= ~HDRIS_OVER;
                pThis.items[old_item_index].state &= ~HDRIS_DOWN;
                // DEBUGINFO - DebugLog.write("out_item", old_item_id);
                var old_item = AP.getElement(old_item_id);
                if ( old_item )
                {
                    AP.removeClassName(old_item, "chdr_item_over");
                    AP.removeClassName(old_item, "chdr_item_down");
                }
            }
            pThis._overstate.over_item = null;
        }
    }
    else if ( ev.type == "mouseout" )
    {
        // Over Item State
        if ( pThis._overstate.over_control
             && !pThis._overstate.over_timer )
        {
            var func = "var el = AP.getElement(\"" + pThis.element.id + "\");";
            func += "var pThis = el ? el.instance : null;";
            func += "if ( pThis ) {";
            func += "   pThis._overstate.over_timer = null;";
            func += "   pThis._overstate.over_control = false;";
            func += "   var old_item_id = pThis._overstate.over_item;";
            func += "   var old_item_index = old_item_id ? pThis._getItemIndex(old_item_id) : -1;";
            func += "   if ( old_item_index != -1 ) {";
            // DEBUGINFO - func += "       DebugLog.write(\"out_item\", old_item_id);";
            func += "       pThis.items[old_item_index].state &= ~HDRIS_OVER;";
            func += "       pThis.items[old_item_index].state &= ~HDRIS_DOWN;";
            func += "       var old_item = AP.getElement(old_item_id);";
            func += "       if ( old_item ) {";
            func += "           AP.removeClassName(old_item, \"chdr_item_over\");";
            func += "           AP.removeClassName(old_item, \"chdr_item_down\");";
            func += "       }";
            func += "   }";
            func += "   pThis._overstate.over_item = null;";
            // DEBUGINFO - func += "   DebugLog.write(\"out_timer\");";
            func += "};";
            pThis._overstate.over_timer = setTimeout(func, 50);
        }
    }
    
    return true;
}

////////////////////////////////////////////////////////////////////////////
// onSplitted
////////////////////////////////////////////////////////////////////////////
ColumnHeader.onSplitted = function(param)
{
    if ( param.code != SPLTN_POS_CHANGED )
        return 0;
    
    var dtX = param.param.x;
    if ( !dtX )
        return 0;

    var pObj = param.producer;
    var pThisEl = AP.getElement(param.subscriber.hdr);
    var pThis = pThisEl ? pThisEl.instance : null;
    if ( !pThis )
    {
        return 0;
    }
    var item_index = pThis._getItemIndex(param.subscriber.itemId);
    if ( item_index == -1 )
    {
        return 0;
    }
    
    pThis.setItem({
        id: item_index,
        width: pThis.items[item_index].width + dtX
    });
    
    return 0;
}

////////////////////////////////////////////////////////////////////////////
// onContextMenu
////////////////////////////////////////////////////////////////////////////
ColumnHeader.onContextMenu = function(ev)
{
    ev || (ev = window.event);
    return AP.stopEvent(ev);
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
ColumnHeader.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && ColumnHeader.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// getItemElement
////////////////////////////////////////////////////////////////////////////
ColumnHeader.getItemElement = function(obj)
{
    for ( var o = obj;
          o && (o.className.indexOf("chdr_item") == -1);
          o = o.parentNode )
    {
        if ( o.instance && ColumnHeader.prototype.isPrototypeOf(o.instance) )
        {
            return null;
        }
    }
    return o;
}

////////////////////////////////////////////////////////////////////////////
// TimeInput extends AP
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// TimeInput DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="time_ctrl">
    <input type="hidden">
    <div class="time_ctrl_cb">
        <select>
            <option>01
            <option>02
        </select>
    </div>
    <div class="time_ctrl_separator">
        &nbsp;
    </div>
    <div class="time_ctrl_cb">
        <select>
            <option>01
            <option>02
        </select>
    </div>
    <div class="time_ctrl_separator">
        &nbsp;
    </div>
    <div class="time_ctrl_cb">
        <select>
            <option>AM
            <option>PM
        </select>
    </div>
</div>
*/
////////////////////////////////////////////////////////////////////////////
// constructor
////////////////////////////////////////////////////////////////////////////
TimeInput = function()
{
    this.subclass = AP;
    this.subclass();
    
    this.items = new Array();

    this.className = "TimeInput";
}
TimeInput.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
TimeInput.prototype.create = function(parent, name)
{
    this.subclass = AP.prototype.create;
    this.subclass(parent, "time_ctrl");
    
    this.input = AP.createNamedElement({
        tagName: "input",
        parent: this.element,
        name: name,
        type: "hidden"
    });

    this.cbHour = this._insertTimeField(this.element, "hour");
    this.cbHour.addNotifyHandler( [TimeInput.onChange, this] );
    
    var divSeparator = AP.createElement("div", this.element, "time_ctrl_separator");
    divSeparator.innerHTML = ":";

    this.cbMin = this._insertTimeField(this.element, "min");
    this.cbMin.addNotifyHandler( [TimeInput.onChange, this] );
    
    var divSeparator = AP.createElement("div", this.element, "time_ctrl_separator");

    this.cbDayPart = this._insertTimeField(this.element, "daypart");
    this.cbDayPart.addNotifyHandler( [TimeInput.onChange, this] );
    
    this.time("now");
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
TimeInput.prototype.setPosition = function(pos)
{
    this.subclass = AP.prototype.setPosition;
    this.subclass(pos);    
}

////////////////////////////////////////////////////////////////////////////
// getName
////////////////////////////////////////////////////////////////////////////
TimeInput.prototype.getName = function()
{
    return this.input.name;
}

////////////////////////////////////////////////////////////////////////////
// _insertTimeField
////////////////////////////////////////////////////////////////////////////
TimeInput.prototype._insertTimeField = function(parent, type)
{
    var divField = AP.createElement("div", parent, "time_ctrl_cb");
    
    var cb = new ComboBox();
    cb.create(divField, "", CB_FLAGS_DROPDOWNLIST);
    cb.setPosition({ cx:50, cy: AP.clientHeight(divField) });
    if ( type == "hour" )
    {
        for ( var loop = 1; loop <= 12; loop++ )
        {
            cb.appendItem({ text: ((loop < 10) ? ("0" + loop) : loop), value: loop });
        }
    }
    else if ( type == "min" )
    {
        for ( var loop = 0; loop < 60; loop++ /*+= 5*/ )
        {
            cb.appendItem({ text:((loop < 10) ? ("0" + loop) : loop), value: loop });
        }       
    }
    else if ( type == "daypart" )
    {
        cb.appendItem({ text: "AM", value: 0 });
        cb.appendItem({ text: "PM", value: 1 });
    }
    return cb;
}

////////////////////////////////////////////////////////////////////////////
// time - get/set time
////////////////////////////////////////////////////////////////////////////
TimeInput.prototype.time = function(time)
{
    if ( AP.is_set(time) !== false )
    {
        var tickTime = 0;
        
        if ( typeof time == "string" )
        {
            if ( time.toLowerCase() == "now" )
            {
                tickTime = Math.floor( AP.getTickCount() / 1000 );
            }
            else if ( time.toLowerCase() == "empty" )
            {
                tickTime = -1;
            }
            else
                throw ("not implemented");
        }
        else
        {
            tickTime = time;
        }
        
        var time_t = {
            hour: -1,
            min: -1,
            dayPart: -1
        }
        if ( tickTime > -1 )
        {
            var dateTime = new Date();
            
            dateTime.setTime(tickTime*1000);
            
            time_t.hour = dateTime.getUTCHours();
            time_t.min = dateTime.getUTCMinutes();
            time_t.dayPart = (Math.floor(dateTime.getTime() / (12*3600*1000)) < 1 ? 0 : 1);
            
            delete dateTime;
        }
        
        this.setTime(time_t);
    }

    var tickTime = parseInt(this.input.value);
    if ( isNaN(tickTime) )
    {
        tickTime = -1;
    }
    return tickTime;
}

////////////////////////////////////////////////////////////////////////////
// setTime
////////////////////////////////////////////////////////////////////////////
/*
var time = {
    hour: value,
    min: value,
    daypart: value (optional, ignored if hour > 12 or 0)
}
*/
TimeInput.prototype.setTime = function(time)
{
    if ( AP.is_set(time.hour) )
    {
        if ( time.hour == 0 )
        {
            time.hour = 12;
            time.daypart = 0;
        }
        else if ( time.hour > 12 )
        {
            time.hour -= 12;
            time.daypart = 1;
        }
        this.cbHour.value(time.hour);
    }
    if ( AP.is_set(time.min) )
    {
        this.cbMin.value(time.min);
    }
    if ( AP.is_set(time.dayPart) )
    {
        this.cbDayPart.value(time.dayPart);
    }
}

////////////////////////////////////////////////////////////////////////////
// getTime
////////////////////////////////////////////////////////////////////////////
TimeInput.prototype.getTime = function(daypart)
{
    daypart = AP.is_set(daypart, false);
    
    var time = {
        hour: this.cbHour.value(),
        min: this.cbMin.value(),
        daypart: this.cbDayPart.value()
    };
    
    if ( time.daypart == -1 )
    {
        time.hour = -1;
    }
    
    if ( !daypart )
    {
        if ( time.hour != -1 && time.daypart != -1 )
        {
            if ( time.hour == 12 && time.daypart == 0 )
            {
                time.hour -= 12;
                time.daypart = -1;
            }
            else if ( time.daypart == 1 && (time.hour > 0 && time.hour < 12) )
            {
                time.hour += 12;
                time.daypart = -1;
            }
        }
        else
        {
            time.daypart = -1;
        }
    }
    
    return time;
}

////////////////////////////////////////////////////////////////////////////
// _setTime
////////////////////////////////////////////////////////////////////////////
TimeInput.prototype._setTime = function(tickTime)
{
    this.input.value = tickTime;
    this.fireNotify(TIN_CHANGED);
}

////////////////////////////////////////////////////////////////////////////
// onChange
////////////////////////////////////////////////////////////////////////////
TimeInput.onChange = function(param)
{
    if ( param.code != CBN_ONCHANGED )
    {
        return 0;
    }

    var pThis = param.subscriber;
    
    var time_t = pThis.getTime(false);
    if ( time_t.hour == -1 || time_t.min == -1 )
    {
        pThis._setTime(-1);
    }
    else
    {
        var time = time_t.hour * 3600;
        time += time_t.min * 60;
        pThis._setTime(time);
    }
        
    return 0;
}

////////////////////////////////////////////////////////////////////////////
// getInstance
////////////////////////////////////////////////////////////////////////////
TimeInput.getInstance = function(obj)
{
    for ( var o = obj; o && !(o.instance && TimeInput.prototype.isPrototypeOf(o.instance)); o = o.parentNode );
    return (o ? o.instance : null);
}

////////////////////////////////////////////////////////////////////////////
// FieldSet extends AP
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// FieldSet DOM
////////////////////////////////////////////////////////////////////////////
/*
<div class="fieldset">
    <!--Client Area-->
    <div class="fieldset_client_area">
        <!--Client Body-->
    </div>
    <!--Client Area-->
    <!--Top Left Corner-->
    <div class="fieldset_ch fieldset_tlch">
        <div class="fieldset_c">
            <b class="fieldset_b_px b1"></b>
            <b class="fieldset_b_px b2"></b>
            <b class="fieldset_b_px b3"></b>
        </div>
    </div>
    <!--Top Left Corner-->
    <!--Top Right Corner-->
    <div class="fieldset_ch fieldset_trch">
        <div class="fieldset_c">
            <b class="fieldset_b_px b1"></b>
            <b class="fieldset_b_px b2"></b>
            <b class="fieldset_b_px b3"></b>
        </div>
    </div>
    <!--Top Right Corner-->
    <!--Bottom Left Corner-->
    <div class="fieldset_ch fieldset_blch">
        <div class="fieldset_c">
            <b class="fieldset_b_px b1"></b>
            <b class="fieldset_b_px b2"></b>
            <b class="fieldset_b_px b3"></b>
        </div>
    </div>
    <!--Bottom Left Corner-->
    <!--Bottom Right Corner-->
    <div class="fieldset_ch fieldset_brch">
        <div class="fieldset_c">
            <b class="fieldset_b_px b1"></b>
            <b class="fieldset_b_px b2"></b>
            <b class="fieldset_b_px b3"></b>
        </div>
    </div>
    <!--Bottom Right Corner-->
    <!--Legend-->
    <div class="fieldset_legend">
        General
    </div>
    <!--Legend-->
</div>
*/
////////////////////////////////////////////////////////////////////////////
// constructor
////////////////////////////////////////////////////////////////////////////
FieldSet = function()
{
    this.subclass = AP;
    this.subclass();
    
    this.className = "FieldSet";
}
FieldSet.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
FieldSet.prototype.create = function(parent, flags, htmLegend)
{
    this.subclass = AP.prototype.create;
    this.subclass(parent, "fieldset");
    this.flags = AP.is_set(flags, 0);
    
    // Client Area
    this.divClient = AP.createElement("div", this.element, "fieldset_client_area");
    
    // Corners
    this.corners = {};
        // Top Left
    this.corners.tl = {};
    this.corners.tl.holder = AP.createElement("div", this.element, "fieldset_ch fieldset_tlch");
    this.corners.tl.corner = AP.createElement("div", this.corners.tl.holder, "fieldset_c");
    this.corners.tl.b1 = AP.createElement("b", this.corners.tl.corner, "fieldset_b_px b1");
    this.corners.tl.b2 = AP.createElement("b", this.corners.tl.corner, "fieldset_b_px b2");
    this.corners.tl.b3 = AP.createElement("b", this.corners.tl.corner, "fieldset_b_px b3");
        // Top Right
    this.corners.tr = {};
    this.corners.tr.holder = AP.createElement("div", this.element, "fieldset_ch fieldset_trch");
    this.corners.tr.corner = AP.createElement("div", this.corners.tr.holder, "fieldset_c");
    this.corners.tr.b1 = AP.createElement("b", this.corners.tr.corner, "fieldset_b_px b1");
    this.corners.tr.b2 = AP.createElement("b", this.corners.tr.corner, "fieldset_b_px b2");
    this.corners.tr.b3 = AP.createElement("b", this.corners.tr.corner, "fieldset_b_px b3");
        // Bottom Left
    this.corners.bl = {};
    this.corners.bl.holder = AP.createElement("div", this.element, "fieldset_ch fieldset_blch");
    this.corners.bl.corner = AP.createElement("div", this.corners.bl.holder, "fieldset_c");
    this.corners.bl.b1 = AP.createElement("b", this.corners.bl.corner, "fieldset_b_px b1");
    this.corners.bl.b2 = AP.createElement("b", this.corners.bl.corner, "fieldset_b_px b2");
    this.corners.bl.b3 = AP.createElement("b", this.corners.bl.corner, "fieldset_b_px b3");
        // Bottom Right
    this.corners.br = {};
    this.corners.br.holder = AP.createElement("div", this.element, "fieldset_ch fieldset_brch");
    this.corners.br.corner = AP.createElement("div", this.corners.br.holder, "fieldset_c");
    this.corners.br.b1 = AP.createElement("b", this.corners.br.corner, "fieldset_b_px b1");
    this.corners.br.b2 = AP.createElement("b", this.corners.br.corner, "fieldset_b_px b2");
    this.corners.br.b3 = AP.createElement("b", this.corners.br.corner, "fieldset_b_px b3");
    
    // Legend
    this.divLegend = AP.createElement("div", this.element, "fieldset_legend");
    this.legend(htmLegend);
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
FieldSet.prototype.setPosition = function(pos)
{
    pos.flags = AP.is_set(pos.flags, 0);
    this.subclass = AP.prototype.setPosition;
    this.subclass(pos);
    
    if ( AP.is_set(pos.cy) ||
         pos.flags & POS_FLAGS_RECALCULATE )
    {
        var clientHeight = AP.clientHeight(this.element);
        if ( clientHeight > 0 )
        {
            AP.setPosition(this.divClient, {
                cy: clientHeight
            });
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// text - get/set legend
////////////////////////////////////////////////////////////////////////////
FieldSet.prototype.legend = function(htmLegend)
{
    if ( AP.is_set(htmLegend) )
    {
        this.divLegend.innerHTML = htmLegend;
    }
    
    return this.divLegend.innerHTML;
}

////////////////////////////////////////////////////////////////////////////
// getClientArea
////////////////////////////////////////////////////////////////////////////
FieldSet.prototype.getClientArea = function()
{
    return this.divClient;
}

////////////////////////////////////////////////////////////////////////////
// Calendar extends AP
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// Calendar DOM
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// constructor
////////////////////////////////////////////////////////////////////////////
Calendar = function()
{
    this.subclass = AP;
    this.subclass();
    
    this.className = "Calendar";
}
Calendar.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
Calendar.prototype.create = function(parent)
{
    this.subclass = AP.prototype.create;
    this.subclass(parent, "calendar");
}

////////////////////////////////////////////////////////////////////////////
// value
////////////////////////////////////////////////////////////////////////////
Calendar.prototype.value = function(value)
{
    if ( AP.is_set(value) !== false )
    {
    }
    return "";
}

////////////////////////////////////////////////////////////////////////////
// DateRange extends AP
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// DateRange DOM
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// constructor
////////////////////////////////////////////////////////////////////////////
DateRange = function()
{
    this.subclass = AP;
    this.subclass();
    
    this.cbMode = new ComboBox();
    this.inFrom = new ImgInput();
    this.inTo = new ImgInput();
    this.ctrlDateFrom = null;
    this.ctrlDateTo = null;
    this.className = "DateRange";
}
DateRange.prototype = new AP;

////////////////////////////////////////////////////////////////////////////
// create
////////////////////////////////////////////////////////////////////////////
DateRange.prototype.create = function(parent)
{
    this.subclass = AP.prototype.create;
    this.subclass(parent, "date_range");
    
    this.cbMode.create(this.element, "", CB_FLAGS_DROPDOWNLIST | CB_FLAGS_REPLACEIMAGE);
    this.cbMode.addNotifyHandler( [DateRange.onCotrolsNotify, this] );
    
    this.cbMode.appendItem({
        text: "Any",
        value: "any"
    });
    this.cbMode.appendItem({
        text: "Today",
        value: 0
    });
    this.cbMode.appendItem({
        text: "Last 7 days",
        value: 7
    });
    this.cbMode.appendItem({
        text: "Last 30 days",
        value: 30
    });
    this.cbMode.appendItem({
        text: "Date Range",
        value: "range"
    });
    
    this.inFrom.create(this.element, "", IMGIN_FLAGS_TEXT | IMGIN_FLAGS_READONLY);
    this.inTo.create(this.element, "", IMGIN_FLAGS_TEXT | IMGIN_FLAGS_READONLY);

    this.value({
        value: "any",
        from: "",
        to: ""
    });

    this.ctrlDateFrom = new Epoch("", "popup", this.inFrom.input);
    this.ctrlDateTo = new Epoch("", "popup", this.inTo.input);
}

////////////////////////////////////////////////////////////////////////////
// destroy
////////////////////////////////////////////////////////////////////////////
DateRange.prototype.destroy = function()
{
    this.ctrlDateFrom.calendar.parentNode.removeChild(this.ctrlDateFrom.calendar);
    this.ctrlDateFrom = null;
    this.ctrlDateTo.calendar.parentNode.removeChild(this.ctrlDateTo.calendar);
    this.ctrlDateTo = null;

    this.subclass = AP.prototype.destroy;
    this.subclass();
}

////////////////////////////////////////////////////////////////////////////
// setPosition
////////////////////////////////////////////////////////////////////////////
DateRange.prototype.setPosition = function(pos)
{
    pos.cy = 50;
    this.subclass = AP.prototype.setPosition;
    this.subclass(pos);
    
    if ( AP.is_set(pos.cx) ||
         pos.flags & POS_FLAGS_RECALCULATE )
    {
        var clientWidth = AP.clientWidth(this.element);
        this.cbMode.setPosition({
            x: 0,
            y: 0,
            cx: clientWidth
        });
        
        this.inFrom.setPosition({
            x: 0,
            y: 25,
            cx: Math.floor( (clientWidth - 2) / 2 )
        });
        this.inTo.setPosition({
            x: Math.ceil(clientWidth / 2),
            y: 25,
            cx: Math.floor( (clientWidth - 2) / 2 )
        });
    }
}

////////////////////////////////////////////////////////////////////////////
// value
////////////////////////////////////////////////////////////////////////////
/*
var value = {
    value: nDays,
    from: date,
    to: date
}
*/
DateRange.prototype.value = function(value)
{
    if ( AP.is_set(value) !== false )
    {
        this.inFrom.value(value.from);
        this.inTo.value(value.to);
        this.cbMode.value(value.value);
    }

    var retValue = {
        value: this.cbMode.value(),
        from: this.inFrom.value(),
        to: this.inTo.value()
    }
    
    return retValue;
}

////////////////////////////////////////////////////////////////////////////
// onCotrolsNotify
////////////////////////////////////////////////////////////////////////////
DateRange.onCotrolsNotify = function(param)
{
    var pObj = param.producer;
    var pThis = param.subscriber;
    
    if ( pObj == pThis.cbMode )
    {
        if ( param.code == CBN_ONCHANGED )
        {
            var value = pObj.value();
            pThis.inFrom.enable( value == "range" ? true : false );
            pThis.inTo.enable( value == "range" ? true : false );

            if ( value == "any" )
            {
                // TODO:
                // from - 1970
                // to - current
                var dateCurr = new Date();
                var date1970 = new Date("01/01/1971");

                pThis.inTo.value(dateCurr.dateFormat("m/d/Y"));
                pThis.inFrom.value(date1970.dateFormat("m/d/Y"));
            }
            else if ( value == "range" )
            {
                // do nothing
            }
            else
            {
                // TODO:
                // to = current
                // from = current - day*value
                var dateCurr = new Date((new Date()).dateFormat("m/d/Y"));
                var dateTSS = dateCurr.valueOf() - 1000*60*60*24*value;
                dateTSS = new Date(dateTSS);
                pThis.inTo.value(dateCurr.dateFormat("m/d/Y"));
                pThis.inFrom.value(dateTSS.dateFormat("m/d/Y"));
            }
        }
    }
    
    return 0;
}
