/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) 2004-2010 Open-Xchange, Inc.
 * Mail: info@open-xchange.com 
 * 
 * @author Viktor Pracht <viktor.pracht@open-xchange.com>
 */

function centerPopupWindow(popupwindow, width, height) {

	var center = function () {
	    // get available width & height
	    var availWidth = jQuery(window).width();
	    var availHeight = jQuery(window).height();
	    // center
	    jQuery(popupwindow).css({
    		left: Math.floor((availWidth - width) / 2) + "px",
    		top: Math.floor((availHeight - height) / 2) + "px"
    	});
	};
	
	// show
	jQuery(popupwindow).css("visibility", "hidden").show();
	
	// get width & height
	var content = jQuery(".popup_center_area", popupwindow);
	
	// get width
	if (width === undefined) {
	    width = content.children().width();
	}
    
	// get height
	if (height === undefined) {
        // getting the height of all elements for the final maximum height
        height = 0;
        //height = ontent.children().height();
        content.children().each(function(index) {    
        	height = height + jQuery(this).height();
        });
	}
    
    // adjust outer element
    jQuery(popupwindow).css({ width: width + "px", height: height + 0 + "px" });
    // center outer element
	center();
	// hide
	jQuery(popupwindow).hide().css("visibility", "visible");
	
	// bind resize event
	// deactivated by ben. some folder popups should stay the way they popup and don't move around after a resize
	// the left/top cords are set in the given classes
	//jQuery(window).bind("resize", center);
}

register("Loaded", function() {
    addDOMEvent($("body"), "contextmenu", function(e) {
        switch ((e.target || e.srcElement).nodeName.toLowerCase()) {
            case "textarea":
            case "input":
            case "a":
                break;
            default:
                stopEvent(e);
                return false;
        }
    });
    for (var i in init.movable) {
        var node = $(init.movable[i]);
		var parent = $(node.id.substring(0, node.id.length-2));
		(function(node, parent) {
            addDOMEvent(node, "mousedown", function(e) {
                if (e.button != LeftButton) return;
                moving = parent;
                offsetX = parent.offsetLeft - e.clientX;
                offsetY = parent.offsetTop - e.clientY;
                parent.style.cursor = "move";
                function moveHandler(e) {
                    var topposition=(e.clientY + offsetY);
                    var leftposition=(e.clientX + offsetX);
                    if(topposition<0) {
                        topposition=0;
                    }
                    else if(topposition+parent.offsetHeight > $("body").offsetHeight) {
                        topposition=$("body").offsetHeight-parent.offsetHeight;
                    }
                    if(leftposition<0) {
                        leftposition=0;
                    }
                    else if(leftposition+parent.offsetWidth > $("body").offsetWidth) {
                        leftposition=$("body").offsetWidth-parent.offsetWidth;
                    }
                    moving.style.left = leftposition + "px";
                    moving.style.top = topposition + "px";
                    stopEvent(e);
                }
                function upHandler(e) {
                    if (moving && e.button == LeftButton) {
                        moving = null;
                        parent.style.cursor = "auto";
                        removeDOMEvent(body, "mousemove", moveHandler);
                        removeDOMEvent(body, "mouseup", upHandler);
                        //removeDOMEvent(body, "mouseout", upHandler);
                        stopEvent(e);
                    }
                }
                addDOMEvent(body, "mousemove", moveHandler);
                addDOMEvent(body, "mouseup", upHandler);
                //addDOMEvent(body, "mouseout", upHandler);
                cancelDefault(e);
            });
            
            addDOMEvent(node.firstChild, "click", function() {
            	parent.style.display = "none";
	        });

        })(node, parent);
    }
});

/**
 * Adds a DOM event listener to the onclick event of the "Close" icon in the
 * right top corner of a popup window.
 * @param {Object} popupwindow The DOM node of the popup window.
 * @param {Function} onclose The callback function which will be called when
 * the popup window is closed by clicking on the "Close" icon.
 */
