/**
 * eGroupware Framework ui object
 * @package framework
 * @author Hadi Nategh <hn@stylite.de>
 * @author Andreas Stoeckel <as@stylite.de>
 * @copyright Stylite AG 2014
 * @description Framework ui object, is implementation of UI class
 */

/*egw:uses
	vendor.bower-asset.jquery.dist.jquery;
	/api/js/jquery/mousewheel/mousewheel.js;
	egw_inheritance.js;
*/

import "../../../vendor/bower-asset/jquery/dist/jquery.min.js";
import "../jquery/jquery.noconflict.js";
import "../jquery/mousewheel/mousewheel.js";
import '../jsapi/egw_inheritance.js';
import {EGW_KEY_ENTER, EGW_KEY_SPACE} from '../egw_action/egw_action_constants';
import interact from "@interactjs/interactjs";

/**
 * ui siemenu entry class
 * Basic sidebar menu implementation
 *
 * @type @exp;Class@call;extend
 */
window.fw_ui_sidemenu_entry = (function(){ "use strict"; return Class.extend(
{
	/**
	 * Framework ui sidemenu entry class constructor
	 *
	 * @param {object} _parent specifies the parent egw_fw_ui_sidemenu
	 * @param {object} _baseDiv specifies "div" element the entries should be appended to.
	 * @param {object} _elemDiv
	 * @param {string} _name specifies the title of the entry in the side menu
	 * @param {string} _icon specifies the icon which should be viewd besides the title in the side menu
	 * @param {function}(_sender) _callback specifies the function which should be called when the entry is clicked. The _sender parameter passed is a reference to this egw_fw_ui_sidemenu_entry element.
	 * @param {object} _tag can be used to attach any user data to the object. Inside egw_fw _tag is used to attach an egw_fw_class_application to each sidemenu entry.
	 * @param {string} _app application name
	 */
	init: function (_parent, _baseDiv, _elemDiv, _name, _icon, _callback, _tag, _app)
	{
		this.baseDiv = _baseDiv;
		this.elemDiv = _elemDiv;
		this.entryName = _name;
		this.icon = _icon;
		this.tag = _tag;
		this.parent = _parent;
		this.atTop = false;
		this.isDraged = false;

		//Add a new div for the new entry to the base div
		this.headerDiv = document.createElement("div");
		this.headerDiv.id = _app+'_sidebox_header';
		jQuery(this.headerDiv).addClass("egw_fw_ui_sidemenu_entry_header");

		//Create the icon and set its image
		var iconDiv = egw.image_element(this.icon, _name);
		jQuery(iconDiv).addClass("egw_fw_ui_sidemenu_entry_icon");

		//Create the AJAX loader image (currently NOT used)
		this.ajaxloader = document.createElement("div");
		jQuery(this.ajaxloader).addClass("egw_fw_ui_ajaxloader");
		jQuery(this.ajaxloader).hide();

		//Create the entry name header
		var entryH1 = document.createElement("h1");
		jQuery(entryH1).text(this.entryName);

		//Append icon, name, and ajax loader
		jQuery(this.headerDiv).append(iconDiv);
		jQuery(this.headerDiv).append(entryH1);
		jQuery(this.headerDiv).append(this.ajaxloader);
		this.headerDiv._parent = this;
		this.headerDiv._callbackObject = new egw_fw_class_callback(this, _callback);
		jQuery(this.headerDiv).click(function(){
			if (!this._parent.isDraged)
			{
				this._callbackObject.call(this);
			}
			this._parent.isDraged = false;
			return true;
		});

		//close button on active header
		this.closeButton = document.createElement('span');
		this.closeButton.classList.add('close');


		//Create the content div
		this.contentDiv = document.createElement("div");
		this.contentDiv.id = _app+'_sidebox_content';
		jQuery(this.contentDiv).addClass("egw_fw_ui_sidemenu_entry_content")
			.attr("role","menu")
			.attr("aria-label",this.entryName);
		jQuery(this.contentDiv).hide();

		//Add in invisible marker to store the original position of this element in the DOM tree
		this.marker = document.createElement("div");
		this.marker._parent = this;
		this.marker.className = 'egw_fw_ui_sidemenu_marker';
		var entryH1_ = document.createElement("h1");
		jQuery(entryH1_).text(this.entryName);
		jQuery(this.marker).append(entryH1_);
		jQuery(this.marker).hide();

		//Create a container which contains all generated elements and is then added
		//to the baseDiv
		this.containerDiv = document.createElement("div");
		this.containerDiv._parent = this;
		jQuery(this.containerDiv).append(this.marker);
		jQuery(this.containerDiv).append(this.headerDiv);
		jQuery(this.containerDiv).append(this.contentDiv);

		//Append header and content div to the base div
		jQuery(this.elemDiv).append(this.containerDiv);
	},

	/**
	 * setContent replaces the content of the sidemenu entry with the content given by _content.
	 * @param {string} _content HTML/Text which should be displayed.
	 */
	setContent: function(_content)
	{
		//Set the content of the contentDiv
		jQuery(this.contentDiv).empty();
		jQuery(this.contentDiv).append(_content);
	},

	/**
	 * open openes this sidemenu_entry and displays the content.
	 */
	open: function()
	{
		jQuery(this.baseDiv).prepend(this.contentDiv);
		jQuery(this.baseDiv).prepend(this.headerDiv);

		this.atTop = true;

		jQuery(this.headerDiv).addClass("egw_fw_ui_sidemenu_entry_header_active");
		jQuery(this.contentDiv).show();
	},

	/**
	 * close closes this sidemenu_entry and hides the content.
	 */
	close: function()
	{
		/* Move the content and header div behind the marker again */
		if (this.atTop)
		{
			jQuery(this.marker).after(this.contentDiv);
			jQuery(this.marker).after(this.headerDiv);
			this.atTop = false;
		}

		jQuery(this.headerDiv).removeClass("egw_fw_ui_sidemenu_entry_header_active");
		jQuery(this.contentDiv).hide();
	},

	setCloseButton: function(_callback)
	{
		if (typeof _callback == "function" && this.closeButton)
		{
			this.headerDiv.append(this.closeButton);
			this.closeButton.addEventListener('click', _callback);
		}
	},

	/**
	 * egw_fw_ui_sidemenu_entry_header_active
	 * showAjaxLoader shows the AjaxLoader animation which should be displayed when
	 * the content of the sidemenu entry is just being loaded.
	 */
	showAjaxLoader: function()
	{
		jQuery(this.ajaxloader).show();
	},

	/**
	 * showAjaxLoader hides the AjaxLoader animation
	 */
	hideAjaxLoader: function()
	{
		jQuery(this.ajaxloader).hide();
	},

	/**
	 * Removes this entry.
	 */
	remove: function()
	{
		jQuery(this.headerDiv).remove();
		jQuery(this.contentDiv).remove();
	}
});}).call(window);

