/* Filename: contextMenu.js
 * This file is part of the gRaDF Semantic Web browsing tool.
 * Copyright (C) 2006  Marcus Cobden
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 * (or see the file gpl.txt)
 * 
 * 	Description:
 * This file contains a set of utility classes to create and display custom context menus.
 * 
 * 	Classes:
 * ContextMenu
 * ContextMenuList
 * ContextMenuItem
 * ContextMenuNestItem
 *
 * 	Version information:
 * $HeadURL: svn+ssh://mc1204@svn.ugforge.ecs.soton.ac.uk/projects/grdf/code/branches/serverside/js/contextMenu.js $
 * $Date: 2007-06-21 08:38:10 -0700 (Thu, 21 Jun 2007) $
 * $Author: mc1204 $
 * $Rev: 134 $
 */

/**
 *  ContextMenu
 * Root class for every context menu. Handles the events,
 * timeouts and display of all the sub sections
 * 
 * @param MenuLists subsections of the menu. Usually instances of ContextMenuList
 */
function ContextMenu (menuLists) {
	
	var div = document.createElement('div');
	div.setAttribute('class','contextMenu');
	
	for (i in menuLists)
		div.appendChild(menuLists[i].dom);
		
	div.callback = this;
	div.addEventListener('mouseover', this.mouseListener, false);
	div.addEventListener('mouseout', this.mouseListener, false);
	// Happens after the click events of any child nodes propagates down, so removes the menu
	div.addEventListener('click', this.hideEvent, false);
	div.addEventListener('contextmenu', function(event){
		event.preventDefault();	
		event.returnValue = false; // Not nessecary?
		event.stopPropagation();
		return false;
	}, false);
	
	this.dom = div;
	this.child = menuLists;
	this.state = false;
	this.timeoutID = null;
	this.opacitySteps = [];
	for (i =0; i <= 10; i++)
		this.opacitySteps[i] = Math.sqrt(1 - ((i -10)/10)*((i -10)/10));
		
	// 0 == hidden
	this.opacity = 0;
	// 0 == stop
	this.opacityDirection = 0;
	this.opacityTimer = null;
	
	return this;
}

/**
 * show
 * Displays the ContextMenu
 * 
 * @param event Mouse event which triggered this context menu.
 */
ContextMenu.prototype.show = function(event) {
	if (this.timeoutID != null)
		window.clearTimeout(this.timeoutID);

	var x = event.clientX;
	var y = event.clientY;
	this.target = event.currentTarget;
	window.clearInterval(this.opacityTimer);
	this.dom.style.setProperty('opacity','0', null);
	
	for (i in this.child){
		this.child[i].show();
	}
	
	document.body.appendChild(this.dom);

	if (this.dom.offsetWidth + x > window.innerWidth)
		x -= this.dom.offsetWidth +4;
	if (this.dom.offsetHeight + y > window.innerHeight)
		y -= this.dom.offsetHeight +4;

	this.dom.style.setProperty('left', x + 'px', null);
	this.dom.style.setProperty('top', y + 'px', null);
	
	// Appear
	this.dom.style.removeProperty('opacity');
	this.opacity = this.opacitySteps.length;
	
	// Fade in
	// this.opacity = 0;
	// this.opacityDirection = 1;
	// this.opacityTimer = window.setInterval(this.doFade,10, this);
	
	this.timeoutID = window.setTimeout(this.hideEvent,2000, this);

	// Stop propagation
	event.preventDefault();	
	event.returnValue = false; // Not nessecary?
	event.stopPropagation();
	return false;
};

ContextMenu.prototype.doFade = function(menu) {
	if (menu.opacityDirection > 0) {
		if (menu.opacity < menu.opacitySteps.length){
			menu.opacity += menu.opacityDirection;
			menu.dom.style.setProperty('opacity',menu.opacitySteps[menu.opacity -1], null);
		} else {
			menu.opacityDirection = 0;
			window.clearInterval(menu.opacityTimer);
		}
	} else if (menu.opacityDirection < 0) {
		if (menu.opacity > 1){			
			menu.opacity += menu.opacityDirection;
			menu.dom.style.setProperty('opacity',menu.opacitySteps[menu.opacity -1], null);
		} else {
			menu.opacityDirection = 0;
			window.clearInterval(menu.opacityTimer);
			document.body.removeChild(menu.dom);
		}
	} else if (menu.opacityDirection == 0) {
		window.clearInterval(menu.opacityTimer);
	}
};

/**
 * mouseListener
 * Keeps track of whether the mouse is inside the ContextMenu or not
 * 
 * @param event Event from the trigger. Either a mouseover or mouseout event.
 */
ContextMenu.prototype.mouseListener = function (event) {
	event.currentTarget.callback.state = (event.type == 'mouseover');
	if (event.type == 'mouseout' && event.currentTarget.callback.timeoutID == null){
		event.currentTarget.callback.timeoutID = window.setTimeout(event.currentTarget.callback.hideEvent, 750, this.callback);
	}
};

/**
 * hideEvent
 * decides if the ContextMenu should hide. triggered either by a timeout or a mouse click
 * 
 * @param arg Either a reference to the ContextMenu object, or an Event from a mouse click
 */