function addOnClose(popupwindow, onclose) {
	addDOMEvent(popupwindow.getElementsByTagName('img')[1], "click", onclose);
}

/**
 * Removes a previously added DOM event listener from the onclick event of
 * the "Close" icon in the rihgt top corner of a popup window.
 * @param {Object} popupwindow The DOM node of the popup window.
 * @param {Function} onclose The callback function which was supplied in
 * a previous call to {@link addOnClose}.
 */
function removeOnClose(popupwindow, onclose) {
    removeDOMEvent(getElement(popupwindow.firstChild).firstChild, "click", onclose);
}

/**
 * @class The common class for all popups
 * @param {DOMNode} node The DOM node which will be displayed and hidden.
 * The opacity effect works only if the node is positioned by CSS.
 */
function Popup(node) {
    this.node = node;
    body.appendChild(this.node);
}

/** @private */
Popup.stopAnimation = null;

Popup.prototype = {
    show: function() {
        if (this.isVisible) return;
        this.isVisible = true;
        if (Popup.stopAnimation) Popup.stopAnimation();
        var style = this.node.style;
        if(configGetKey("gui.effects.fading")) {
            Popup.stopAnimation = animate(300, 1,
                function(value) {
                    style.opacity = value;
                    style.filter = "alpha(opacity=" + value * 100 + ")";
                }, function() {
                    style.opacity = "";
                    style.filter = "";
                    Popup.stopAnimation = null;
                });
        }
        style.display = "";
        hideIFrames();
    },
    
    hide: function() {
        if (!this.isVisible) return;
        this.isVisible = false;
        if (Popup.stopAnimation) Popup.stopAnimation();
        this.node.style.display = "none";
        showIFrames();
    },
    
    /**
     * Indicates whether the popup is currently displayed.
     */
    isVisible: false,
    
    /**
     * Positions the popup so that it is fully visible. By default, the popup is
     * positioned with its top left corner at the specified desired coordinates.
     * If the popup would not be fully visible because it would extend beyond
     * the right or the bottom edge of the browser window, the right or
     * the bottom edge of the popup is placed at the specified coordinates
     * instead of the left or the top edge, respectively.
     * All coordinates are given in px relative to the document body.
     * @param {Number} x The preferred X coordinate of the left popup edge.
     * @param {Number} y The preferred Y coordinate of the top popup edge.
     * @param {Number} w The distance to add to the desired X coordinate if
     * the right popup edge is used instead of the left edge. Defaults to 0.
     * A positive value will keep a rectangle at x to x + w always covered,
     * a negative value will keep a rectangle at x + w to x always cleared.
     * @param {Number} h The distance to add to the desired Y coordinate if
     * the bottom popup edge is used instead of the top edge. Defaults to 0.
     * A positive value will keep a rectangle at y to y + h always covered,
     * a negative value will keep a rectangle at y + h to y always cleared.
     */
    position: function (x, y, w, h) {
        // prepare
        var $ = jQuery, node = $(this.node), width = node.width(), height = node.height();
        // x
        if (x + width > $(document.body).width()) {
            x = x + (w || 0) - width;
        }
        // y
        if (y + height > $(document.body).height()) {
            y = y + (h || 0) - height;
        }
        // set new position
        node.css({
            left: x + "px",
            top: y + "px"
        });
    }
};

/**
 * @class The common class for all popups which disappear when the user clicks
 * somewhere else.
 * @param {DOMNode} node The DOM node which will be displayed and hidden.
 * @augments Popup
 */