/**
 *
 * @type @exp;Class@call;extend
 */
window.fw_ui_sidemenu = (function(){ "use strict"; return Class.extend(
{
	/**
	* The constructor of the egw_fw_ui_sidemenu.
	*
	* @param {object} _baseDiv specifies the "div" in which all entries added by the addEntry function should be displayed.
	*/
   init:function(_baseDiv)
   {
	   this.baseDiv = _baseDiv;
	   this.elemDiv = document.createElement('div');
	   jQuery(this.baseDiv).append(this.elemDiv);
	   this.entries = new Array();
	   this.activeEntry = null;
   },

   /**
	* Funtion used internally to recursively step through a dom tree and add all appliction
	* markers in their order of appereance
	*
	* @param {array} _resultArray
	* @param {array} _children
	*/
   _searchMarkers: function(_resultArray, _children)
   {
	   for (var i = 0; i < _children.length; i++)
	   {
		   var child = _children[i];

		   if (child.className == 'egw_fw_ui_sidemenu_marker' && typeof child._parent != 'undefined')
		   {
			   _resultArray.push(child._parent);
		   }

		   this._searchMarkers(_resultArray, child.childNodes);
	   }
   },


   /**
	* Adds an entry to the sidemenu.
	*
	* @param {string} _name specifies the title of the new sidemenu entry
	* @param {string} _icon specifies the icon displayed aside the title
	* @param {function}(_sender) _callback specifies the function which should be called when a callback is clicked
	* @param {object} _tag extra data
	* @param {string} _app application name
	*/
   addEntry: function(_name, _icon, _callback, _tag, _app)
   {
	   //Create a new sidemenu entry and add it to the list
	   var entry = new egw_fw_ui_sidemenu_entry(this, this.baseDiv, this.elemDiv, _name, _icon,
		   _callback, _tag, _app);
	   this.entries[this.entries.length] = entry;

	   return entry;
   },

   /**
	* Openes the specified entry whilst closing all other entries in the list.
	*
	* @param {object} _entry specifies the entry which should be opened.
	*/
   open: function(_entry)
   {
	   //Close all other entries
	   for (var i = 0; i < this.entries.length; i++)
	   {
		   if (this.entries[i] != _entry)
		   {
			   this.entries[i].close();
		   }
	   }

	   if (_entry != null)
	   {
		   _entry.open();
	   }

	   this.activeEntry = _entry;
   },


   /**
	* Deletes all sidemenu entries.
	*/
   clean: function()
   {
	   for (var i = 0; i < this.entries.length; i++)
	   {
		   this.entries[i].remove();
	   }

	   this.entries = new Array();
   }
});}).call(window);

/**
 * Class: egw_fw_ui_tab
 * The egw_fw_ui_tab represents a single tab "sheet" in the ui
 */


/**
 * The constructor of the egw_fw_ui_tab class.
 *
 * @param {object} _parent specifies the parent egw_fw_ui_tabs class
 * @param {object} _contHeaderDiv specifies the container "div" element, which should contain the headers
 * @param {object} _contDiv specifies the container "div" element, which should contain the contents of the tabs
 * @param {string} _icon specifies the icon which should be viewed besides the title of the tab
 * @param {function}(_sender) _callback specifies the function which should be called when the tab title is clicked. The _sender parameter passed is a reference to this egw_fw_ui_tab element.
 * @param {function}(_sender) _closeCallback specifies the function which should be called when the tab close button is clicked. The _sender parameter passed is a reference to this egw_fw_ui_tab element.
 * @param {object} _tag can be used to attach any user data to the object. Inside egw_fw _tag is used to attach an egw_fw_class_application to each sidemenu entry.
 * @param {int} _pos is the position where the tab will be inserted
 * @param {string} application status (e.g. status="5")
 */
