/**
 * eGroupWare egw_action framework - egw action framework
 *
 * @link http://www.egroupware.org
 * @author Andreas Stöckel <as@stylite.de>
 * @copyright 2011 by Andreas Stöckel
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @package egw_action
 * @version $Id$
 */

/*egw:uses
	vendor.bower-asset.jquery.dist.jquery;
	egw_menu;
*/

import {egwAction, egwActionImplementation, egwActionObject} from './egw_action.js';
import {egwFnct} from './egw_action_common.js';
import {egwMenu, _egw_active_menu} from "./egw_menu.js";
import {EGW_KEY_ENTER, EGW_KEY_MENU} from "./egw_action_constants.js";

if (typeof window._egwActionClasses == "undefined")
	window._egwActionClasses = {};
_egwActionClasses["popup"] = {
	"actionConstructor": egwPopupAction,
	"implementation": getPopupImplementation
};

export function egwPopupAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple)
{
	var action = new egwAction(_id, _handler, _caption, _icon, _onExecute, _allowOnMultiple);
	action.type = "popup";
	action.canHaveChildren = ["popup"];
	action["default"] = false;
	action.order = 0;
	action.group = 0;
	action.hint = false;
	action.checkbox = false;
	action.radioGroup = 0;
	action.checked = false;
	action.confirm_mass_selection = null;
	action.shortcut = null;
	action.singleClick = false;

	action.set_singleClick = function(_value) {
		action["singleClick"] = _value;
	};

	action.set_default = function(_value) {
		action["default"] = _value;
	};

	action.set_order = function(_value) {
		action.order = _value;
	};

	action.set_group = function(_value) {
		action.group = _value;
	};

	action.set_hint = function(_value) {
		action.hint = _value;
	};

	// If true, the action will be rendered as checkbox
	action.set_checkbox = function(_value) {
		action.checkbox = _value;
	};

	action.set_checked = function(_value) {
		action.checked = _value;
	};

	/**
	 * Set either a confirmation prompt, or TRUE to indicate that this action
	 * cares about large selections and to ask the confirmation prompt(s)
	 *
	 * @param {String|Boolean} _value
	 */
	action.set_confirm_mass_selection = function(_value) {
		action.confirm_mass_selection = _value;
	};

	// Allow checkbox to be set from context using the given function
	action.set_isChecked = function(_value) {
		action.isChecked = new egwFnct(this, null, []);
		if(_value !== null)
		{
			action.isChecked.setValue(_value);
		}
	};

	// If radioGroup is >0 and the element is a checkbox, radioGroup specifies
	// the group of radio buttons this one belongs to
	action.set_radioGroup = function(_value) {
		action.radioGroup = _value;
	};

	action.set_shortcut = function(_value) {
		if (_value)
		{
			var sc = {
				"keyCode": -1,
				"shift": false,
				"ctrl": false,
				"alt": false
			};

			if (typeof _value == "object" && typeof _value.keyCode != "undefined" &&
			    typeof _value.caption != "undefined")
			{
				sc.keyCode = _value.keyCode;
				sc.caption = _value.caption;
				sc.shift = (typeof _value.shift == "undefined") ? false : _value.shift;
				sc.ctrl = (typeof _value.ctrl == "undefined") ? false : _value.ctrl;
				sc.alt = (typeof _value.alt == "undefined") ? false : _value.alt;
			}

			this.shortcut = sc;
		}
		else
		{
			this.shortcut = false;
		}
	};

	return action;
}

var
	_popupActionImpl = null;

export function getPopupImplementation()
{
	if (!_popupActionImpl)
	{
		_popupActionImpl = new egwPopupActionImplementation();
	}
	return _popupActionImpl;
}