ContextMenu.prototype.hideEvent = function (arg) {
	var menu;
	
	// if this happens from a click event rather than a timeout
	if (arg.type != null){
		menu = arg.currentTarget.callback;
	} else {
		menu = arg;
	}
	
	// Mouse outside (from the timeout event above)
	if (! menu.state){
		// Incase an event has already removed the menu
		// if(menu.timeoutID != null){
		// 		window.clearInterval(menu.opacityTimer);
		// 		menu.opacity = menu.opacitySteps.length;
		// 		menu.opacityDirection = -1;
		// 		menu.opacityTimer = window.setInterval(menu.doFade,10, menu);
		// 	}
		if(menu.timeoutID != null){
			window.clearTimeout(menu.timeoutID);
			window.clearInterval(menu.opacityTimer);
			document.body.removeChild(menu.dom);
		}
	} else if (this != window) { // When it happens from a click event not a timeout
		// Clear the timers
		window.clearTimeout(menu.timeoutID);
		window.clearInterval(menu.opacityTimer);
		document.body.removeChild(menu.dom);
	}
	menu.timeoutID = null;
};

/**
 *  ContextMenuList
 * Contains a set of menu options. Every context menu will contain atleast one of these.
 * 
 * @param menuItems Menu items with which to populate this list. Ususally instances of either ContextMenuItem or ContextMenuNestItem
 */
function ContextMenuList (menuItems) {
	var list = document.createElement('ul');
	
	for (i in menuItems){
		list.appendChild(menuItems[i].dom);
	}
	this.child = menuItems;
	this.dom = list;
	this.visible = true;
	
	return this;
}

/**
 * setVisible
 * Set whether this item is visible or not.
 * 
 * @param vis boolean. 
 */
ContextMenuList.prototype.setVisible = function(vis) {
	this.visible = vis;
};

/**
 * getVisible
 * 
 * @return boolean. Whether this item is visible or not
 */
ContextMenuList.prototype.getVisible = function() {
	if(!this.visible)
		return false;
	
	var vis = false;
	for (i in this.child){
		vis = vis || this.child[i].getVisible();
		if(vis)
			break;
	}
	return vis;
};

/**
 * show
 * 
 * Set this item to be visible
 */
ContextMenuList.prototype.show = function() {
	if(! this.getVisible()){
		this.dom.style.display = 'none';
		this.dom.setAttribute('class','hidden');
	} else {
		this.dom.style.display = '';
		this.dom.removeAttribute('class');
		for (i in this.child){
			this.child[i].show();
		}
	}
};

function ContextMenuItem (text, clickEvent, classes) {
	var item = document.createElement('li');
	if (text != null)
		item.appendChild(document.createTextNode(text));
	if (clickEvent != null)
		item.addEventListener('click', clickEvent, false);
	if (classes != null)
		item.setAttribute('class', classes);
	
	this.dom = item;
	this.visible = true;
	return this;
}

/**
 * setVisible
 * Set whether this item is visible or not.
 * 
 * @param vis boolean. 
 */
ContextMenuItem.prototype.setVisible = function(vis) {
	this.visible = vis;
};

/**
 * getVisible
 * 
 * @return boolean. Whether this item is visible or not
 */

ContextMenuItem.prototype.getVisible = function() {
	return this.visible;
};

/**
 * show
 * 
 * Set this item to be visible
 */
ContextMenuItem.prototype.show = function() {
	if(! this.visible){
		this.dom.style.display = 'none';
	} else {
		this.dom.style.display = '';
	}
};

function ContextMenuNestItem (menuList) {
	var item = document.createElement('li');
	item.setAttribute('class', 'nested');
	
	item.appendChild(menuList.dom);
	
	this.child = menuList;
	this.dom = item;
	this.visible = true;
	return this;
}

/**
 * setVisible
 * Set whether this item is visible or not.
 * 
 * @param vis boolean. 
 */
ContextMenuNestItem.prototype.setVisible = function(vis) {
	this.visible = vis;
};

/**
 * getVisible
 * 
 * @return boolean. Whether this item is visible or not
 */
ContextMenuNestItem.prototype.getVisible = function() {
	return this.child.getVisible();
};

/**
 * show
 * 
 * Set this item to be visible
 */
ContextMenuNestItem.prototype.show = function() {
	if(! this.getVisible()){
		this.dom.style.display = 'none';
	} else {
		this.dom.style.display = '';
		this.child.show();
	}
};

/*
***** Example Usage ******
function oncontextmenu(event){
	var menu = this.nodeContext;
	if (menu == null) {	
		menu = new ContextMenu({
			0: new ContextMenuList({
				0: new ContextMenuItem('Inspect', this.placeholderCallback)
				}),
			1: new ContextMenuList({
				0: new ContextMenuItem('Connected Nodes..', null, 'label'),
				1: new ContextMenuNestItem(new ContextMenuList({
						0: new ContextMenuItem('Add', this.placeholderCallback),
						1: new ContextMenuItem('Add by Predicate..', this.placeholderCallback)
					}))
				}),
			2: new ContextMenuList({
				0: new ContextMenuItem('Stick\\Unstick', this.placeholderCallback, 'default'),
				1: new ContextMenuItem('Remove\\Hide Node', this.placeholderCallback)
				})
			});
		
		this.nodeContext = menu;
	}
	
	return menu.show(event);
}
*/