window.egw_fw_ui_tab = function(_parent, _contHeaderDiv, _contDiv, _icon, _callback,
	_closeCallback,	_tag, _pos, _status)
{
	this.parent = _parent;
	this.contHeaderDiv = _contHeaderDiv;
	this.contDiv = _contDiv;
	this.title = '';
	this.tag = _tag;
	this.closeable = true;
	this.callback = _callback;
	this.closeCallback = _closeCallback;
	this.position = _pos;
	this.status = _status;
	this.notification = 0;
	this.hint = '';

	//Create the header div and set its "click" function and "hover" event
	this.headerDiv = document.createElement("span");
	this.headerDiv._position = _pos;
	jQuery(this.headerDiv).attr('id', this.tag.appName+'-egw_fw_ui_tab_header').addClass("egw_fw_ui_tab_header");
	this.headerDiv.setAttribute('title', this.tag.displayName);

	//Create a new callback object and attach it to the header div
	this.headerDiv._callbackObject = new egw_fw_class_callback(this, _callback);
	jQuery(this.headerDiv).click(
		function(){
			this._callbackObject.call(this);
		});

	//Attach the hover effect to the header div
	jQuery(this.headerDiv).hover(
		function() {
			if (!jQuery(this).hasClass("egw_fw_ui_tab_header_active"))
				jQuery(this).addClass("egw_fw_ui_tab_header_hover");
		},
		function() {
			jQuery(this).removeClass("egw_fw_ui_tab_header_hover");
		}
	);

	// If dragging something over the tab, activate that app
	var tab = this.headerDiv;
	this.headerDiv.addEventListener('dragenter', (event) =>
	{
		event.stopPropagation();
		if (!this.headerDiv.parentElement.dataset.dragblock)
		{
			this.headerDiv.parentElement.dataset.dragblock = true;
			tab._callbackObject.call(tab);
		}
		tab.parentElement.addEventListener("dragend", dragBlockEnd)
		tab.parentElement.addEventListener("dragleave", dragBlockEnd)
	});
	var dragBlockEnd = (e) =>
	{


		tab.parentElement.removeEventListener("dragend", dragBlockEnd);
		tab.parentElement.removeEventListener("dragleave", dragBlockEnd);
		window.clearTimeout(tab.parentElement.dataset.dragblock);
		tab.parentElement.dataset.dragblock = window.setTimeout(() =>
		{
			tab.parentElement.removeAttribute("data-dragblock");
		}, 100);
	}


	//Create the close button and append it to the header div
	this.closeButton = document.createElement("span");
	this.closeButton._callbackObject = new egw_fw_class_callback(this, _closeCallback);
	jQuery(this.closeButton).addClass("egw_fw_ui_tab_close_button");
	jQuery(this.closeButton).click(
		function(){
			//Only call the close callback if the tab is set closeable
			if (this._callbackObject.context.closeable)
			{
				this._callbackObject.call(this);
				return false;
			}
			return true;
		});

	this.notificationDiv = document.createElement("div");
	var self = this;
	jQuery(this.notificationDiv).addClass('notifyTabDiv')
			.hide()
			.click(function(e){
				if (app.notifications.tabToggle(self.tag.appName))
				{
					e.stopImmediatePropagation();
				}
			})
			.appendTo(this.headerDiv);
	jQuery(this.headerDiv).append(this.closeButton);

	//Create the icon and append it to the header div
	var icon = egw.image_element(_icon);
	jQuery(icon).addClass("egw_fw_ui_tab_icon");
	jQuery(this.headerDiv).append(icon);

	//Create the title h1 and append it to the header div
	this.headerH1 = document.createElement("h1");
	this.setTitle('');
	jQuery(this.headerDiv).append(this.headerH1);

	//Add close tab button on sidemenuentry for frameworkTabs
	if (this.tag.isFrameworkTab)
	{
		this.tag.sidemenuEntry.setCloseButton(function(){

			//Only call the close callback if the tab is set closeable
			if (this._callbackObject.context.closeable)
			{
				this._callbackObject.call(this);
				return false;
			}
			return true;

		}.bind(this.closeButton));
	}

	this.contentDiv = document.createElement("div");
	jQuery(this.contentDiv).addClass("egw_fw_ui_tab_content")
		.attr("role","application")
		.hide();

	//Sort the element in at the given position
	var _this = this;
	var $_children = jQuery(this.contHeaderDiv).children();
	var _cnt = $_children.size();

	if (_cnt > 0 && _pos > -1)
	{
		$_children.each(function(i) {
			if (_pos <= this._position)
			{
				jQuery(this).before(_this.headerDiv);
				return false;
			}
			else if (i == (_cnt - 1))
			{
				jQuery(this).after(_this.headerDiv);
				return false;
			}
		});
	}
	else
	{
		jQuery(this.contHeaderDiv).append(this.headerDiv);
	}

	jQuery(this.contDiv).append(this.contentDiv);
}

/**
 * set notification
 *
 * @param {int} _value if set to 0 the notification gets reset if nothing set
 * it will increase the notification value by one
 */
window.egw_fw_ui_tab.prototype.setNotification = function(_value)
{
	this.notification = typeof _value != 'undefined' ? _value : this.notification+1;
	jQuery(this.notificationDiv).text(this.notification).toggle(this.notification > 0);
};

/**
 * setTitle sets the title of this tab. An existing title will be removed.
 *
 * @param {string} _title HTML/Text which should be displayed.
 */
window.egw_fw_ui_tab.prototype.setTitle = function(_title)
{
	this.title = _title;
	jQuery(this.headerH1).empty();
	jQuery(this.headerH1).text(_title);
};

/**
 * setHint sets tooltip of this tab. An existing tooltip will be removed.
 *
 * @param {string} _hint Text which should be displayed.
 */
window.egw_fw_ui_tab.prototype.setHint = function(_hint)
{
	this.hint = _hint;
	egw().tooltipBind(jQuery(this.headerDiv), _hint);
};

/**
 * setTitle sets the content of this tab. Existing content is removed.
 *
 * @param {string} _content HTML/Text which should be displayed.
 */
window.egw_fw_ui_tab.prototype.setContent = function(_content)
{
	jQuery(this.contentDiv).empty();
	jQuery(this.contentDiv).append(_content);
};

/**
 * Shows the content of the tab. Only one tab should be displayed at once. By using egw_fw_ui_tabs.showTab
 * you can assure this.
 */
window.egw_fw_ui_tab.prototype.show = function()
{
	jQuery(this.headerDiv).addClass("egw_fw_ui_tab_header_active");
	var content = jQuery(this.contentDiv);
	if(!content.is(':visible'))
	{
		content.show();

		// Trigger an event on the browser content, so apps & widgets know
		if(this.tag && this.tag.browser && this.tag.browser.contentDiv)
		{
			jQuery(this.tag.browser.contentDiv).trigger('show');
		}
		else if(content) // if the content is an iframe (eg. Calendar views)
		{
			jQuery(content).find('.egw_fw_content_browser_iframe').trigger('show');
		}
	}
};