function MousePopup(node) {
    Popup.call(this, node);
    var Self = this;
    if (document.addEventListener) {
        this.clickHandler = function(e) {
            if (Self.captureEvents(e.target)) stopEvent(e);
        };
    } else {
        var recursion = {};
        this.clickHandler = function(e) {
            if (recursion[e.type]) return;
            if (!Self.captureEvents(e.srcElement)) Self.mouseHandler(e);
        };
        this.mouseHandler = function(e) {
            if (recursion[e.type]) return;
            recursion[e.type] = true;
            if (MousePopup.active) {
                body.onlosecapture = null;
                body.releaseCapture();
            }
            switch (e.type) {
                case "click":
                    e.srcElement.click();
                    break;
                default:
                    e.srcElement.fireEvent("on" + e.type);
            }
            if (MousePopup.active) {
                body.onlosecapture = MousePopup.hideActive;
                body.setCapture();
            }
            recursion[e.type] = false;
        };
    }
}

/**
 * The number of currently displayed MousePopups.
 * @type Number
 */
MousePopup.active = 0;

MousePopup.prototype = extend(Popup, {

    show: function() {
        Popup.prototype.show.call(this);
        if (!MousePopup.active && "Hover" in window && Hover.current)
            Hover.current.hide();
        this.registerHide();
        MousePopup.active++;
    },

    hide: function() {
        // check if the pop-up is visible. otherwise MousePopup.active gets into
        // an invalid state and hovers do not work any longer
        if (this.isVisible) {
            Popup.prototype.hide.call(this);
            MousePopup.active--;
            this.unregisterHide();
        }
    },

    /**
     * Decides what to do with captured events while the popup menu is
     * displayed.
     * @param {DOMNode} t the target of the captured event.
     * @type Boolean
     * @return Whether to prevent the bubbling and the default action of
     * the event.
     */
    captureEvents: function(t) {
        while (t && !t.oxPopupMenu) t = t.parentNode;
        if (!t) this.hide();
        return false;
    }

});

if (document.addEventListener) {
    MousePopup.prototype.registerHide = function() {
        body.parentNode.addEventListener("click", this.clickHandler, true);
        body.parentNode.addEventListener("contextmenu", this.clickHandler,
            true);
        body.parentNode.addEventListener("mousedown", this.clickHandler, true);
    };
    MousePopup.prototype.unregisterHide = function() {
        body.parentNode.removeEventListener("click", this.clickHandler, true);
        body.parentNode.removeEventListener("contextmenu", this.clickHandler,
            true);
        body.parentNode.removeEventListener("mousedown", this.clickHandler,
            true);
    };
} else {
    MousePopup.activePopups = {};
    MousePopup.nextID = 0;
    MousePopup.hideActive = function() {
        for (var i in MousePopup.activePopups)
            MousePopup.activePopups[i].hide();
    };
    MousePopup.prototype.registerHide = function() {
        if (!MousePopup.active) {
            addDOMEvent(body, "click", this.clickHandler);
            addDOMEvent(body, "mousedown", this.clickHandler);
            addDOMEvent(body, "mouseover", this.mouseHandler);
            addDOMEvent(body, "mouseout", this.mouseHandler);
            body.onlosecapture = MousePopup.hideActive;
            body.setCapture();
        }
        if (!this.activeID) this.activeID = ++MousePopup.nextID;
        MousePopup.activePopups[this.activeID] = this;
    };
    MousePopup.prototype.unregisterHide = function() {
        if (!MousePopup.active) {
            removeDOMEvent(body, "click", this.clickHandler);
            removeDOMEvent(body, "mousedown", this.clickHandler);
            removeDOMEvent(body, "mouseover", this.mouseHandler);
            removeDOMEvent(body, "mouseout", this.mouseHandler);
            body.onlosecapture = null;
            body.releaseCapture();
        }
        delete MousePopup.activePopups[this.activeID];
    };
}

/**
 * @class The common class for all popup menus.
 * @augments Popup
 */
function PopupMenu() {
    MousePopup.call(this, newnode("div", {
        position: "absolute",
        display: "none"
    }, {
        className: "PopupMenuDiv background-color-popup-menu border-color-popup-menu font-size-small",
        oxPopupMenu: true
    }));
    this.items = [];
}

/**
 * Delay for displaying and hiding of sub-menus, in milliseconds.
 * @type Number
 * @default 100
 */
PopupMenu.timeout = 100;

/**
 * @private
 */