export function egwPopupActionImplementation()
{
	var ai = new egwActionImplementation();

	ai.type = "popup";

	ai.auto_paste = true;

	/**
	 * Registers the handler for the default action
	 *
	 * @param {DOMNode} _node
	 * @param {function} _callback
	 * @param {object} _context
	 * @returns {boolean}
	 */
	ai._registerDefault = function(_node, _callback, _context)
	{
		var defaultHandler = function(e) {
			// Prevent bubbling bound event on <a> tag, on touch devices
			// a tag should be handled by default event
			if (egwIsMobile() && e.target.tagName == "A")	return true;

			if (typeof document.selection != "undefined" && typeof document.selection.empty != "undefined")
			{
				document.selection.empty();
			}
			else if( typeof window.getSelection != "undefined")
			{
				var sel = window.getSelection();
				sel.removeAllRanges();
			}

			if (!(_context.manager.getActionsByAttr('singleClick', true).length > 0 &&
					e.originalEvent.target.classList.contains('et2_clickable')))
			{
				_callback.call(_context, "default", ai);
			}
			
			// Stop action from bubbling up to parents
			e.stopPropagation();
			e.cancelBubble = true;

			// remove context menu if we are in mobile theme
			// and intended to open the entry
			if (_egw_active_menu && e.which == 1) _egw_active_menu.hide();
			return false;
		};

		if (egwIsMobile() || _context.manager.getActionsByAttr('singleClick', true).length > 0) {
			jQuery(_node).bind('click', defaultHandler);
		} else {
			_node.ondblclick = defaultHandler;
		}
	};

	ai._getDefaultLink = function(_links) {
		var defaultAction = null;
		for (var k in _links)
		{
			if (_links[k].actionObj["default"] && _links[k].enabled)
			{
				defaultAction = _links[k].actionObj;
				break;
			}
		}

		return defaultAction;
	};

	ai._searchShortcut = function (_key, _objs, _links) {
		for (var i = 0; i < _objs.length; i++)
		{
			var sc = _objs[i].shortcut;
			if (sc && sc.keyCode == _key.keyCode && sc.shift == _key.shift &&
			    sc.ctrl == _key.ctrl && sc.alt == _key.alt &&
			    _objs[i].type == "popup" && (typeof _links[_objs[i].id] == "undefined" ||
			    _links[_objs[i].id].enabled))
			{
				return _objs[i];
			}

			var obj = this._searchShortcut(_key, _objs[i].children, _links);
			if (obj) {
				return obj;
			}
		}
	};

	ai._searchShortcutInLinks = function(_key, _links) {
		var objs = [];
		for (var k in _links)
		{
			if (_links[k].enabled)
			{
				objs.push(_links[k].actionObj);
			}
		}

		return ai._searchShortcut(_key, objs, _links);
	};

	/**
	 * Handles a key press
	 *
	 * @param {object} _key
	 * @param {type} _selected
	 * @param {type} _links
	 * @param {type} _target
	 * @returns {Boolean}
	 */
	ai._handleKeyPress = function(_key, _selected, _links, _target) {
		// Handle the default
		if (_key.keyCode == EGW_KEY_ENTER && !_key.ctrl && !_key.shift && !_key.alt) {
			var defaultAction = this._getDefaultLink(_links);
			if (defaultAction)
			{
				defaultAction.execute(_selected);
				return true;
			}
		}

		// Menu button
		if (_key.keyCode == EGW_KEY_MENU && !_key.ctrl)
		{
			return this.doExecuteImplementation({posx:0,posy:0}, _selected, _links, _target);
		}


		// Check whether the given shortcut exists
		var obj = this._searchShortcutInLinks(_key, _links);
		if (obj)
		{
			obj.execute(_selected);
			return true;
		}

		return false;
	};
	ai._handleTapHold = function (_node, _callback)
	{
		let holdTimer = 600;
		let maxDistanceAllowed = 40;
		let tapTimeout = null;
		let startx = 0;
		let starty = 0;

		//TODO (todo-jquery): ATM we need to convert the possible given jquery dom node object into DOM Element, this
		// should be no longer neccessary after removing jQuery nodes.
		if (_node instanceof jQuery)
		{
			_node = _node[0];
		}
		_node.addEventListener('touchstart', function(e){

			tapTimeout = setTimeout(function(event){
				_callback(e);
			}, holdTimer);
			startx = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
			starty = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
		});
		_node.addEventListener('touchend', function(){
			clearTimeout(tapTimeout);
		});
		_node.addEventListener('touchmove', function(_event){
			if (tapTimeout == null) return;
			let e = _event.originalEvent;
			let x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
			let y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
			if (Math.sqrt((x-startx)*(x-startx) + (y-starty)+(y-starty)) > maxDistanceAllowed) clearTimeout(tapTimeout);
		});
	}
	/**
	 * Registers the handler for the context menu
	 *
	 * @param {DOMNode} _node
	 * @param {function} _callback
	 * @param {object} _context
	 * @returns {boolean}
	 */
	ai._registerContext = function(_node, _callback, _context)
	{
		var contextHandler = function(e) {

			//Obtain the event object
			if (!e)
			{
				e = window.event;
			}

			if (_egw_active_menu)
			{
				_egw_active_menu.hide();
			}
			else if (!e.ctrlKey && e.which == 3 || e.which === 0) // tap event indicates by 0
			{
				var _xy = ai._getPageXY(e);
				var _implContext = {event:e, posx:_xy.posx, posy: _xy.posy};
				_callback.call(_context, _implContext, ai);
			}

			e.cancelBubble = !e.ctrlKey || e.which == 1;
			if (e.stopPropagation && e.cancelBubble)
			{
				e.stopPropagation();
			}
			return !e.cancelBubble;
		};
		// Safari still needs the taphold to trigger contextmenu
		// Chrome has default event on touch and hold which acts like right click
		this._handleTapHold(_node, contextHandler);
		jQuery(_node).on('contextmenu', contextHandler);
	};

	ai.doRegisterAction = function(_aoi, _callback, _context)
	{
		var node = _aoi.getDOMNode();

		if (node)
		{
			this._registerDefault(node, _callback, _context);
			this._registerContext(node, _callback, _context);
			return true;
		}
		return false;
	};

	ai.doUnregisterAction = function(_aoi)
	{
		var node = _aoi.getDOMNode();
		jQuery(node).off();
	};

	/**
	 * Builds the context menu and shows it at the given position/DOM-Node.
	 *
	 * @param {object} _context
	 * @param {type} _selected
	 * @param {type} _links
	 * @param {type} _target
	 * @returns {Boolean}
	 */
	ai.doExecuteImplementation = function(_context, _selected, _links, _target)
	{
		if (typeof _target == "undefined")
		{
			_target = null;
		}

		ai._context = _context;
		if (typeof _context == "object" && typeof _context.keyEvent == "object")
		{
			return ai._handleKeyPress(_context.keyEvent, _selected, _links, _target);
		}
		else if (_context != "default")
		{
			//Check whether the context has the posx and posy parameters
			if ((typeof _context.posx != "number" || typeof _context.posy != "number") &&
			    typeof _context.id != "undefined")
			{
				// Calculate context menu position from the given DOM-Node
				var node = _context;

				x = jQuery(node).offset().left;
				y = jQuery(node).offset().top;

				_context = {"posx": x, "posy": y};
			}

			var menu = ai._buildMenu(_links, _selected, _target);
			menu.showAt(_context.posx, _context.posy);

			return true;
		}
		else
		{
			var defaultAction = ai._getDefaultLink(_links);
			if (defaultAction)
			{
				defaultAction.execute(_selected);
			}
		}

		return false;
	};

	/**
	 * Groups and sorts the given action tree layer
	 *
	 * @param {type} _layer
	 * @param {type} _links
	 * @param {type} _parentGroup
	 */
	ai._groupLayers = function(_layer, _links, _parentGroup)
	{
		// Seperate the multiple groups out of the layer
		var link_groups = {};

		for (var i = 0; i < _layer.children.length; i++)
		{
			var actionObj = _layer.children[i].action;

			// Check whether the link group of the current element already exists,
			// if not, create the group
			var grp = actionObj.group;
			if (typeof link_groups[grp] == "undefined")
			{
				link_groups[grp] = [];
			}

			// Search the link data for this action object if none is found,
			// visible and enabled = true is assumed
			var visible = true;
			var enabled = true;

			if (typeof _links[actionObj.id] != "undefined")
			{
				visible = _links[actionObj.id].visible;
				enabled = _links[actionObj.id].enabled;
			}

			// Insert the element in order
			var inserted = false;
			var groupObj = {
				"actionObj": actionObj,
				"visible": visible,
				"enabled": enabled,
				"groups": []
			};

			for (var j = 0; j < link_groups[grp].length; j++)
			{
				var elem = link_groups[grp][j].actionObj;
				if (elem.order > actionObj.order)
				{
					inserted = true;
					link_groups[grp].splice(j, 0, groupObj);
					break;
				}
			}

			// If the object hasn't been inserted, add it to the end of the list
			if (!inserted)
			{
				link_groups[grp].push(groupObj);
			}

			// If this child itself has children, group those elements too
			if (_layer.children[i].children.length > 0)
			{
				this._groupLayers(_layer.children[i], _links, groupObj);
			}
		}

		// Transform the link_groups object into an sorted array
		var groups = [];

		for (var k in link_groups)
		{
			groups.push({"grp": k, "links": link_groups[k]});
		}

		groups.sort(function(a, b) {
			var ia = parseInt(a.grp);
			var ib = parseInt(b.grp);
			return (ia > ib) ? 1 : ((ia < ib) ? -1 : 0);
		});

		// Append the groups to the groups2 array
		var groups2 = [];
		for (var i = 0; i < groups.length; i++)
		{
			groups2.push(groups[i].links);
		}

		_parentGroup.groups = groups2;
	};

	/**
	 * Build the menu layers
	 *
	 * @param {type} _menu
	 * @param {type} _groups
	 * @param {type} _selected
	 * @param {type} _enabled
	 * @param {type} _target
	 */
	ai._buildMenuLayer = function(_menu, _groups, _selected, _enabled, _target)
	{
		var firstGroup = true;

		for (var i = 0; i < _groups.length; i++)
		{
			var firstElem = true;

			// Go through the elements of each group
			for (var j = 0; j < _groups[i].length; j++)
			{
				var link = _groups[i][j];

				if (link.visible)
				{
					// Add an seperator after each group
					if (!firstGroup && firstElem)
					{
						_menu.addItem("", "-");
					}
					firstElem = false;

					var item = _menu.addItem(link.actionObj.id, link.actionObj.caption,
						link.actionObj.iconUrl);
					item["default"] = link.actionObj["default"];

					// As this code is also used when a drag-drop popup menu is built,
					// we have to perform this check
					if (link.actionObj.type == "popup")
					{
						item.set_hint(link.actionObj.hint);
						item.set_checkbox(link.actionObj.checkbox);
						item.set_checked(link.actionObj.checked);
						if(link.actionObj.checkbox && link.actionObj.isChecked)
						{
							item.set_checked(link.actionObj.isChecked.exec(link.actionObj, _selected));
						}
						item.set_groupIndex(link.actionObj.radioGroup);

						if (link.actionObj.shortcut && !egwIsMobile())
						{
							var sc = link.actionObj.shortcut;
							item.set_shortcutCaption(sc.caption);
						}
					}

					item.set_data(link.actionObj);
					if (link.enabled && _enabled)
					{
						item.set_onClick(function(elem) {
							// Pass the context
							elem.data.menu_context = ai._context;

							// Copy the "checked" state
							if (typeof elem.data.checked != "undefined")
							{
								elem.data.checked = elem.checked;
							}

							elem.data.execute(_selected, _target);

							if (typeof elem.data.checkbox != "undefined" && elem.data.checkbox)
							{
								return elem.data.checked;
							}
						});
					}
					else
					{
						item.set_enabled(false);
					}

					// Append the parent groups
					if (link.groups)
					{
						this._buildMenuLayer(item, link.groups, _selected, link.enabled, _target);
					}
				}
			}

			firstGroup = firstGroup && firstElem;
		}
	};

	/**
	 * Builds the context menu from the given action links
	 *
	 * @param {type} _links
	 * @param {type} _selected
	 * @param {type} _target
	 * @returns {egwMenu|egwActionImplementation._buildMenu.menu}
	 */
	ai._buildMenu = function(_links, _selected, _target)
	{
		// Build a tree containing all actions
		var tree = {"root": []};

		// Automatically add in Drag & Drop actions
		if(this.auto_paste && !egwIsMobile())
		{
			this._addCopyPaste(_links,_selected);
		}

		for (var k in _links)
		{
			_links[k].actionObj.appendToTree(tree);
		}

		// We need the dummy object container in order to pass the array by
		// reference
		var groups = {
			"groups": []
		};

		if (tree.root.length > 0)
		{
			// Sort every action object layer by the given sort position and grouping
			this._groupLayers(tree.root[0], _links, groups);
		}

		var menu = new egwMenu();

		// Build the menu layers
		this._buildMenuLayer(menu, groups.groups, _selected, true, _target);

		return menu;
	};

	ai._getPageXY = function getPageXY(event)
	{
		// document.body.scrollTop does not work in IE
		var scrollTop = document.body.scrollTop ? document.body.scrollTop :
			document.documentElement.scrollTop;
		var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft :
			document.documentElement.scrollLeft;

		return {'posx': (event.clientX + scrollLeft), 'posy': (event.clientY + scrollTop)};
	};

	/**
	 * Automagically add in context menu items for copy and paste from
	 * drag and drop actions, based on current clipboard and the accepted types
	 *
	 * @param {object[]} _links Actions for inclusion in the menu
	 * @param {egwActionObject[]} _selected Currently selected entries
	 */
	ai._addCopyPaste = function (_links, _selected)
	{
		// Get a list of drag & drop actions
		var drag = _selected[0].getSelectedLinks('drag').links;
		var drop = _selected[0].getSelectedLinks('drop').links;

		// No drags & no drops means early exit (only by default added egw_cancel_drop does NOT count!)
		if ((!drag || jQuery.isEmptyObject(drag)) &&
			(!drop || jQuery.isEmptyObject(drop) ||
				Object.keys(drop).length === 1 && typeof drop.egw_cancel_drop !== 'undefined'))
		{
			return;
		}

		// Find existing actions so we don't get copies
		var mgr = _selected[0].manager;
		var copy_action = mgr.getActionById('egw_copy');
		var add_action = mgr.getActionById('egw_copy_add');
		var clipboard_action = mgr.getActionById('egw_os_clipboard');
		var paste_action = mgr.getActionById('egw_paste');

		// Fake UI so we can simulate the position of the drop
		var ui = {
			position: {top: 0, left: 0},
			offset: {top: 0, left: 0}
		};
		if(this._context.event)
		{
			var event = this._context.event.originalEvent;
			ui.position = {top: event.pageY, left: event.pageX};
			ui.offset = {top: event.offsetY, left: event.offsetX};
		}
		// Create default copy menu action
		if(drag && !jQuery.isEmptyObject(drag))
		{
			// Don't re-add if it's there
			if(copy_action == null)
			{
				// Create a drag action that allows linking
				copy_action = mgr.addAction('popup', 'egw_copy', egw.lang('Copy to clipboard'), egw.image('copy'), function(action, selected) {
					// Copied, now add to clipboard
					var clipboard = {
						type:[],
						selected:[]
					};

					// When pasting we need to know the type of drag
					for(var k in drag)
					{
						if(drag[k].enabled && drag[k].actionObj.dragType.length > 0)
						{
							clipboard.type = clipboard.type.concat(drag[k].actionObj.dragType);
						}
					}
					clipboard.type = jQuery.unique(clipboard.type);
					// egwAction is a circular structure and can't be stringified so just take what we want
					// Hopefully that's enough for the action handlers
					for(var k in selected)
					{
						if(selected[k].id) clipboard.selected.push({id:selected[k].id, data:selected[k].data});
					}

					// Save it in session
					egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify(clipboard));
				},true);
				copy_action.group = 2.5;
			}
			if(add_action == null)
			{
				// Create an action to add selected to clipboard
				add_action = mgr.addAction('popup', 'egw_copy_add', egw.lang('Add to clipboard'), egw.image('copy'), function(action, selected) {
					// Copied, now add to clipboard
					var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || {
						type:[],
						selected:[]
					};

					// When pasting we need to know the type of drag
					for(var k in drag)
					{
						if(drag[k].enabled && drag[k].actionObj.dragType.length > 0)
						{
							clipboard.type = clipboard.type.concat(drag[k].actionObj.dragType);
						}
					}
					clipboard.type = jQuery.unique(clipboard.type);
					// egwAction is a circular structure and can't be stringified so just take what we want
					// Hopefully that's enough for the action handlers
					for(var k in selected)
					{
						if(selected[k].id) clipboard.selected.push({id:selected[k].id, data:selected[k].data});
					}

					// Save it in session
					egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify(clipboard));
				},true);
				add_action.group = 2.5;

			}
			if(clipboard_action == null)
			{
				// Create an action to add selected to clipboard
				clipboard_action = mgr.addAction('popup', 'egw_os_clipboard', egw.lang('Copy to OS clipboard'), egw.image('copy'), function(action) {

					if(document.queryCommandSupported('copy'))
					{
						jQuery(action.data.target).trigger('copy');
					}
				},true);
				clipboard_action.group = 2.5;
			}
			let os_clipboard_caption = "";
			if(this._context.event)
			{
				os_clipboard_caption = this._context.event.originalEvent.target.innerText.trim();
				clipboard_action.set_caption(egw.lang('Copy "%1"', os_clipboard_caption.length > 20 ? os_clipboard_caption.substring(0, 20) + '...' : os_clipboard_caption));
				clipboard_action.data.target = this._context.event.originalEvent.target;
			}
			jQuery(clipboard_action.data.target).off('copy').on('copy', function(event) {
				// Cancel any no-select css
				var target = jQuery(clipboard_action.data.target);
				var old_select = target.css('user-select');
				target.css('user-select','all');

				var range = document.createRange();
				range.selectNode(clipboard_action.data.target);
				window.getSelection().removeAllRanges();
				window.getSelection().addRange(range);

				target.css('user-select',old_select);

				var successful = false;
				try {
					// detect we are in IE via checking setActive, since it's
					// only supported in IE, and make sure there's clipboardData object
					if (typeof event.target.setActive !='undefined' && window.clipboardData)
					{
						window.clipboardData.setData('Text', jQuery(clipboard_action.data.target).text().trim());
					}
					if(event.clipboardData)
					{
						event.clipboardData.setData('text/plain', jQuery(clipboard_action.data.target).text().trim());
						event.clipboardData.setData('text/html', jQuery(clipboard_action.data.target).html());
					}
					// Show fail message, just in case
					egw.message(egw.lang('Use Ctrl-C/Cmd-C to copy'));

					successful = document.execCommand('copy');
				} catch(err) {}

				if(successful)
				{
					// Clear fail message
					egw.message('');
					window.getSelection().removeAllRanges();
					target.css('user-select',old_select);
					return false;
				}
			});
			if(typeof _links[copy_action.id] == 'undefined')
			{
				_links[copy_action.id] = {
					"actionObj": copy_action,
					"enabled": true,
					"visible": true,
					"cnt": 0
				};
			}
			if(typeof _links[add_action.id] == 'undefined')
			{
				_links[add_action.id] = {
					"actionObj": add_action,
					"enabled": true,
					"visible": true,
					"cnt": 0
				};
			}
			if(typeof _links[clipboard_action.id] == 'undefined')
			{
				_links[clipboard_action.id] = {
					"actionObj": clipboard_action,
					"enabled": os_clipboard_caption.length > 0,
					"visible": os_clipboard_caption.length > 0,
					"cnt": 0
				};
			}
		}

		// Create default paste menu item
		if(drop && !jQuery.isEmptyObject(drop))
		{
			// Create paste action
			// This injects the clipboard data and calls the original handler
			var paste_exec = function(action, selected) {
				// Add in clipboard as a sender
				var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard'));
				// Fake drop position
				drop[action.id].actionObj.ui = ui;
				// Set a flag so apps can tell the difference, if they need to
				drop[action.id].actionObj.paste = true;

				drop[action.id].actionObj.execute(clipboard.selected,selected[0]);

				drop[action.id].actionObj.paste = false;
			};

			var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || {
				type:[],
				selected:[]
			};

			// Don't re-add if action already exists
			if(paste_action == null)
			{
				paste_action = mgr.addAction('popup', 'egw_paste', egw.lang('Paste'), egw.image('editpaste'), paste_exec,true);
				paste_action.group = 2.5;
				paste_action.order = 9;
				paste_action.canHaveChildren.push('drop');
			}

			// Set hint to something resembling current clipboard
			var hint = egw.lang('Clipboard') + ":\n";
			paste_action.set_hint(hint);
			// Add titles of entries
			for(var i = 0; i < clipboard.selected.length; i++)
			{
				var id = clipboard.selected[i].id.split('::');
				egw.link_title(id[0],id[1],function(title) {if(title)this.hint += title+"\n";},paste_action);
			}

			// Add into links so it's included in menu
			if(paste_action && paste_action.enabled.exec(paste_action, clipboard.selected, _selected[0]))
			{
				if(typeof _links[paste_action.id] == 'undefined')
				{
					_links[paste_action.id] = {
						"actionObj": paste_action,
						"enabled": false,
						"visible": clipboard != null,
						"cnt": 0
					};
				}
				while(paste_action.children.length > 0)
				{
					paste_action.children[0].remove();
				}

				// If nothing [valid] in the clipboard, don't bother with children
				if(clipboard == null || typeof clipboard.type != 'object')
				{
					return;
				}

				// Add in actual actions as children
				for(var k in drop)
				{
					// Add some choices - need to be a copy, or they interfere with
					// the original
					var drop_clone = jQuery.extend({},drop[k].actionObj);
					var parent = paste_action.parent === drop_clone.parent ? paste_action : (paste_action.getActionById(drop_clone.parent.id) || paste_action);
					drop_clone.parent = parent;
					drop_clone.onExecute = new egwFnct(this, null, []);
					drop_clone.children = [];
					drop_clone.set_onExecute(paste_exec);
					parent.children.push(drop_clone);
					parent.allowOnMultiple = paste_action.allowOnMultiple && drop_clone.allowOnMultiple;
					_links[k] = jQuery.extend({},drop[k]);
					_links[k].actionObj = drop_clone;

					// Drop is allowed if clipboard types intersect drop types
					_links[k].enabled = false;
					_links[k].visible = false;
					for (var i = 0; i < drop_clone.acceptedTypes.length; i++)
					{
						if (clipboard.type.indexOf(drop_clone.acceptedTypes[i]) != -1)
						{
							_links[paste_action.id].enabled = true;
							_links[k].enabled = true;
							_links[k].visible = true;
							break;
						}
					}
				}
			}
		}
	};
	return ai;
}