/**
 * Hides the content of this tab.
 */
window.egw_fw_ui_tab.prototype.hide = function()
{
	jQuery(this.headerDiv).removeClass("egw_fw_ui_tab_header_active");
	var content = jQuery(this.contentDiv);
	if(content.is(':visible'))
	{
		content.hide();

		// Trigger an event on the browser content, so apps & widgets know
		if(this.tag && this.tag.browser && this.tag.browser.contentDiv)
		{
			jQuery(this.tag.browser.contentDiv).trigger('hide');
		}
	}
};

/**
 * hide tab header only
 */
window.egw_fw_ui_tab.prototype.hideTabHeader = function()
{
	jQuery(this.headerDiv).hide();
};

/**
 * Removes this tab and all its content.
 */
window.egw_fw_ui_tab.prototype.remove = function()
{
	this.hide();
	jQuery(this.contentDiv).empty().remove();
	jQuery(this.headerDiv).empty().remove();
	this.notificationDiv.remove();
};

/**
 * Sets whether the close button is shown/the close callback ever gets called.
 *
 * @param {boolean} _closeable if true, the close button is shown, if false, the close button is hidden. default is true.
 */
window.egw_fw_ui_tab.prototype.setCloseable = function(_closeable)
{
	this.closeable = _closeable;
	if (_closeable)
		jQuery(this.closeButton).show();
	else
		jQuery(this.closeButton).hide();
};


/**
 * Class: egw_fw_ui_tabs
 * The egw_fw_ui_tabs class cares about displaying a set of tab sheets.
 */


/**
 * The constructor of the egw_fw_ui_sidemenu_tabs class. Two "divs" are created inside the specified container element, one for the tab headers and one for the tab contents.
 *
 * @param {object} _contDiv specifies "div" element the tab ui element should be displayed in.
 */
window.egw_fw_ui_tabs = function(_contDiv)
{
	this.contDiv = _contDiv;

	//Create a div for the tab headers
	this.contHeaderDiv = document.createElement("div");
	jQuery(this.contHeaderDiv).addClass("egw_fw_ui_tabs_header");
	jQuery(this.contDiv).append(this.contHeaderDiv);

	this.appHeaderContainer = jQuery(document.createElement("div"));
	this.appHeaderContainer.addClass("egw_fw_ui_app_header_container");
	jQuery(this.contDiv).append(this.appHeaderContainer);

	this.appHeader = jQuery(document.createElement("div"));
	this.appHeader.addClass("egw_fw_ui_app_header");
	this.appHeader.hide();
	this.appHeaderContainer.append(this.appHeader);

	this.tabs = Array();

	this.activeTab = null;
	this.tabHistory = Array();
}

/**
 * Sets the "appHeader" text below the tabs list.
 *
 * @param {string} _text is the text which will be seen in the appHeader.
 * @param {string} _msg_class css class for message
 */
window.egw_fw_ui_tabs.prototype.setAppHeader = function(_text, _msg_class)
{
	this.appHeader.text(_text);
	this.appHeader.prop('class', "egw_fw_ui_app_header");
	if (_msg_class) this.appHeader.addClass(_msg_class);
	this.appHeader.show();
};

/**
 * Function internally used to remove double entries from the tab history. The tab
 * history is used to store the order in which the tabs have been opened, to be able
 * to switch back to the last tab when a tab is closed. Double entries in the tab history
 * may appear whenever a tab is deleted.
 */
window.egw_fw_ui_tabs.prototype.cleanHistory = function()
{
	for (var i = this.tabHistory.length - 1; i >= 0; i--)
	{
		if (this.tabHistory[i] == this.tabHistory[i - 1])
		{
			array_remove(this.tabHistory, i);
		}
	}
};

/**
 * Adds a new tab to the tabs ui element.
 * @param {string} _icon which should be displayed on the tab sheet header
 * @param {function} _callback (_sender) function which should be called whenever the tab header is clicked. The _sender parameter passed is a reference to this egw_fw_ui_tab element.
 * @param {function} _closeCallback (_sender) function which should be called whenever the close button of the tab is clicked. The _sender parameter passed is a reference to this egw_fw_ui_tab element.
 * @param {object} _tag can be used to attach any user data to the object. Inside egw_fw _tag is used to attach an egw_fw_class_application to each sidemenu entry.
 * @param {int} _pos specifies the position in the tab list. If _pos is -1, the tab will be added to the end of the tab list
 * @param {string} application status
 */
window.egw_fw_ui_tabs.prototype.addTab = function(_icon, _callback, _closeCallback, _tag, _pos, _status)
{
	var pos = -1;
	if (typeof _pos != 'undefined')
		pos = _pos;

	var tab = new egw_fw_ui_tab(this, this.contHeaderDiv, this.contDiv, _icon, _callback,
		_closeCallback, _tag, pos, _status);

	//Insert the tab into the tab list.
	var inserted = false;
	if (pos > -1)
	{
		for (var i in this.tabs)
		{
			if (this.tabs[i].position > pos)
			{
				this.tabs.splice(i, 0, tab);
				inserted = true;
				break;
			}
		}
	}

	if (pos == -1 || !inserted)
	{
		this.tabs[this.tabs.length] = tab;
	}

	if (this.activeTab == null)
		this.showTab(tab);

	return tab;
};

/**
 * Removes the specified tab from the tab list whilst trying to keep one tab open.
 * The tab which will be opened is determined throughout the tab open history.
 *
 * @param {object} _tab is the object which should be closed.
 */