PopupMenu.getTimeout = function() { return PopupMenu.timeout; };

/**
 * An optional callback which is called when the menu is displayed.
 * It can be used to adjust the menu by disabling or hiding certain menu items.
 * @function
 * @name PopupMenu.prototype.onShow
 */

/**
 * An optional callback which is called when the menu is hidden.
 * It can be used to release any resources acquired in onShow.
 * @function
 * @name PopupMenu.prototype.onHide
 */

PopupMenu.prototype = extend(MousePopup, {
    /**
     * Displays the popup menu.
     * @protected
     */
    show: function() {
        if (this.isVisible) {
            return;
        }
        if (this.onShow) this.onShow(this.getContext());
        MousePopup.prototype.show.call(this);
        for (var i in this.items) this.items[i].show();
        PopupMenu.last = this;
    },
    
    hide: function() {
        if (!this.isVisible) return;
        MousePopup.prototype.hide.call(this);
        for (var i in this.items) this.items[i].hide();
        if (this.onHide) this.onHide();
    },
    
    close: function() {
        PopupMenu.last = null;
        this.hide();
        if (this.parent instanceof MenuItem) this.parent.close();
    },
    
    addItem: function(item) {
        this.items.push(item);
        this.node.appendChild(item.node);
        item.parent = this;
        if (item.icon) this.iconAdded();
        if (item.submenu) this.submenuAdded();
    },
    
    removeItem: function(item) {
        for (var i = 0; i < this.items.length; i++) {
            if (this.items[i] == item) {
                this.items.splice(i, 1);
                this.node.removeChild(item.node);
                break;
            }
        }
    },
    
    removeAllItems: function() {
        for (var i = 0; i < this.items.length; i++) 
            this.node.removeChild(this.items[i].node);
        this.items = [];
    },
    
    itemSelected: function(e) {
        for (var i in this.items) this.items[i].siblingSelected(e);
    },
    
    /**
     * Returns the context of the popup menu.
     */
    getContext: function() {
        var p = this.parent;
        return p && p.getContext !== undefined ? p.getContext() : {};
    },
    
    /**
     * @ignore
     */
    iconAdded: function() { PopupMenu.setIconClass(this.node, true); },
    
    /**
     * @ignore
     */
    submenuAdded: function() { PopupMenu.setSubmenuClass(this.node, true); }
    
});

/**
 * @private
 */
PopupMenu.setIconClass = classNameSetter("popup-menu-icons");

/**
 * @private
 */
PopupMenu.setSubmenuClass = classNameSetter("popup-menu-submenus");

/**
 * @class A menu item for PopupMenu and its descendants.
 * @param {I18nString} text The text of the menu item.
 * @param {Function or PopupMenu} action Either a callback function which is
 * called when the menu item is activated, or a PopupMenu object which is
 * displayed as a submenu.
 */