window.egw_fw_ui_tabs.prototype.removeTab = function(_tab)
{
	//Delete the deleted tab from the history
	for (var i = this.tabHistory.length - 1; i >= 0; i--)
	{
		if (this.tabHistory[i] == _tab)
			array_remove(this.tabHistory, i);
	}

	//Delete entries in the histroy which might be double
	this.cleanHistory();
	// lookup for next available tab
	var lookUpTheNextTab = function(_tab){
		for(var t in this.tabs)
		{
			if (_tab != this.tabs[t] && (this.tabs[t]['status'] != '5' || this.tabs[t]['tag']['isFrameworkTab'])) return this.tabs[t];
		}
	}.bind(this);
	//Special treatement if the currently active tab gets deleted
	if (_tab == this.activeTab)
	{
		//Search for the next tab which should be selected
		if (this.tabs.length > 0)
		{
			//Check whether there is another tab in the tab history,
			//if not, look up for the next available tab.
			var tab = lookUpTheNextTab(_tab);
			if (typeof this.tabHistory[this.tabHistory.length - 1] != 'undefined')
			{
				tab = this.tabHistory[this.tabHistory.length - 1];
			}

			tab.callback.call(tab);
		}
	}

	//Perform the actual deletion of the tab
	_tab.remove();
	for (var i = this.tabs.length - 1; i >= 0; i--)
	{
		if (this.tabs[i] == _tab)
			array_remove(this.tabs, i);
	}
};

/**
 * Shows the specified _tab whilst closing all others.
 *
 * @param {object} _tab is the object which should be opened.
 */
window.egw_fw_ui_tabs.prototype.showTab = function(_tab)
{
	if (this.activeTab != _tab && (_tab.status != '5' || _tab.tag.isFrameworkTab))
	{
		for (var i = 0; i < this.tabs.length; i++)
		{
			if (this.tabs[i] != _tab)
			{
				this.tabs[i].hide();
			}
		}

		_tab.show();
		this.activeTab = _tab;

		if (this.tabHistory[this.tabHistory.length - 1] != _tab)
			this.tabHistory[this.tabHistory.length] = _tab;

		//Limit the tabHistory size in order to save memory
		if (this.tabHistory.length > 50)
		{
			array_remove(this.tabHistory, 0);
		}
	}
};

/**
 * Calls the setCloseable function of all tabs in the list.
 *
 * @param {boolean} _closeable
 */
window.egw_fw_ui_tabs.prototype.setCloseable = function(_closeable)
{
	for (var i = 0; i < this.tabs.length; i++)
	{
		this.tabs[i].setCloseable(_closeable);
	}
};

/**
 * Clears all data, removes all tabs, independently from the question, whether they may be closed or
 * not.
 */
window.egw_fw_ui_tabs.prototype.clean = function()
{
	//Remove all tabs, clean the tabs array
	for (var i = 0; i < this.tabs.length; i++)
	{
		array_remove(this.tabs, i);
	}

	//Reset all arrays and references
	this.tabs = new Array();
	this.activeTab = null;
	this.tabHistory = new Array();

	return true;
};

/**
 * Check if we have not the last tab visible in the tab stack
 *
 * @return {boolean} returns true if the open tab is not the last visible tab otherwise false
 */
window.egw_fw_ui_tabs.prototype._isNotTheLastTab = function()
{
	var n = 0;
	for (var i in this.tabs)
	{
		//exclude open tabs with status 5, e.g. status app
		if (this.tabs[i]['status'] != '5' || this.tabs[i]['tag']['isFrameworkTab']) n++;
	}
	return n > 1 ? true : false;
};

/**
 * get tab object for given appname
 *
 * @param {string} _appname
 * @returns {object|boolean} returns tab object, returns false if no tab found
 */
window.egw_fw_ui_tabs.prototype.getTab = function(_appname)
{
	for (var i = 0; i < this.tabs.length; i++)
	{
		if (this.tabs[i] && this.tabs[i]['tag']['appName'] == _appname)
		{
			return this.tabs[i];
		}
	}
	return false;
};

/**
 * Class: egw_fw_ui_category
 * A class which manages and renderes a simple menu with categories, which can be opened and shown
 *
 * @param {object} _contDiv
 * @param {string} _name
 * @param {string} _title
 * @param {object} _content
 * @param {function} _callback
 * @param {function} _animationCallback
 * @param {object} _tag
 */

window.egw_fw_ui_category = function(_contDiv, _name, _title, _content, _callback, _animationCallback, _tag)
{
	//Copy the parameters
	this.contDiv = _contDiv;
	this.catName = _name;
	this.callback = _callback;
	this.animationCallback = _animationCallback;
	this.tag = _tag;

	// Unique ID for accessibility
	let uid = "sidebox_nav_"+egw.uid();

	//Create the ui divs
	this.headerDiv = document.createElement('nav');
	jQuery(this.headerDiv).addClass('egw_fw_ui_category')
		.attr("aria-haspopup",true)
		.attr("aria-labelledby",uid)
		.attr("role","section")
		.attr("tabindex",0);

	//Add the text
	var entryH2 = document.createElement('h2');
	jQuery(entryH2)
		.attr("id",uid)
		.append(_title);
	jQuery(this.headerDiv).append(entryH2);

	//Add the content
	this.contentDiv = document.createElement('ul');
	this.contentDiv._parent = this;
	jQuery(this.contentDiv).addClass('egw_fw_ui_category_content');
	jQuery(this.contentDiv).append(_content);
	jQuery(this.contentDiv).hide();

	//Add content and header to the content div, add some magic jQuery code in order to make it foldable
	this.headerDiv._parent = this;
	entryH2._parent = this;
	this.headerDiv.addEventListener("keydown",
		function(e) {
			if (!["Enter", " "].includes(e.key) || e.target !== this)
			{
				return;
			}
			if (!jQuery(this).hasClass("egw_fw_ui_category_active"))
			{
				this._parent.open(false);
			}
			else
			{
				this._parent.close(false);
			}
		}
	);
	entryH2.addEventListener("click",
		function(e) {
			if (!jQuery(this).parent().hasClass("egw_fw_ui_category_active"))
			{
				this._parent.open(false);
			}
			else
			{
				this._parent.close(false);
			}
			e.stopPropagation();
		});
	jQuery(this.contDiv).append(this.headerDiv);
	jQuery(this.headerDiv).append(this.contentDiv);
}