function MenuItem(text, action) {
    if (action instanceof PopupMenu) {
        this.submenu = action;
        action.parent = this;
    } else
        this.action = action;
    var Self = this;
    this.node = newnode("div", { position: "relative" },
        { className: "PopupEntryDiv" },
        [newnode("span", 0, 0, [addTranslated(text)])]);
    addDOMEvent(this.node, "click", function(e) {
        if (Self.checkbox) {
            var target = e.target || e.srcElement;
            if (target == Self.checkbox) {
                cancelBubbling(e);
                handleClick();
            } else {
                stopEvent(e);
                Self.checkbox.click();
            }
        } else {
            stopEvent(e);
            handleClick();
        }
        function handleClick() {
            if (Self.enabled) {
                if (Self.action && Self.action(Self.getContext()) !== false)
                    Self.parent.close();
            } else {
                if (Self.disabled_action && Self.disabled_action() !== false)
                    Self.parent.close();
            }
        }
    });
    with (FSM) var transitions = [
        Trans("start", "selected", Event(this.node, "mouseover", select)),
        Trans("selected", "start", Event(this.node, "mouseout",
            MouseOut(deselect)))
    ];
    if (this.submenu) {
        with (FSM) {
            var deselection = Trans("both", "open", Callback(deselect));
            transitions = transitions.concat([
                Trans("selected", "both", Timeout(PopupMenu.getTimeout, open)),
                deselection,
                Trans("open", "both", Event(this.node, "mouseover", select)),
                Trans("open", "start", Timeout(PopupMenu.getTimeout, close))
            ]);
        }
        this.node.appendChild(newnode("img", IE < 8 ? 0 : {
            position: "absolute", right: 0, top: 0, bottom: 0, margin: "auto", width: "4px", height: "7px"
        }, { src: getFullImgSrc("/img/arrows/arrow_darkgrey_right.gif") }));
    }
    this.fsm = FSM("start", transitions);
    if (this.submenu) this.siblingSelected = deselection.fire;

    function select() {
        Self.parent.itemSelected();
        if (!Self.enabled) return false;
        Self.node.className =
            "PopupEntryDivActive border-color-PMG-selection-elements";
    }
    function deselect() { Self.node.className = "PopupEntryDiv"; }
    function open() {
        Self.submenu.show();
        var pos = getAbsolutePosition(Self.node);
        Self.submenu.position(pos.x + Self.node.offsetWidth, pos.y,
                              -Self.node.offsetWidth, Self.node.offsetHeight);
    }
    function close() { Self.submenu.hide(); }
}

MenuItem.prototype = {

    enabled: true,
    
    enable: function() { this.setEnabled(true); },
    
    disable: function() { this.setEnabled(false); },
    
    setEnabled: function(enabled) {
        this.enabled = enabled;
        this.node.className = this.enabled ? "PopupEntryDiv"
                                           : "PopupEntryDivDisabled font-color-disabled";
        if (this.img) this.img.src = this.getIcon();
    },
    
    siblingSelected: emptyFunction,
    
    setIcon: function(icon, disabled_icon) {
        this.icon = icon;
        this.disabled_icon = disabled_icon;
        if (icon) {
            if (this.img) {
                this.img.src = this.getIcon();
            } else {
                this.img = newnode("img", {
                    position: "absolute", left: 0, top: 0, bottom: 0,
                    margin: "auto"
                }, { src: this.getIcon() });
                this.node.insertBefore(this.img, this.node.firstChild);
            }
            if (this.parent) this.parent.iconAdded();
        } else if (this.img) {
            this.node.removeChild(this.img);
            delete this.img;
        }
        return this;
    },
    
    getIcon: function() {
        return getFullImgSrc(this.enabled ? this.icon
                                          : (this.disabled_icon || this.icon));
    },
    
    setChecked: function(checked) {
        if (this.checkbox) {
            this.checkbox.defaultChecked = checked;
            this.checkbox.checked = checked;
        } else {
            this.checkbox = newnode("input", 0,
                { type: "checkbox", className: "noborder", defaultChecked: checked, checked: checked });
            this.node.insertBefore(this.checkbox, this.node.firstChild);
        }
        return this;
    },
    
    getChecked: function() {
        return this.checkbox && this.checkbox.checked;
    },
    
    show: function() {
        if (this.onShow) this.onShow(this.getContext());
        this.fsm.reset();
        this.node.className = this.enabled ? "PopupEntryDiv"
                                           : "PopupEntryDivDisabled font-color-disabled";
        if (this.img) this.img.src = this.getIcon();
    },

    hide: function() {
        if (this.submenu) this.submenu.hide();
        this.fsm.disable();
    },
    
    close: function() { this.parent.close(); },
    
    visible: true,
    
    setVisible: function(visible) {
        this.visible = visible;
        this.node.style.display = visible ? "" : "none";
    },
    
    /**
     * The current waiting status.
     * @type Boolean
     */
    waiting: false,
    
    /**
     * Sets the waiting status.
     * When waiting, the menu item is shown as disabled, with an animated icon.
     * @param {Boolean} waiting The new waiting status.
     */
    setWaiting: function(waiting) {
        if (waiting == this.waiting) return;
        this.waiting = waiting;
        if (waiting) {
            this.waitEnabled = this.enabled;
            this.setEnabled(false);
            this.waitSetEnabled = this.setEnabled;
            this.setEnabled = function(enabled) { this.waitEnabled = enabled; };
            this.waitIcon = this.icon;
            this.waitDisabledIcon = this.disabled_icon;
            this.setIcon("img/ox_animated_withoutbg.gif");
        } else {
            this.setEnabled = this.waitSetEnabled;
            this.setEnabled(this.waitEnabled);
            this.setIcon(this.waitIcon, this.waitDisabledIcon);
            delete this.waitEnabled;
            delete this.waitSetEnabled;
            delete this.waitIcon;
            delete this.waitDisabledIcon;
        }
    },
    
    /**
     * Configures the menu item to automatically enter the waiting state when
     * the enabled status cannot be computed from
     * @param {Function} enableFunction A function which computes the enabled
     * state of this menu item from Value objects.
     */
    setEnabledF: function(enableFunction) {
        var Self = this;
        Value.eval(enableFunction, function(wait, enable) {
            if (!wait) Self.setEnabled(enable);
            Self.setWaiting(wait);
        });
    },

    /**
     * Automatically disables the menu item if its submenus do not have a single
     * enabled option.
     */
    autoSetEnabled: function() {
        this.getAutoEnabled(function (flag) {
            this.setEnabled(flag);
        });
    },
    
    // use continuation since it might be async
    getAutoEnabled: function (cont) {
        // has no sub menu?
        if (!this.submenu) {
            cont(this.enabled && this.visible);
        } else {
            // flag
            var found = false;
            // join
            var join = new Join(function () {
                cont(found);
            });
            var lock = join.add();
            // loop over items
            var i = 0, items = this.submenu.items, $l = items.length, item, callback;
            for (; i < $l; i++) {
                // supports auto enabled?
                if (items[i].getAutoEnabled) {
                    // closure
                    (function (item) {
                        // call back
                        item.getAutoEnabled(join.add(function (flag) {
                            found = found || flag;
                        }));
                    }(items[i]));
                }
            }
            lock();
        }
    },
    
    /**
     * Returns the context in which this item is used. Usually, this is
     * the current selection for which a context menu is displayed.
     */
    getContext: function() {
        var p = this.parent;
        return p && p.getContext !== undefined ? p.getContext() : {};
    }
};