window.egw_fw_ui_category.prototype.open = function(_instantly)
{
	this.callback.call(this, true);
	jQuery(this.headerDiv).addClass('egw_fw_ui_category_active')
		.attr("aria-expanded",true);

	if (_instantly)
	{
		jQuery(this.contentDiv).show();
		this.animationCallback();
	}
	else
	{
		jQuery(this.contentDiv).slideDown(200, function() {
			this._parent.animationCallback.call(this._parent);
		});
	}
	jQuery("li:first-child", this.headerDiv).eq(0).focus();
};

window.egw_fw_ui_category.prototype.close = function(_instantly)
{
	this.callback.call(this, false);
	jQuery(this.headerDiv).removeClass('egw_fw_ui_category_active')
		.attr("aria-expanded", false);

	if (_instantly)
	{
		jQuery(this.contentDiv).hide();
		this.animationCallback();
	}
	else
	{
		jQuery(this.contentDiv).slideUp(200, function() {
			this._parent.animationCallback.call(this._parent);
		});
	}
};

window.egw_fw_ui_category.prototype.remove = function()
{
	//Delete the content and header div
	jQuery(this.contDiv).remove();
	jQuery(this.headerDiv).remove();
};

/**
 * egw_fw_ui_scrollarea class
 *
 * @param {object} _contDiv
 */

window.egw_fw_ui_scrollarea = function(_contDiv)
{
	this.startScrollSpeed = 50.0; //in px/sec
	this.endScrollSpeed = 250.0; //in px/sec
	this.scrollSpeedAccel = 75.0; //in px/sec^2
	this.timerInterval = 0.04; //in seconds  //20ms is the timer base timer resolution on windows systems

	this.contDiv = _contDiv;
	this.contHeight = 0;
	this.boxHeight = 0;
	this.scrollPos = 0;
	this.buttonScrollOffs = 0;
	this.maxScrollPos = 0;
	this.buttonsVisible = true;
	this.mouseOver = false;
	this.scrollTime = 0.0;
	this.btnUpEnabled = true;
	this.btnDownEnabled = true;

	//Wrap a new "scroll" div around the content of the content div
	this.scrollDiv = document.createElement("div");
	this.scrollDiv.style.position = "relative";
	jQuery(this.scrollDiv).addClass("egw_fw_ui_scrollarea");

	//Mousewheel handler
	var self = this;
	jQuery(this.scrollDiv).on('mousewheel',function(e, delta) {
		var noscroll = false;

		// Do not scrolldown/up when we are on selectbox items
		// seems Firefox does not prevent the mousewheel event over
		// selectbox items with scrollbars
		if (e.target.tagName == "OPTION" || e.target.tagName == "SELECT")
		{
			noscroll = true;
		}
		if (delta && !noscroll)
		{
			e.stopPropagation();
			self.scrollDelta(- delta * 30);
			if (self.contHeight != this.scrollHeight) self.update();
		}

	});

	//Create a container which contains the up/down buttons and the scrollDiv
	this.outerDiv = document.createElement("div");
	jQuery(this.outerDiv).addClass("egw_fw_ui_scrollarea_outerdiv");
	jQuery(this.outerDiv).append(this.scrollDiv);

	jQuery(this.contDiv).children().appendTo(this.scrollDiv);
	jQuery(this.contDiv).append(this.outerDiv);
	this.contentDiv = this.scrollDiv;

	//Create the "up" and the "down" button
	this.btnUp = document.createElement("span");
	jQuery(this.btnUp).addClass("egw_fw_ui_scrollarea_button");
	jQuery(this.btnUp).addClass("egw_fw_ui_scrollarea_button_up");
	jQuery(this.btnUp).hide();

	this.btnUp._parent = this;
	jQuery(this.btnUp).mouseenter(function(){
		this._parent.mouseOverToggle(true, -1);
		jQuery(this).addClass("egw_fw_ui_scrollarea_button_hover");
	});
	jQuery(this.btnUp).click(function(){
		this._parent.setScrollPos(0);
	});
	jQuery(this.btnUp).mouseleave(function(){
		this._parent.mouseOverToggle(false, -1);
		jQuery(this).removeClass("egw_fw_ui_scrollarea_button_hover");
	});

	jQuery(this.outerDiv).prepend(this.btnUp);

	this.btnDown = document.createElement("span");
	jQuery(this.btnDown).addClass("egw_fw_ui_scrollarea_button");
	jQuery(this.btnDown).addClass("egw_fw_ui_scrollarea_button_down");
	jQuery(this.btnDown).hide();

	this.btnDown._parent = this;
	jQuery(this.btnDown).mouseenter(function(){
		this._parent.mouseOverToggle(true, 1);
		jQuery(this).addClass("egw_fw_ui_scrollarea_button_hover");
	});
	jQuery(this.btnDown).click(function() {
		this._parent.setScrollPos(this._parent.maxScrollPos);
	});
	jQuery(this.btnDown).mouseleave(function(){
		this._parent.mouseOverToggle(false, 1);
		jQuery(this).removeClass("egw_fw_ui_scrollarea_button_hover");
	});

	jQuery(this.outerDiv).prepend(this.btnDown);

	//Update - read height of the children elements etc.
	this.update();
}