/**
 * Creates a new dynamic value.
 * A dynamic value is a special object which works like a variable which
 * contains the results of an asynchronous request and automatically updates
 * calculations in which it is used when the request completes. The reason for
 * using dynamic values instead of the Join class is that the results may be
 * computed before the last request completes because some values are not needed
 * e. g. (a && b) is false when a is false, regardless of the value of b.
 *
 * New dynamic values are created by calling the function Value():
 * <code>var x = Value(), y = Value();</code>
 * They can then be used in calculations by appending "()" to the variable
 * names:
 * <code>function calculation() { return x() > 3 && y() < 9; }</code>
 * To enable automatic updates, the calculation function and a callback function
 * which reacts to updates, are passed to Value.eval():
 * <code>Value.eval(calculation, function(wait, result) { ... });</code>
 * The callback function takes two parameters. The first parameter (wait) is
 * true if the calculation could not be completed because some required dynamic
 * values didn't receive their values yet. Otherwise, if the first parameter is
 * false, then the second parameter (result) contains the result of
 * the calculation.
 *
 * When a dynamic value becomes outdated, e. g. just before a new request is
 * started to receive the new value, the old value must be cleared by calling
 * the clear() method. When the request completes, the new value is set by
 * calling the set() method:
 * <code>
 * x.clear();
 * ox.JSON.get(..., function(data) { x.set(...); });
 * </code>
 */
function Value() {
    var f = function() {
        var e = Value.evaluations[Value.evaluations.length - 1];
        f.callbacks[e.id] = e.callback;
        if ("value" in f) return f.value; else throw Value.WaitException;
    };
    f.callbacks = {};
    f.set = function(value) {
        if (this.value == value) return;
        this.value = value;
        this.update();
    };
    f.clear = function() {
        delete this.value;
        this.update();
    };
    f.update = function() {
        var callbacks = this.callbacks;
        this.callbacks = {};
        for (var i in callbacks) callbacks[i]();
    };
    return f;
};

Value.WaitException = {};

Value.id = 0;

Value.evaluations = [];

/**
 * Creates an automatically updated calculation.
 * @param {Function} f A calculation function which uses dynamic values.
 * @param {Function} callback A callback function which is called with one or
 * two parameters: The first parameter is true when f throws Value.WaitException
 * (usually a result of attempting to evaluate an empty dynamic value).
 * The second parameter contains the return value of f and is present only when
 * the first parameter is false.
 */
Value.eval = function(f, callback) {
    var updatePending = false;
    function delayUpdate() {
        if (!updatePending) {
            updatePending = true;
            setTimeout(update, 0);
        }
    }
    function update() {
        updatePending = false;
        Value.evaluations.push({ id: Value.id++, callback: delayUpdate });
        try {
            var result = f();
            Value.evaluations.pop();
            callback(false, result);
        } catch (e) {
            Value.evaluations.pop();
            if (e == Value.WaitException) callback(true); else throw e;
        }
    }
    update();
};

/**
 * A separator for popup menus. Can be used as a MenuItem most of the time, but
 * the only available method is setVisible.
 */
function MenuSeparator() {
    this.node = newnode("div",
        { border: "1px inset gray", margin: "0.6em 0" });
    var Self = this;
    addDOMEvent(this.node, "mouseover",
        function() { Self.parent.itemSelected(); });
}

MenuSeparator.prototype = {
    siblingSelected: emptyFunction,
    show: emptyFunction,
    hide: emptyFunction,
    setVisible: function(visible) {
        this.visible = visible;
        this.node.style.display = visible ? "" : "none";
    }
};

/**
 * @augments PopupMenu
 */
function ContextMenu() { PopupMenu.call(this); }

ContextMenu.prototype = extend(PopupMenu, {

    /**
     * Displays the context menu at the specified coordinates.
     * @param {Number} x The X coordinate of the menu (usually Event.clientX).
     * @param {Number} y The Y coordinate of the menu (usually Event.clientY).
     * @param context The context which is passed to all children of the menu.
     */
    display: function (x, y, context) {
        this.context = context;
        this.position(x, y);
        this.show();
    },
    
    getContext: function () {
        return this.context;
    }
});

/**
 * @augments PopupMenu
 * @param {DOMNode} trigger The DOM node which will trigger the display of
 * the menu. The menu is displayed below the DOM node.
 */
function PulldownMenu(trigger) {
    PopupMenu.call(this);
    this.trigger = trigger;
    var Self = this;
    addDOMEvent(trigger, "click", function(e) {
        if (PopupMenu.last == Self) PopupMenu.last = null; else Self.show();
        cancelBubbling(e);
    });
    trigger.oxPulldownTrigger = true;
}

PulldownMenu.prototype = extend(PopupMenu, {

    show: function() {
        this.node.style.minWidth = this.trigger.offsetWidth + "px";
        PopupMenu.prototype.show.call(this);
        var pos = getAbsolutePosition(this.trigger);
        this.position(pos.x, pos.y + this.trigger.offsetHeight,
                      this.trigger.offsetWidth, -this.trigger.offsetHeight);
    },
    
    captureEvents: function(t) {
        while (t && !t.oxPopupMenu && !t.oxPulldownTrigger) t = t.parentNode;
        if (!t || t == this.trigger) {
            this.hide();
            return true;
        } else if (t.oxPulldownTrigger) {
            this.hide();
        }
        return false;
    }

});