window.egw_fw_ui_scrollarea.prototype.setScrollPos = function(_pos)
{
	var scrollArea = egw.preference('scroll_area', 'common') == 1 ? true : false;
	if (this.buttonsVisible)
	{
		if (scrollArea)
		{
			if (_pos <= 0)
			{
				if (this.btnUpEnabled)
					jQuery(this.btnUp).addClass("egw_fw_ui_scrollarea_button_disabled");
				if (!this.btnDownEnabled)
					jQuery(this.btnDown).removeClass("egw_fw_ui_scrollarea_button_disabled");
				this.btnDownEnabled = true;
				this.btnUpEnabled = false;
				_pos = 0;
			}
			else if (_pos >= this.maxScrollPos)
			{
				if (this.btnDownEnabled)
					jQuery(this.btnDown).addClass("egw_fw_ui_scrollarea_button_disabled");
				if (!this.btnUpEnabled)
					jQuery(this.btnUp).removeClass("egw_fw_ui_scrollarea_button_disabled");
				this.btnDownEnabled = false;
				this.btnUpEnabled = true;
				_pos = this.maxScrollPos;
			}
			else
			{
				if (!this.btnUpEnabled)
					jQuery(this.btnUp).removeClass("egw_fw_ui_scrollarea_button_disabled");
				if (!this.btnDownEnabled)
					jQuery(this.btnDown).removeClass("egw_fw_ui_scrollarea_button_disabled");
				this.btnUpEnabled = true;
				this.btnDownEnabled = true;
			}
		}
		else
		{
			if (_pos <= 0)
			{
				_pos = 0;
			}
			else if (_pos >= this.maxScrollPos)
			{
				_pos = this.maxScrollPos;
			}
			jQuery(this.btnUp).addClass("egw_fw_ui_scrollarea_button_disabled");
			jQuery(this.btnDown).addClass("egw_fw_ui_scrollarea_button_disabled");
		}
		this.scrollPos = _pos;

		//Apply the calculated scroll position to the scrollDiv
		this.scrollDiv.style.top = Math.round(-_pos) + 'px';
	}
};

window.egw_fw_ui_scrollarea.prototype.scrollDelta = function(_delta)
{
	this.setScrollPos(this.scrollPos + _delta);
};

window.egw_fw_ui_scrollarea.prototype.toggleButtons = function(_visible)
{
	if (_visible)
	{
		jQuery(this.btnDown).show();
		jQuery(this.btnUp).show();
		this.buttonHeight = jQuery(this.btnDown).outerHeight();
		this.maxScrollPos = this.contHeight - this.boxHeight;
		this.setScrollPos(this.scrollPos);
	}
	else
	{
		this.scrollDiv.style.top = '0';
		jQuery(this.btnDown).hide();
		jQuery(this.btnUp).hide();
	}

	this.buttonsVisible = _visible;
};

window.egw_fw_ui_scrollarea.prototype.update = function()
{
	//Get the height of the content and the outer box
	this.contHeight = jQuery(this.scrollDiv).outerHeight();
	this.boxHeight = jQuery(this.contDiv).height();

	this.outerDiv.scrollIntoView();
	this.toggleButtons(this.contHeight > this.boxHeight);
	this.setScrollPos(this.scrollPos);
};

window.egw_fw_ui_scrollarea.prototype.getScrollDelta = function(_timeGap)
{
	//Calculate the current scroll speed
	var curScrollSpeed = this.startScrollSpeed + this.scrollSpeedAccel * this.scrollTime;
	if (curScrollSpeed > this.endScrollSpeed)
	{
		curScrollSpeed = this.endScrollSpeed;
	}

	//Increment the scroll time counter
	this.scrollTime = this.scrollTime + _timeGap;

	//Return the actual delta value
	return curScrollSpeed * _timeGap;
};

window.egw_fw_ui_scrollarea.prototype.mouseOverCallback = function(_context)
{
	//Do the scrolling
	_context.scrollDelta(_context.getScrollDelta(_context.timerInterval) *
		_context.dir);

	if (_context.mouseOver)
	{
		//Set the next timeout
		setTimeout(function(){_context.mouseOverCallback(_context);},
			Math.round(_context.timerInterval * 1000));
	}
};

window.egw_fw_ui_scrollarea.prototype.mouseOverToggle = function(_over, _dir)
{
	this.mouseOver = _over;
	this.dir = _dir;
	this.update();
	if (_over)
	{
		var _context = this;
		setTimeout(function(){_context.mouseOverCallback(_context);},
			Math.round(_context.timerInterval * 1000));
	}
	else
	{
		this.scrollTime = 0.0;
	}
};

/**
 * egw_fw_ui_splitter class
 */

window.EGW_SPLITTER_HORIZONTAL = 0;
window.EGW_SPLITTER_VERTICAL = 1;

window.egw_fw_ui_splitter = function(_contDiv, _orientation, _resizeCallback, _constraints, _tag)
{
	//Copy the parameters
	this.tag = _tag;
	this.contDiv = _contDiv;
	this.orientation = _orientation;
	this.resizeCallback = _resizeCallback;
	this.startPos = 0;
	this.constraints =
	[
		{
			"size": 0,
			"minsize": 0,
			"maxsize": 0
		},
		{
			"size": 0,
			"minsize": 0,
			"maxsize": 0
		}
	];

	//Copy the given constraints parameter, keeping the default values set above
	if (_constraints.constructor == Array)
	{
		for (var i = 0; i < 2; i++)
		{
			if (typeof _constraints[i] != 'undefined')
			{
				if (typeof _constraints[i].size != 'undefined')
					this.constraints[i].size = _constraints[i].size;
				if (typeof _constraints[i].minsize != 'undefined')
					this.constraints[i].minsize = _constraints[i].minsize;
				if (typeof _constraints[i].maxsize != 'undefined')
					this.constraints[i].maxsize = _constraints[i].maxsize;
			}
		}
	}

	//Create the actual splitter div
	this.splitterDiv = document.createElement('div');
	this.splitterDiv._parent = this;
	jQuery(this.splitterDiv).addClass("egw_fw_ui_splitter");

	//Setup the options for the dragable object
	var dragoptions = {
		listeners : {
			start(event)
			{
				return event.currentTarget._parent.dragStartHandler.call(event.currentTarget._parent, event);
			},
			move (event)
			{
				return event.currentTarget._parent.dragHandler.call(event.currentTarget._parent, event);
			},
			end (event)
			{
				return event.currentTarget._parent.dragStopHandler.call(event.currentTarget._parent, event);
			}
		}
	};

	switch (this.orientation)
	{
		case EGW_SPLITTER_HORIZONTAL:
			dragoptions.startAxis = 'y';
			jQuery(this.splitterDiv).addClass("egw_fw_ui_splitter_horizontal");
			break;
		case EGW_SPLITTER_VERTICAL:
			dragoptions.startAxis = 'x';
			jQuery(this.splitterDiv).addClass("egw_fw_ui_splitter_vertical");
			break;
	}
	interact(this.splitterDiv)
		.styleCursor(false)
		.draggable(dragoptions);


	//Handle mouse hovering of the splitter div
	jQuery(this.splitterDiv).mouseenter(function() {
		jQuery(this).addClass("egw_fw_ui_splitter_hover");
	});
	jQuery(this.splitterDiv).mouseleave(function() {
		jQuery(this).removeClass("egw_fw_ui_splitter_hover");
	});

	jQuery(this.contDiv).append(this.splitterDiv);
}

window.egw_fw_ui_splitter.prototype.clipDelta = function(_delta)
{
	var result = _delta;

	for (var i = 0; i < 2; i++)
	{
		var mul = (i == 0) ? 1 : -1;

		if (this.constraints[i].maxsize > 0)
		{
			var size = this.constraints[i].size + mul * result;
			if (size > this.constraints[i].maxsize)
				result += mul * (this.constraints[i].maxsize - size);
		}

		if (this.constraints[i].minsize > 0)
		{
			var size = this.constraints[i].size + mul * result;
			if (size < this.constraints[i].minsize)
				result += mul * (this.constraints[i].minsize - size);
		}
	}

	return result;
};

window.egw_fw_ui_splitter.prototype.dragStartHandler = function(event)
{
	switch (this.orientation)
	{
		case EGW_SPLITTER_HORIZONTAL:
			this.startPos = event.clientY0;
			break;
		case EGW_SPLITTER_VERTICAL:
			this.startPos = event.clientX0;
			break;
	}
};

window.egw_fw_ui_splitter.prototype.dragHandler = function(event)
{
	var delta = 0;
	switch (this.orientation)
	{
		case EGW_SPLITTER_HORIZONTAL:
			delta = event.delta.y;
			break;
		case EGW_SPLITTER_VERTICAL:
			delta = event.delta.x;
			break;
	}

	//Clip the delta value
	this.constraints[0].size += delta;
	this.constraints[1].size -= delta;

	// reset the size to minsize if it's dragged beyond it
	if (this.constraints[0].size + delta <= this.constraints[0].minsize)
	{
		this.constraints[0].size = 	this.constraints[0].minsize;
		return;
	}

	this.resizeCallback(this.constraints[0].size, this.constraints[1].size);
};


window.egw_fw_ui_splitter.prototype.dragStopHandler = function(event)
{
	this.resizeCallback(this.constraints[0].size, this.constraints[1].size);
};

/**
 * Disable/Enable drabbale splitter
 * @param {type} _state
 */
window.egw_fw_ui_splitter.prototype.set_disable = function (_state)
{
	interact(this.splitterDiv).options.drag.enabled = !_state;
};

/**
 * Constructor for toggleSidebar UI object
 *
 * @param {type} _contentDiv sidemenu div
 * @param {function} _toggleCallback callback function to set toggle prefernces and resize handling
 * @param {object} _callbackContext context of the toggleCallback
 * @returns {egw_fw_ui_toggleSidebar}
 */
window.egw_fw_ui_toggleSidebar = function(_contentDiv, _toggleCallback, _callbackContext)
{
	var self = this;
	this.toggleCallback = _toggleCallback;
	this.toggleDiv = jQuery(document.createElement('div'))
			.attr({id:"egw_fw_toggler", title:egw.lang("show/hide")})
			.addClass('noPrint')
			.click(function(){
				self.onToggle(_callbackContext);
			});
	var span = jQuery(document.createElement('span')).addClass('et2_clickable').appendTo(this.toggleDiv);

	if (egw.preference('audio_effect', 'common') === "1")
	{
		this.toggleAudio = jQuery(document.createElement('audio'))
				.attr({src:"data:audio/mp3;base64, "})
				.appendTo(this.toggleDiv);
	}
	this.contDiv = jQuery(_contentDiv);
	this.contDiv.prepend(this.toggleDiv);
};

/**
 * Toggle menu on/off
 * @param {object} _callbackContext context of the toggleCallback
 */
window.egw_fw_ui_toggleSidebar.prototype.onToggle = function(_callbackContext)
{
	if (typeof this.toggleAudio != 'undefined') this.toggleAudio[0].play();
	if (this.contDiv.hasClass('egw_fw_sidebar_toggleOn'))
	{
		this.contDiv.removeClass('egw_fw_sidebar_toggleOn');
		var splitter = _callbackContext.splitterUi;
		splitter.set_disable(false);
		this.toggleCallback.call(_callbackContext,'off');
		window.setTimeout(function() {
			jQuery(window).resize();
		},500);
	}
	else
	{
		this.contDiv.addClass('egw_fw_sidebar_toggleOn');
		_callbackContext.splitterUi.set_disable(true);
		this.toggleCallback.call(_callbackContext, 'on');
	}
};

/**
 * Set sidebar toggle state
 *
 * @param {string} _state state can be 'on' or 'off'
 * @param {type} _toggleCallback callback function to handle toggle preference and resize
 * @param {type} _context context of callback function
 */
window.egw_fw_ui_toggleSidebar.prototype.set_toggle = function (_state, _toggleCallback, _context)
{
	this.contDiv.toggleClass('egw_fw_sidebar_toggleOn',_state === 'on'?true:false);
	_context.splitterUi.set_disable(_state === 'on'?true:false);
	_toggleCallback.call(_context, _state);
};