Greasy Fork

Ikariam Core

Framework for Ikariam userscript developers.

目前为 2014-10-08 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.icu/scripts/5574/20444/Ikariam%20Core.js

/**
 * Additional methods for processing strings.
 * 
 * @namespace	String
 */
/**
 * Additional methods for processing arrays.
 * 
 * @namespace	Array
 */
// This curly bracket is for easy folding of all prototype methods and has not other sense. Please don't remove. :)
{
	/**
	 * Replaces characters or whitespaces at the beginning of a string.
	 *
	 * @param	{string}	toRemove
	 *   A string containing the characters to remove (optional, if not set: trim whitespaces).
	 *
	 * @return	{string}
	 *   The trimmed string.
	 */
	String.prototype.ltrim = function(toRemove) {
		// Is there a string with the characters to remove?
		var special = !!toRemove;
	
		// Return the trimmed string.
		return special ? this.replace(new RegExp('^[' + toRemove + ']+'), '') : this.replace(/^\s+/, '');
	};
	
	/**
	 * Replaces characters or whitespaces at the end of a string.
	 *
	 * @param	{string}	toRemove
	 *   A string containing the characters to remove (optional, if not set: trim whitespaces).
	 *
	 * @return	{string}
	 *   The trimmed string.
	 */
	String.prototype.rtrim = function(toRemove) {
		// Is there a string with the characters to remove?
		var special = !!toRemove;
	
		// Return the trimmed string.
		return special ? this.replace(new RegExp('[' + toRemove + ']+$'), '') : this.replace(/\s+$/, '');
	};
	
	/**
	 * Replaces characters or whitespaces at the beginning and end of a string.
	 *
	 * @param	{string}	toRemove
	 *   A string containing the characters to remove (optional, if not set: trim whitespaces).
	 *
	 * @return	{string}
	 *   The trimmed string.
	 */
	String.prototype.trim = function(toRemove) {
		return this.ltrim(toRemove).rtrim(toRemove);
	};
	
	/**
	 * Encodes HTML-special characters in a string.
	 *
	 * @return	{string}
	 *   The encoded string.
	 */
	String.prototype.encodeHTML = function() {
		// Set the characters to encode.
		var characters = {
			'&':	'&',
			'"':	'"',
			'\'':	''',
			'<':	'&lt;',
			'>':	'&gt;'
		};
		
		// Return the encoded string.
		return this.replace(/([\&"'<>])/g, function(string, symbol) { return characters[symbol]; });
	};
	
	/**
	 * Decodes HTML-special characters in a string.
	 *
	 * @return	{string}
	 *   The decoded string.
	 */
	String.prototype.decodeHTML = function() {
		// Set the characters to decode.
		var characters = {
			'&amp;':	'&',
			'&quot;':	'"',
			'&apos;':	'\'',
			'&lt;':		'<',
			'&gt;':		'>'
		};
		
		// Return the decoded string.
		return this.replace(/(&quot;|&apos;|&lt;|&gt;|&amp;)/g, function(string, symbol) { return characters[symbol]; });
	};
	
	/**
	 * Repeats a string a specified number of times.
	 * 
	 * @param	{int}	nr
	 *   The number of times to repeat the string.
	 * 
	 * @return	{string}
	 *   The repeated string.
	 */
	String.prototype.repeat = function(nr) {
		var ret = this;
		
		// Repeat the string.
		for(var i = 1; i < nr; i++) {
			ret += this;
		}
		
		return ret;
	};
	
	/**
	 * Inserts an element at a specified position into an array.
	 * 
	 * @param	{mixed}	item
	 *   The item which should be inserted.
	 * @param	{int}	index
	 *   The position where the element should be added. If not set, the element will be added at the end.
	 */
	Array.prototype.insert = function (item, index) {
		var maxIndex = this.length;
		
		// Get the index to insert.
		index = !index && index != 0 ? maxIndex : index;
		index = Math.max(index, 0);			// No negative index.
		index = Math.min(index, maxIndex);	// No index bigger than the array length.
		
		this.splice(index, 0, item);
	};
	
	/**
	 * Deletes an element at a specified position from an array.
	 * 
	 * @param	{int}	index
	 *   The position of the element which should be deleted.
	 */
	Array.prototype.remove = function(index) {
		if(index >= 0 && index < this.length - 1) {
			this.splice(index, 1);
		}
	};
}

/**
 * Instantiate a new set of core functions.<br>
 * {@link https://www.assembla.com/spaces/ikariam-tools/ Script homepage}
 * 
 * @version	1.0
 * @author	Tobbe	<[email protected]>
 * 
 * @global
 * 
 * @class
 * @classdesc	Core functions for Ikariam.
 */
function IkariamCore() {
	/**
	 * Storage for accessing <code>this</code> as reference to IkariamCore in subfunctions. Do <b>NOT</b> delete!
	 * 
	 * @private
	 * @inner
	 * 
	 * @type	IkariamCore
	 */
	var _this = this;
	
	/**
	 * A reference to the window / unsafeWindow.
	 * 
	 * @instance
	 * 
	 * @type	window
	 */
	this.win = typeof unsafeWindow != 'undefined' ? unsafeWindow : window;
	
	/**
	 * Reference to window.ikariam.
	 * 
	 * @instance
	 * 
	 * @type	object
	 */
	this.ika = this.win.ikariam;
	
	// Set the console to the "rescued" debugConsole.
	var _console = this.win.debugConsole;
	
	// If debugging is disabled or the debug console not available, set all functions to "null".
	if(!scriptInfo.debug || !_console) {
		_console = {};
	}
	
	// Define all Firebug tags.
	var _tags = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'exception',
				'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'profile', 'profileEnd',
				'table', 'time', 'timeEnd', 'timeStamp', 'trace', 'warn'];
	
	// Check all firebug functions.
	for(var i = 0; i < _tags.length; i++) {
		// Get the key.
		var _key = _tags[i];
		
		// If the function is not set yet, set it to "null".
		if(!_console[_key]) {
			_console[_key] = function() { return; };
		}
	}
	
	/**
	 * Debugging console.
	 * For more information about commands that are available for the Firebug console see {@link http://getfirebug.com/wiki/index.php/Console_API Firebug Console API}.<br>
	 * Available commands: <code>assert, clear, count, debug, dir, dirxml, error, exception, group, groupCollapsed, groupEnd,
	 * info, log, profile, profileEnd, table, time, timeEnd, timeStamp, trace, warn</code><br>
	 * <br>
	 * The console is deactivated by the ikariam page but with the script {@link https://userscripts.org/scripts/show/158528 RescueConsole} you can use it.
	 * 
	 * @instance
	 * 
	 * @type	console
	 */
	this.con = _console;

	/**
	 * Instantiate a new set of myGM functions.
	 * 
	 * @inner
	 * 
	 * @class
	 * @classdesc	Functions for cross-browser compatibility of the GM_* functions.<br>Also there are some new functions implemented.
	 */
	function myGM() {
		/*--------------------------------------------*
		 * Private variables, functions and settings. *
		 *--------------------------------------------*/
		
		/**
		 * Storage for style sheets which will be added by the script.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	element[]
		 */
		var _styleSheets = {};
		
		/**
		 * Storage for notification id for possibility to identify a notification popup.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	int
		 */
		var _notificationId = 0;
		
		/**
		 * The prefix which schuld be added to the values stored in localStorage / cookies.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	string
		 */
		var _prefix = 'script' + scriptInfo.id;
		
		/**
		 * If the Greasemonkey functions GM_setVaule, GM_getValue, GM_deleteValue and GM_listValues can be used.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	boolean
		 */
		var _canUseGmStorage = !(typeof GM_getValue == 'undefined' || (typeof GM_getValue.toString == 'function' && GM_getValue.toString().indexOf('not supported') > -1))
									&& !(typeof GM_setValue == 'undefined' || (typeof GM_setValue.toString == 'function' && GM_setValue.toString().indexOf('not supported') > -1))
									&& !(typeof GM_deleteValue == 'undefined' || (typeof GM_deleteValue.toString == 'function' && GM_deleteValue.toString().indexOf('not supported') > -1))
									&& !(typeof GM_listValues == 'undefined' || (typeof GM_listValues.toString == 'function' && GM_listValues.toString().indexOf('not supported') > -1));
		
		/**
		 * If the Greasemonkey function GM_getResourceText can be used.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	boolean
		 */
		var _canUseGmRessource = !(typeof GM_getResourceText == 'undefined' || (typeof GM_getResourceText.toString == 'function' && GM_getResourceText.toString().indexOf('not supported') > -1));
		
		/**
		 * If the Greasemonkey function GM_xmlhttpRequest can be used.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	boolean
		 */
		var _canUseGmXhr = !(typeof GM_xmlhttpRequest == 'undefined' || (typeof GM_xmlhttpRequest.toString == 'function' && GM_xmlhttpRequest.toString().indexOf('not supported') > -1));
		
		/**
		 * If the local storage can be used.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	boolean
		 */
		var _canUseLocalStorage = !!_this.win.localStorage;
		
		/*-------------------------------------------*
		 * Public variables, functions and settings. *
		 *-------------------------------------------*/
		
		/**
		 * Read only access to the script identifying prefix.
		 * 
		 * @instance
		 * 
		 * @return	{string}
		 *   The prefix for storing.
		 */
		this.prefix = function() {
			return _prefix;
		};
		
		/**
		 * Store a value specified by a key.
		 * 
		 * @instance
		 * 
		 * @param	{string}	key
		 *   The key of the value.
		 * @param	{mixed}	value
		 *   The value to store.
		 */
		this.setValue = function(key, value) {
			// Stringify the value to store also arrays.
			var toStore = _this.win.JSON.stringify(value);
	
			// If the use of the default GM_setValue ist possible, use it.
			if(_canUseGmStorage) {
				// Store the value.
				GM_setValue(key, toStore);
	
			// Otherwise use the local storage if possible.
			} else if(_canUseLocalStorage) {
				// Store the value.
				_this.win.localStorage.setItem(_prefix + key, toStore);
	
			// Otherwise use cookies.
			} else {
				// Prepare the cookie name and value.
				var data	= escape(_prefix + key) + '=' + escape(toStore);
	
				// Set the expire date to January 1st, 2020.
				var expire	= 'expires=' + (new Date(2020, 0, 1, 0, 0, 0, 0)).toGMTString();
	
				// Set the path to root.
				var path	= 'path=/';
	
				// Made the cookie accessible from all servers.
				var domain	= 'domain=ikariam.com';
	
				// Set the cookie.
				_this.win.document.cookie = data + ';' + expire + ';' + path + ';' + domain;
			}
		};
	
		/**
		 * Get a value and return it.
		 * 
		 * @instance
		 * 
		 * @param	{string}	key
		 *   The key of the value.
		 * @param	{mixed}		defaultValue
		 *   The value which is set if the value is not set.
		 *
		 * @return	{mixed}
		 *   The stored value.
		 */
		this.getValue = function(key, defaultValue) {
			// Put the default value to JSON.
			defaultValue = _this.win.JSON.stringify(defaultValue);
			
			// Storage for the value.
			var value = defaultValue;
	
			// If the use of the default GM_getValue ist possible, use it.
			if(_canUseGmStorage) {
				// Get the value.
				value = GM_getValue(key, defaultValue);
	
			// Otherwise use the local storage if possible.
			} else if(_canUseLocalStorage) {
				// Get the value.
				var valueTmp = _this.win.localStorage.getItem(_prefix + key);
	
				// If the value is not set, let the default value set.
				if(valueTmp) {
					value = valueTmp;
				}
	
			// Otherwise use cookies.
			} else {
				// Get all cookies.
				var allCookies = document.cookie.split("; ");
	
				// Loop over all cookies.
				for(var i = 0; i < allCookies.length; i++) {
					// Get the key and value of a cookie.
					var oneCookie = allCookies[i].split("=");
	
					// If the key is the correct key, get the value.
					if(oneCookie[0] == escape(_prefix + key)) {
						// Get the value and abort the loop.
						value = unescape(oneCookie[1]);
						break;
					}
				}
			}
			
			// Return the value (parsed for the correct return type).
			return _this.win.JSON.parse(value);
		};
	
		/**
		 * Delete a value specified by a key.
		 * 
		 * @instance
		 * 
		 * @param	{string}	key
		 *   The key of the value.
		 */
		this.deleteValue = function(key) {
			// If the use of the default GM_deleteValue ist possible, use it.
			if(_canUseGmStorage) {
				// Delete the value.
				GM_deleteValue(key);
	
			// Otherwise use the local storage if possible.
			} else if(_canUseLocalStorage) {
				// Remove the value.
				_this.win.localStorage.removeItem(_prefix + key);
	
			// Otherwise use cookies.
			} else {
				// Prepare the cookie name.
				var data	= escape(_prefix + key) + '=';
	
				// Set the expire date to January 1st, 2000 (this will delete the cookie).
				var expire	= 'expires=' + (new Date(2000, 0, 1, 0, 0, 0, 0)).toGMTString();
	
				// Set the path to root.
				var path	= 'path=/';
	
				// Made the cookie accessible from all servers.
				var domain	= 'domain=ikariam.com';
	
				// Set the cookie.
				_this.win.document.cookie = data + ';' + expire + ';' + path + ';' + domain;
			}
		};
	
		/**
		 * Returns an array with the keys of all values stored by the script.
		 * 
		 * @instance
		 * 
		 * @return	{mixed[]}
		 *   The array with all keys.
		 */
		this.listValues = function() {
			// Create an array for the storage of the values keys.
			var key = new Array();
	
			// If the use of the default GM_listValues ist possible, use it.
			if(_canUseGmStorage) {
				// Store the key(s) to the array.
				key = GM_listValues();
	
			// Otherwise use the local storage if possible.
			} else if(_canUseLocalStorage) {
				// Loop over all stored values.
				for(var i = 0; i < _this.win.localStorage.length; i++) {
					// Get the key name of the key with the number i.
					var keyName = _this.win.localStorage.key(i);
	
					// If the value is set by the script, push the key name to the array.
					if(keyName.indexOf(_prefix) != -1) {
						key.push(keyName.replace(_prefix, ''));
					}
				}
	
			// Otherwise use cookies.
			} else {
				// Get all cookies.
				var allCookies = document.cookie.split("; ");
	
				// Loop over all cookies.
				for(var i = 0; i < allCookies.length; i++) {
					// Get the key name of a cookie.
					var keyName = unescape(allCookies[i].split("=")[0]);
	
					// If the key value is set by the script, push the key name to the array.
					if(keyName.indexOf(_prefix) != -1) {
						key.push(keyName.replace(_prefix, ''));
					}
				}
			}
	
			// Return all keys.
			return key;
		};
	
		/**
		 * Adds a style element to the head of the page and return it.
		 * 
		 * @instance
		 * 
		 * @param	{string}	styleRules
		 *   The style rules to be set.
		 * @param	{string}	id
		 *   An id for the style set, to have the possibility to delete it. (optional, if none is set, the stylesheet is not stored)
		 * @param	{boolean}	overwrite
		 *   If a style with id should overwrite an existing style.
		 *
		 * @return	{boolean}
		 *    If the stylesheet was stored with the id.
		 */
		this.addStyle = function(styleRules, id, overwrite) {
			// If the element was stored is saved here.
			var storedWithId = false;
	
			// If overwrite, remove the old style sheet.
			if(overwrite && overwrite == true) {
				this.removeStyle(id);
			}
	
			// If the stylesheet doesn't exists.
			if(!id || (id && !_styleSheets[id])) {
				// Create a new style element and set the style rules.
				var style = this.addElement('style', document.head);
				style.type	= 'text/css';
				style.innerHTML	= styleRules;
	
				// If an id is set, store it.
				if(id) {
					_styleSheets[id] = style;
					storedWithId = true;
				}
			}
	
			// Return if the stylesheet was stored with an id.
			return storedWithId;
		};
	
		/**
		 * Removes a style element set by the script.
		 * 
		 * @instance
		 * 
		 * @param	{string}	id
		 *   The id of the stylesheet to delete.
		 *
		 * @return	{boolean}
		 *    If the stylesheet could be deleted.
		 */
		this.removeStyle = function(id) {
			// Stores if the stylesheet could be removed.
			var removed = false;
	
			// If there is an id set and a stylesheet with the id exists.
			if(id && _styleSheets[id]) {
				// Remove the stylesheet from the page.
				document.head.removeChild(_styleSheets[id]);
	
				// Remove the stylesheet from the array.
				delete	_styleSheets[id];
	
				// Set removed to true.
				removed = true;
			}
	
			// Return if the stylesheet could be removed.
			return removed;
		};
		
		/**
		 * Makes a cross-site XMLHttpRequest.
		 * 
		 * @instance
		 * 
		 * @param	{mixed[]}	args
		 *   The arguments the request needs. (specified here: {@link http://wiki.greasespot.net/GM_xmlhttpRequest GM_xmlhttpRequest})
		 * 
		 * @return	{mixed}
		 *   The response text or a hint indicating an error.
		 */
		this.xhr = function(args) {
			// Storage for the result of the request.
			var responseText;
	
			// Check if all required data is given.
			if(!args.method || !args.url || !args.onload) {
				return false;
			}
	
			// If the use of the default GM_xmlhttpRequest ist possible, use it.
			if(_canUseGmXhr) {
				// Sent the request.
				var response = GM_xmlhttpRequest(args);
	
				// Get the response text.
				responseText = response.responseText;
	
			// Otherwise show a hint for the missing possibility to fetch the data.
			} else {
				// Storage if the link fetches metadata from userscripts.org
				var isJSON		= (args.url.search(/\.json$/i) != -1);
	
				// Otherwise if it is JSON.
				if(isJSON) {
					// Do the request with a string indicating the error.
					args.onload('{ "is_error": true }');
	
					// Return a string indicating the error.
					responseText = '{ "is_error": true }';
	
				// Otherwise.
				} else {
					responseText = false;
				}
			}
	
			// Return the responseText.
			return responseText;
		};
		
		/**
		 * Returns the content of a resource parsed with JSON.parse.
		 * 
		 * @instance
		 * 
		 * @param	{string}	name
		 *   The name of the resource to parse.
		 */
		this.getResourceParsed = function(name, xhrUrl) {
			// Storage for the response text.
			var responseText = '';
	
			// Function for safer parsing.
			var safeParse = function(key, value) {
				// If the value is a function, return just the string, so it is not executable.
				if(typeof value === 'function' || Object.prototype.toString.apply(value) === '[object function]') {
					return value.toString();
				}

				// Return the value.
				return value;
			};
	
			// If the use of the default GM_getRessourceText ist possible, use it.
			if(_canUseGmRessource) {
				// Set the parsed text.
				responseText = GM_getResourceText(name);
	
			// Otherwise perform a xmlHttpRequest.
			} else {
				// Perform the xmlHttpRequest.
				responseText = this.xhr({
					method:			'GET',
					url:			xhrUrl,
					headers:		{ 'User-agent': navigator.userAgent, 'Accept': 'text/html' },
					synchronous:	true,
					onload:			function(response) { return false; }
				});
			}
	
			// Return the parsed resource text.
			return _this.win.JSON.parse(responseText, safeParse);
		};
		
		/**
		 * Gets the first matching child element by a query and returns it.
		 * 
		 * @instance
		 * 
		 * @param	{string}	query
		 *   The query for the element.
		 * @param	{element}	parent
		 *   The parent element. (optional, default document)
		 *
		 * @return	{element}
		 *   The element.
		 */
		this.$ = function(query, parent) {
			return this.$$(query, parent)[0];
		};
	
		/**
		 * Gets all matching child elements by a query and returns them.
		 * 
		 * @instance
		 * 
		 * @param	{string}	query
		 *   The query for the elements.
		 * @param	{element}	parent
		 *   The parent element. (optional, default document)
		 *
		 * @return	{element[]}
		 *   The elements.
		 */
		this.$$ = function(query, parent) {
			// If there is no parent set, set it to document.
			if(!parent)	parent = document;
			
			// Return the elements.
			return Array.prototype.slice.call(parent.querySelectorAll(query));
		};
		
		/**
		 * Returns the value of the selected option of a select field.
		 *
		 * @param	{string}	id
		 *   The last part of the id of the element.
		 * @param	{boolean}	hasNoPrefix
		 *   Says if the id has no prefix.
		 * @param	{boolean}	addNoSelect
		 *   Says if there should not be added a "Select" at the end of the id.
		 *
		 * @return	{string}
		 *   The value.
		 */
		this.getSelectValue = function(id, hasNoPrefix, addNoSelect) {
			// Get the select field.
			var select = this.$('#' + (hasNoPrefix ? '' : _prefix) + id + (addNoSelect ? '' : 'Select'));
	
			// Return the value.
			return select.options[select.selectedIndex].value;
		};
		
		/**
		 * Creates a new element and adds it to a parent.
		 * 
		 * @instance
		 * 
		 * @param	{string}				type
		 *   The type of the new element.
		 * @param	{element}				parent
		 *   The parent of the new element.
		 * @param	{string}				id
		 *   The last part of the id of the element. (optional, if not set, no id will be set)
		 * @param	{string || String[]}	classes
		 *   The class(es) of the element. (optional, if not set, no class will be set)
		 * @param	{mixed[]}				style
		 *   The styles of the element. (optional, if not set, no style will be set)
		 * @param	{boolean || boolean[]}	hasPrefix
		 *   If no prefix should be used. (optional, if not set, a prefix will be used for id and no prefix will be used for classes)
		 * @param	{element}				nextSib
		 *   The next sibling of the element. (optional, if not set, the element will be added at the end)
		 *
		 * @return	{element}
		 *   The new element.
		 */
		this.addElement = function(type, parent, id, classes, style, hasPrefix, nextSib) {
			// Create the new Element.
			var newElement = document.createElement(type);
	
			// If there is a id, set it.
			if(id) {
				// Get the id prefix.
				var idPrefix = !(hasPrefix == false || (hasPrefix && hasPrefix.id == false)) ? _prefix : '';
	
				// Set the id.
				newElement.id = idPrefix + id;
			}
	
			// Add all classes.
			if(classes && classes != '') {
				// Get the class prefix.
				var classPrefix = !!(hasPrefix == true || (hasPrefix && hasPrefix.classes == true)) ? _prefix : '';
	
				// Set the class(es).
				if(typeof classes == 'string') {
					newElement.classList.add(classPrefix + classes);
				} else {
					for(var i = 0; i < classes.length; i++) {
						if(classes[i] != '') {
							newElement.classList.add(classPrefix + classes[i]);
						}
					}
				}
			}
	
			if(style) {
				for(var i = 0; i < style.length; i++) {
					newElement.style[style[i][0]] = style[i][1];
				}
			}
	
			// Insert the element.
			parent.insertBefore(newElement, nextSib);
	
			// Return the new element.
			return newElement;
		};
		
		/**
		 * Creates new checkboxes and adds it to a parent.
		 * 
		 * @instance
		 * 
		 * @param	{element}	parent
		 *   The parent of the new checkboxes.
		 * @param	{mixed[]}	cbData
		 *   An array containing the data (id, label, checked) of each checkbox.
		 */
		this.addCheckboxes = function(parent, cbData) {
			// Create the checkboxes.
			for(var i = 0; i < cbData.length; i++) {
				// Create the wrapper for the checkbox and the label.
				var wrapper	= _this.myGM.addElement('div', parent, null, 'cbWrapper');
				
				// Create the checkbox and set the attributes.
				var cb		= _this.myGM.addElement('input', wrapper, cbData[i]['id'] + 'Cb', 'checkbox');
				cb.type		= 'checkbox';
				cb.title	= cbData[i]['label'];
				cb.checked	= cbData[i]['checked'] ? 'checked' : '';
			}
			
			// Replace the checkboxes for better appearance.
			_this.ika.controller.replaceCheckboxes();
		};
	
		/**
		 * Creates a new select field and adds it to a parent table.
		 * 
		 * @instance
		 * 
		 * @param	{element}	parentTable
		 *   The parent table of the new select field.
		 * @param	{string}	id
		 *   The last part of the id of the select field.
		 * @param	{mixed}		selected
		 *   The value of the selected option.
		 * @param	{mixed[]}	opts
		 *   An array with the names an values of the options.<br>
		 *   Signature: <code>[{ value: 'val', name: 'name' }]</code>
		 * @param	{string}	labelText
		 *   The text of the select label.
		 */
		this.addSelect = function(parentTable, id, selected, opts, labelText) {
			// Create table row.
			var tr	= _this.myGM.addElement('tr', parentTable);
			
			// Create cells.
			var labelCell	= _this.myGM.addElement('td', tr);
			var selectCell	= _this.myGM.addElement('td', tr, null, 'left');
			
			// Create label.
			var selectLabel			= _this.myGM.addElement('span', labelCell);
			selectLabel.innerHTML	= labelText;
			
			// Create the wrapper for the select.
			var wrapper	= _this.myGM.addElement('div', selectCell, id + 'SelectContainer', ['select_container', 'size175'], new Array(['position', 'relative']));
			
			// Create the select field.
			var select	= _this.myGM.addElement('select', wrapper, id + 'Select', 'dropdown');
			
			// Add the Options.
			for(var i = 0; i < opts.length; i++) {
				// Create an option.
				var option			= _this.myGM.addElement('option', select);
				
				// Set the value and the name.
				option.value		= opts[i].value;
				option.innerHTML	= opts[i].name;
				
				// If the option is selected, set selected to true.
				if(option.value == selected) {
					option.selected = 'selected';
				}
			}
			
			// Replace the dropdown for better appearance.
			_this.ika.controller.replaceDropdownMenus();
		};
	
		/**
		 * Creates a button and adds it to a parent.
		 * 
		 * @instance
		 * 
		 * @param	{element}	parent
		 *   The parent element.
		 * @param	{string}	value
		 *   The value of the button.
		 * @param	{function}	callback
		 *   A callback which should be called when the user clicks on the button.<br>
		 *   Signature: <code>function() : void</code>
		 */
		this.addButton = function(parent, value, callback) {
			// Create the button wrapper.
			var buttonWrapper		= _this.myGM.addElement('div', parent, null, 'centerButton');
			
			// Create the button.
			var button			= _this.myGM.addElement('input', buttonWrapper, null, 'button');
			button.type			= 'button';
			button.value		= value;
			
			// Add a click action to the button.
			button.addEventListener('click', callback, false);
			
			return button;
		};
		
		/**
		 * Shows a notification to the user. You can either create a notification field or an input / output field.
		 * If the field should be an input field, the field is given to the callbacks as parameter.
		 * The abort button is only shown if the abort callback is set.
		 * Also it is possible to have two body parts or just one body part.
		 * This functionality is set by the notification text.<br><br>
		 * 
		 * Possible notification texts:<br>
		 * <code>&#09;text.header (optional)<br>
		 * &#09;text.body or text.bodyTop & text.bodyBottom<br>
		 * &#09;text.confirm (optional)<br>
		 * &#09;text.abort (optional)</code>
		 * 
		 * @instance
		 * 
		 * @param	{string[]}		text
		 *   The notification texts.
		 * @param	{function[]}	callback
		 *   The callbacks for confirm and abort. (optional, default: close panel)<br>
		 *   Signature with input: <code>function(textarea : element) : void</code>
		 *   Signature without input:  <code>function() : void</code>
		 * @param	{boolean}		input
		 *   If a input field should be used. (optional, default: false)
		 * 
		 * @return	{int}
		 *   The notification id.
		 */
		this.notification = function(text, callback, input) {
			// Raise the notification id.
			_notificationId += 1;
			
			// Set a local notification id to be able to have more than 1 notification panels.
			var localNotificationId = _notificationId;
			
			// Function to close the notification panel.
			var closeNotificationPanel = function() {
				// Remove the notification background.
				document.body.removeChild(_this.myGM.$('#' + _prefix + 'notificationBackground' + localNotificationId));
	
				// Remove the notification panel.
				document.body.removeChild(_this.myGM.$('#' + _prefix + 'notificationPanelContainer' + localNotificationId));
			};
			
			// Create the background and the container.
			this.addElement('div', document.body, 'notificationBackground' + localNotificationId, 'notificationBackground', null, true);
			var notificationPanelContainer		= this.addElement('div', document.body, 'notificationPanelContainer' + localNotificationId, 'notificationPanelContainer', null, true);
			var notificationPanel				= this.addElement('div', notificationPanelContainer, 'notificationPanel' + localNotificationId, 'notificationPanel', null, true);
	
			// Create the notification panel header.
			var notificationPanelHeader			= this.addElement('div', notificationPanel, 'notificationPanelHeader' + localNotificationId, 'notificationPanelHeader', null, true);
			var notificationPanelHeaderL		= this.addElement('div', notificationPanelHeader, 'notificationPanelHeaderL' + localNotificationId, 'notificationPanelHeaderL', null, true);
			var notificationPanelHeaderR		= this.addElement('div', notificationPanelHeaderL, 'notificationPanelHeaderR' + localNotificationId, 'notificationPanelHeaderR', null, true);
			var notificationPanelHeaderM		= this.addElement('div', notificationPanelHeaderR, 'notificationPanelHeaderM' + localNotificationId, 'notificationPanelHeaderM', null, true);
			notificationPanelHeaderM.innerHTML	= (text.header ? text.header : _this.Language.$('default_notification_header'));
			var notificationPanelClose			= this.addElement('div', notificationPanelHeaderM, 'notificationPanelClose' + localNotificationId, 'notificationPanelClose', null, true);
			notificationPanelClose.addEventListener('click', closeNotificationPanel, false);
			
			// Create the notification panel body.
			var notificationPanelBody			= this.addElement('div', notificationPanel, 'notificationPanelBody' + localNotificationId, 'notificationPanelBody', null, true);
			var notificationPanelBodyL			= this.addElement('div', notificationPanelBody, 'notificationPanelBodyL' + localNotificationId, 'notificationPanelBodyL', null, true);
			var notificationPanelBodyR			= this.addElement('div', notificationPanelBodyL, 'notificationPanelBodyR' + localNotificationId, 'notificationPanelBodyR', null, true);
			var notificationPanelBodyM			= this.addElement('div', notificationPanelBodyR, 'notificationPanelBodyM' + localNotificationId, 'notificationPanelBodyM', null, true);
			var bodyType = input ? 'textarea' : 'div';
			var body;
			if(text.body) {
				var notificationPanelBodyMContent		= this.addElement(bodyType, notificationPanelBodyM, 'notificationPanelBodyMContent' + localNotificationId, 'notificationPanelBodyMContent', null, true);
				notificationPanelBodyMContent.innerHTML	= text.body;
				body = input ? notificationPanelBodyMContent : null;
			} else {
				var notificationPanelBodyMTop			= this.addElement('div', notificationPanelBodyM, 'notificationPanelBodyMTop' + localNotificationId, 'notificationPanelBodyMTop', null, true);
				notificationPanelBodyMTop.innerHTML		= text.bodyTop ? text.bodyTop : '';
				var notificationPanelBodyMBottom		= this.addElement(bodyType, notificationPanelBodyM, 'notificationPanelBodyMBottom' + localNotificationId, 'notificationPanelBodyMBottom', null, true);
				notificationPanelBodyMBottom.innerHTML	= text.bodyBottom ? text.bodyBottom : '';
				body = input ? notificationPanelBodyMBottom : null;
			}
			this.addElement('div', notificationPanelBodyM, 'notificationPanelBodyPlaceholder' + localNotificationId, 'notificationPanelBodyPlaceholder', null, true);
	
			// Create the notification panel footer.
			var notificationPanelFooter			= this.addElement('div', notificationPanel, 'notificationPanelFooter' + localNotificationId, 'notificationPanelFooter', null, true);
			var notificationPanelFooterL		= this.addElement('div', notificationPanelFooter, 'notificationPanelFooterL' + localNotificationId, 'notificationPanelFooterL', null, true);
			var notificationPanelFooterR		= this.addElement('div', notificationPanelFooterL, 'notificationPanelFooterR' + localNotificationId, 'notificationPanelFooterR', null, true);
			var notificationPanelFooterM		= this.addElement('div', notificationPanelFooterR, 'notificationPanelFooterM' + localNotificationId, 'notificationPanelFooterM', null, true);
			notificationPanelFooterM.innerHTML	= scriptInfo.name + ' v' + scriptInfo.version;
			
			// Create the button wrapper.
			var notificationPanelButtonWrapper	= this.addElement('div', notificationPanel, 'notificationPanelButtonWrapper' + localNotificationId, 'notificationPanelButtonWrapper', null, true);
			
			// Create the confirm button.
			var notificationPanelConfirm		= this.addElement('input', notificationPanelButtonWrapper, 'notificationPanelConfirm' + localNotificationId, ['notificationPanelButton', 'notificationPanelButtonConfirm'], null, true);
			notificationPanelConfirm.type		= 'button';
			notificationPanelConfirm.value		= text.confirm ? text.confirm : _this.Language.$('default_notification_button_confirm');
			if(callback && callback.confirm) {
				if(body) {
					notificationPanelConfirm.addEventListener('click', function() { closeNotificationPanel(); callback.confirm(body); }, false);
				} else {
					notificationPanelConfirm.addEventListener('click', function() { closeNotificationPanel(); callback.confirm(); }, false);
				}
			} else {
				notificationPanelConfirm.addEventListener('click', closeNotificationPanel, false);
			}
			
			// Create the abort button if needed.
			if(callback && callback.abort) {
				var notificationPanelAbort			= this.addElement('input', notificationPanelButtonWrapper, 'notificationPanelAbort' + localNotificationId, ['notificationPanelButton', 'notificationPanelButtonAbort'], null, true);
				notificationPanelAbort.type			= 'button';
				notificationPanelAbort.value		= text.abort ? text.abort : _this.Language.$('default_notification_button_abort');
				if(body) {
					notificationPanelAbort.addEventListener('click', function() { closeNotificationPanel(); callback.abort(body); }, false);
				} else {
					notificationPanelAbort.addEventListener('click', function() { closeNotificationPanel(); callback.abort(); }, false);
				}
			}
			
			return localNotificationId;
		};
		
		/**
		 * Toogle the show / hide Button image and title.
		 * 
		 * @instance
		 * 
		 * @param  {element}	button
		 *   The button to toggle.
		 */
		this.toggleShowHideButton = function(button) {
			// Switch the button picture.
			button.classList.toggle('minimizeImg');
			button.classList.toggle('maximizeImg');
			
			// Switch the button title.
			button.title = (button.title == _this.Language.$('general_fold')) ? _this.Language.$('general_expand') : _this.Language.$('general_fold');
		};
		
		/**
		 * Runs a callback on every property of an object which is not in the prototype.
		 * 
		 * @param	{object}	obj
		 *   The Object where forEach should be used.
		 * @param	{function}	callback
		 *   The callback which should be called.<br>
		 *   Signature: <code>function(propertyValue : mixed, propertyKey : string) : void</code>
		 */
		this.forEach = function(obj, callback) {
			for(var key in obj) {
				if(Object.prototype.hasOwnProperty.call(obj, key)) {
						callback(key, obj[key]);
					}
				}
		};
		
		/*--------------------*
		 * Set some settings. *
		 *--------------------*/
		
		// Set the notification style.
		this.addStyle(
				"." + _prefix + "notificationBackground					{ z-index: 1000000000000; position: fixed; visibility: visible; top: 0px; left: 0px; width: 100%; height: 100%; padding: 0; background-color: #000; opacity: .7; } \
				 ." + _prefix + "notificationPanelContainer				{ z-index: 1000000000001; position: fixed; visibility: visible; top: 100px; left: 50%; width: 500px; height: 370px; margin-left: -250px; padding: 0; text-align: left; color: #542C0F; font: 12px Arial,Helvetica,sans-serif; } \
				 ." + _prefix + "notificationPanel						{ position: relative; top: 0px; left: 0px; background-color: transparent; border: 0 none; overflow: hidden; } \
				 ." + _prefix + "notificationPanelHeader				{ height: 39px; background: none repeat scroll 0 0 transparent; font-weight: bold; line-height: 2; white-space: nowrap; } \
				 ." + _prefix + "notificationPanelHeaderL				{ height: 39px; background-image: url('skin/layout/notes_top_left.png'); background-position: left top; background-repeat: no-repeat; } \
				 ." + _prefix + "notificationPanelHeaderR				{ height: 39px; background-image: url('skin/layout/notes_top_right.png'); background-position: right top; background-repeat: no-repeat; } \
				 ." + _prefix + "notificationPanelHeaderM				{ height: 39px; margin: 0 14px 0 38px; padding: 12px 0 0; background-image: url('skin/layout/notes_top.png'); background-position: left top; background-repeat: repeat-x; color: #811709; line-height: 1.34em; } \
				 ." + _prefix + "notificationPanelBody					{ max-height: 311px; height: 100%; background: none repeat scroll 0 0 transparent; } \
				 ." + _prefix + "notificationPanelBodyL					{ height: 100%; background-image: url('skin/layout/notes_left.png'); background-position: left top; background-repeat: repeat-y; } \
				 ." + _prefix + "notificationPanelBodyR					{ height: 100%; background-image: url('skin/layout/notes_right.png'); background-position: right top; background-repeat: repeat-y; } \
				 ." + _prefix + "notificationPanelBodyM					{ height: 100%; background-color: #F7E7C5; background-image: none;  margin: 0 6px; padding: 0 10px; font-size: 14px; } \
				 ." + _prefix + "notificationPanelBodyMTop				{ max-height: 100px; line-height: 2; } \
				 ." + _prefix + "notificationPanelBodyMTop b			{ line-height: 3.5; font-size:110%; } \
				 ." + _prefix + "notificationPanelBodyM a				{ color: #811709; font-weight: bold; } \
				 ." + _prefix + "notificationPanelBodyM h2				{ font-weight: bold; } \
				 ." + _prefix + "notificationPanelBodyMContent			{ max-height: 270px; padding: 10px; background: url('skin/input/textfield.png') repeat-x scroll 0 0 #FFF7E1; border: 1px dotted #C0C0C0; font: 14px Arial,Helvetica,sans-serif; color: #000000; border-collapse: separate; overflow-y:auto; } \
				 ." + _prefix + "notificationPanelBodyMBottom			{ max-height: 170px; padding: 10px; background: url('skin/input/textfield.png') repeat-x scroll 0 0 #FFF7E1; border: 1px dotted #C0C0C0; font: 14px Arial,Helvetica,sans-serif; color: #000000; border-collapse: separate; overflow-y:auto; } \
				 textarea." + _prefix + "notificationPanelBodyMContent	{ height: 270px; width: 445px; resize: none; } \
				 textarea." + _prefix + "notificationPanelBodyMBottom	{ height: 170px; width: 445px; resize: none; } \
				 ." + _prefix + "notificationPanelBodyPlaceholder		{ height: 20px; } \
				 ." + _prefix + "notificationPanelFooter				{ height: 20px; background: none repeat scroll 0 0 transparent; } \
				 ." + _prefix + "notificationPanelFooterL				{ height: 100%; background-image: url('skin/layout/notes_left.png'); background-position: left top; background-repeat: repeat-y; border: 0 none; } \
				 ." + _prefix + "notificationPanelFooterR				{ height: 21px; background-image: url('skin/layout/notes_br.png'); background-position: right bottom; background-repeat: no-repeat; } \
				 ." + _prefix + "notificationPanelFooterM				{ background-color: #F7E7C5; border-bottom: 3px solid #D2A860; border-left: 2px solid #D2A860; margin: 0 23px 0 3px; padding: 3px 0 2px 3px; font-size: 77%; } \
				 ." + _prefix + "notificationPanelClose					{ cursor: pointer; position: absolute; top: 12px; right: 8px; width: 17px; height: 17px; background-image: url('skin/layout/notes_close.png'); } \
				 ." + _prefix + "notificationPanelButtonWrapper			{ bottom: -4px; position: absolute; margin: 10px auto; width: 100%; text-align: center; } \
				 ." + _prefix + "notificationPanelButton				{ background: url('skin/input/button.png') repeat-x scroll 0 0 #ECCF8E; border-color: #C9A584 #5D4C2F #5D4C2F #C9A584; border-style: double; border-width: 3px; cursor: pointer; display: inline; font-weight: bold; margin: 0px 5px; padding: 2px 10px; text-align: center; font-size: 12px; width: 100px; } \
				 ." + _prefix + "notificationPanelButton:hover			{ color: #B3713F; } \
				 ." + _prefix + "notificationPanelButton:active			{ border-color: #5D4C2F #C9A584 #C9A584 #5D4C2F; border-style: double; border-width: 3px; padding: 3px 10px 1px; } \
				 ." + _prefix + "notificationPanelButtonConfirm			{  } \
				 ." + _prefix + "notificationPanelButtonAbort			{  }",
				'notification', true
			);
		
		// Add the buttons for toggle buttons used styles.
		this.addStyle(
				".minimizeImg, .maximizeImg		{ background: url('skin/interface/window_control_sprite.png') no-repeat scroll 0 0 transparent; cursor: pointer; display: block; height: 18px; width: 18px; } \
				 .minimizeImg					{ background-position: -144px 0; } \
				 .minimizeImg:hover				{ background-position: -144px -19px; } \
				 .maximizeImg					{ background-position: -126px 0; } \
				 .maximizeImg:hover				{ background-position: -126px -19px; }",
				'toggleShowHideButton', true
			);
	}
	
	/**
	 * myGM for cross-browser compatibility of the GM_* functions. (use myGM.* instead of GM_*)<br>
	 * Also there are general used functions stored.
	 * 
	 * @instance
	 * 
	 * @type	IkariamCore~myGM
	 */
	this.myGM = new myGM;
	
	/**
	 * Instantiate a new set of Ikariam specific functions.
	 * 
	 * @inner
	 * 
	 * @class
	 * @classdesc	Ikariam specific functions.
	 */
	function Ikariam() {
		/*-------------------------------------------*
		 * Public variables, functions and settings. *
		 *-------------------------------------------*/
		
		/**
		 * Returns the name of the actual selected view (world, island, town).
		 * 
		 * @instance
		 * 
		 * @return	{string}
		 *   The name of the view.
		 */
		this.view = function() {
			// Get the id of the body.
			var viewId = document.body.id;
			var view = '';
			
			// Get the name of the view depending on the body id.
			switch(viewId) {
				case 'worldmap_iso':
					view = 'world';
				  break;
	
				case 'island':
					view = 'island';
				  break;
	
				case 'city':
					view = 'town';
				  break;
			}
			
			// Return the view name.
			return view;
		};
		
		/**
		 * Parses a string number to an int value.
		 * 
		 * @instance
		 * 
		 * @param	{string}	txt
		 *   The number to format.
		 *
		 * @return	{int}
		 *   The formatted value.
		 */
		this.getInt = function(txt) {
			// Return the formated number.
			return parseInt(txt.replace(/(\.|,)/g, ''));
		};
		
		/**
		 * Formats a number to that format that is used in Ikariam.
		 *
		 * @param	{int}					num
		 *   The number to format.
		 * @param	{boolean || boolean[]}	addColor
		 *   If the number should be coloured. (optional, if not set, a color will be used for negative and no color will be used for positive numbers)
		 * @param	{boolean}				usePlusSign
		 *   If a plus sign should be used for positive numbers.
		 * 
		 * @return	{string}
		 *   The formated number.
		 */
		this.formatToIkaNumber = function(num, addColor, usePlusSign) {
			var txt = num + '';
	
			// Set a seperator every 3 digits from the end.
			txt = txt.replace(/(\d)(?=(\d{3})+\b)/g, '$1' + Language.$('settings_kiloSep'));
	
			// If the number is negative and it is enabled, write it in red.
			if(num < 0 && !(addColor == false || (addColor && addColor.negative == false))) {
				txt = '<span class="red bold">' + txt + '</span>';
			}
	
			// If the number is positive.
			if(num > 0) {
				// Add the plus sign if wanted.
				txt = (usePlusSign ? '+' : '') + txt;
	
				// Color the text green if wanted.
				if(!!(addColor == true || (addColor && addColor.positive == true))) {
					txt = '<span class="green bold">' + txt + '</span>';
				}
			}
	
			// Return the formated number.
			return txt;
		};
		
		/**
		 * Returns if the user is logged in to the mobile version.
		 * 
		 * @instance
		 * 
		 * @return	{boolean}
		 *   The login-status to mobile.
		 */
		this.isMobileVersion = function() {
			return (top.location.href.search(/http:\/\/m/) > -1);
		},
	
		/**
		 * Returns a code consisting of the server name and the country code.
		 * 
		 * @instance
		 * 
		 * @return	{string}
		 *   The code.
		 */
		this.getServerCode = function() {
			// Split the host string.
			var code = top.location.host.split('.');
	
			// Set the language name.
			return (code ? code[1] + '_' + code[0] : 'undefined');
		};
		
		/**
		 * Shows a hint to the user (desktop).
		 * 
		 * @instance
		 * 
		 * @param	{string}	located
		 *   The location of the hint. Possible are all advisors, a clicked element or a committed element.
		 * @param	{string}	type
		 *   The type of the hint. Possible is confirm, error, neutral or follow the mouse.
		 * @param	{string}	msgText
		 *   The hint text.
		 * @param	{string}	msgBindTo
		 *   The JQuery selector of the element the tooltip should be bound to.
		 * @param	{boolean}	msgIsMinSize
		 *   If the message is minimized (only used if type = followMouse).
		 */
		this.showTooltip = function(located, type, msgText, msgBindTo, msgIsMinSize) {
			// Get the message location.
			var msgLocation = -1;
			switch(located) {
				case 'cityAdvisor':
					msgLocation = 1;
				  break;
	
				case 'militaryAdvisor':
					msgLocation = 2;
				  break;
	
				case 'researchAdvisor':
					msgLocation = 3;
				  break;
	
				case 'diplomacyAdvisor':
					msgLocation = 4;
				  break;
	
				case 'clickedElement':
					msgLocation = 5;
				  break;
	
				case 'committedElement':
					msgLocation = 6;
				  break;
			}
	
			// Get the message type.
			var msgType = -1;
			switch(type) {
				case 'confirm':
					msgType = 10;
				  break;
	
				case 'error':
					msgType = 11;
				  break;
	
				case 'neutral':
					msgType = 12;
				  break;
	
				case 'followMouse':
					msgType = 13;
				  break;
			}
			
			// Show the tooltip.
			_this.ika.controller.tooltipController.bindBubbleTip(msgLocation, msgType, msgText, null, msgBindTo, msgIsMinSize);
		};
	}
	
	/**
	 * Ikariam specific functions like converting a number from Ikariam format to int.
	 * 
	 * @instance
	 * 
	 * @type	IkariamCore~Ikariam
	 */
	this.Ikariam = new Ikariam;
	
	/**
	 * Instantiate a new set of localisation functions.
	 * 
	 * @inner
	 * 
	 * @class
	 * @classdesc	Functions for localisating the script.
	 */
	function Language() {
		/*--------------------------------------------*
		 * Private variables, functions and settings. *
		 *--------------------------------------------*/
		
		/**
		 * Default ikariam language code - default for this server.
		 * 
		 * @private
		 * @inner
		 * 
		 * @default	en
		 * 
		 * @type	string
		 */
		var _ikaCode = 'en';
		
		/**
		 * Default ikariam language name - default for this server.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	string
		 */
		var _ikaLang = 'English';
		
		/**
		 * Used language code.
		 * 
		 * @private
		 * @inner
		 * 
		 * @default	en
		 * 
		 * @type	string
		 */
		var _usedCode = 'en';
		
		/**
		 * Used language name.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	string
		 */
		var _usedLang = '';
		
		/**
		 * Used language texts.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	json
		 */
		var _usedText = null;
		
		/**
		 * Default language text. To be used if the used language is not available.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	json
		 */
		var _defaultText = null;
		
		/**
		 * All languages which are registered with their storage type (resource, in-script-array, default).
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	string[]
		 */
		var _registeredLangs = {};
		
		/**
		 * All JSON language resource settings (resource name, url).
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	mixed[]
		 */
		var _jsonLanguageText = {};
		
		/**
		 * All in-script-array language texts.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	json[]
		 */
		var _languageResources = {};
		
		// Split the host string.
		var _lang = top.location.host.split('.');
		
		// Change the language code, if lang exists.
		if(_lang) {
			for(var i = 0; i < _lang.length; i++) {
				if(_lang[i] == 'ikariam') {
					_usedCode = _ikaCode = _lang[i - 1];
					break;
				}
			}
		}
		
		/**
		 * "Translation" of all possible language codes to the corresponding language.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	string[]
		 */
		var _codeTranslation = {
			ae: 'Arabic',		ar: 'Spanish',		ba: 'Bosnian',		bg: 'Bulgarian',	br: 'Portuguese',	by: 'Russian',
			cl: 'Spanish',		cn: 'Chinese',		co: 'Spanish',		cz: 'Czech',		de: 'German',		dk: 'Danish',
			ee: 'Estonian',		en: 'English',		es: 'Spanish',		fi: 'Finish',		fr: 'French',		gr: 'Greek',
			hk: 'Chinese',		hr: 'Bosnian',		hu: 'Hungarian',	id: 'Indonesian',	il: 'Hebrew',		it: 'Italian',
			kr: 'Korean',		lt: 'Lithuanian',	lv: 'Latvian',		mx: 'Spanish',		nl: 'Dutch',		no: 'Norwegian',
			pe: 'Spanish',		ph: 'Filipino',		pk: 'Urdu',			pl: 'Polish',		pt: 'Portuguese',	ro: 'Romanian',
			rs: 'Serbian',		ru: 'Russian',		se: 'Swedish',		si: 'Slovene',		sk: 'Slovak',		tr: 'Turkish',
			tw: 'Chinese',		ua: 'Ukrainian',	us: 'English',		ve: 'Spanish',		vn: 'Vietnamese',	yu: 'Bosnian'
		};
		
		// Set the language.
		_ikaLang	= _codeTranslation[_ikaCode];
		_usedLang	= _codeTranslation[_usedCode];
		
		/**
		 * Set the choosen language text for the script.
		 * 
		 * @private
		 * @inner
		 */
		var _setText = function() {
			if(_registeredLangs[_usedLang]) {
				var type = _registeredLangs[_usedLang];
				
				if(type == 'resource') {
					if(_languageResources[_usedLang]) {
						// Get the ressource.
						_usedText = _this.myGM.getResourceParsed(_languageResources[_usedLang].resourceName, _languageResources[_usedLang].url);
					} else {
						_usedText = { is_error: true };
					}
				} else if(type == 'default') {
					_usedText = _defaultText;
				} else {
					if(_jsonLanguageText[_usedLang]) {
						// Get the ressource.
						_usedText = _jsonLanguageText[_usedLang];
					} else {
						_usedText = { is_error: true };
					}
				}
				
				// Store it to Language._usedText.
				_usedText = (_usedText && !_usedText.is_error) ? _usedText : _defaultText;
	
			// Otherwise: Use the default text.
			} else {
				_usedText = _defaultText;
			}
		};
		
		/*-------------------------------------------*
		 * Public variables, functions and settings. *
		 *-------------------------------------------*/
		
		/**
		 * Set the default language.
		 * 
		 * @instance
		 * 
		 * @param	{string}	name
		 * 	 The Name of the default language.
		 * @param	{json}		json
		 *   JSON with the default language data.
		 */
		this.setDefaultLang = function(name, json) {
			// Set the language as registered language.
			_registeredLangs[name] = 'default';
			
			// Set the default and used language name.
			_usedLang = _usedLang != '' ? _usedLang : name;
			
			// Set the default language data.
			_defaultText = json;
			
			// Set the used language data.
			_setText();
		};
		
		/**
		 * Registers a new language without resource usage.
		 * 
		 * @instance
		 * 
		 * @param	{string}	languageName
		 *   The name of the language.
		 * @param	{json}		json
		 *   JSON with the language data.
		 */
		this.addLanguageText = function(languageName, json) {
			// Set the language as registered language.
			_registeredLangs[languageName] = 'jsonText';
			
			// Set the data for this language.
			_jsonLanguageText[languageName] = json;
			
			// Set the used language data.
			_setText();
		};
		
		/**
		 * Registers a new language resource.
		 * 
		 * @instance
		 * 
		 * @param	{string}	languageName
		 *   Name of the language.
		 * @param	{string}	resourceName
		 *   Name of the resource.
		 * @param	{string}	resourceURL
		 *   URL, if resources are not supported.
		 */
		this.registerLanguageResource = function(languageName, resourceName, resourceURL) {
			// Set the language as registered language.
			_registeredLangs[languageName] = 'resource';
			
			// Set the data for this language.
			_languageResources[languageName] = { resourceName: resourceName, url: resourceURL };
			
			// Set the used language data.
			_setText();
		};
		
		/**
		 * Return the name of the actually used language.
		 * 
		 * @instance
		 * 
		 * @return	{string}
		 *   The country code.
		 */
		this.getLangName = function() {
			return _usedLang;
		};
		
		/**
		 * Return a string which is defined by its placeholder. If the string contains variables defined with %$nr,
		 * they are replaced with the content of the array at this index.
		 * 
		 * @instance
		 * 
		 * @param	{string}	name
		 *   The name of the placeholder.
		 * @param	{mixed[]}	vars
		 *   An array containing variables for replacing in the language string. (optional)
		 *
		 * @return	{string}
		 *   The text.
		 */
		this.getText = function(name, vars) {
			// Set the text to the placeholder.
			var erg = name;
	
			// Split the placeholder.
			var parts = name.split('_');
	
			// If the splitting was successful.
			if(parts) {
				// Set txt to the "next level".
				var txt = _usedText ? _usedText[parts[0]] : null;
	
				// Loop over all parts.
				for(var i = 1; i < parts.length; i++) {
					// If the "next level" exists, set txt to it.
					if(txt && typeof txt[parts[i]] != 'undefined') {
						txt = txt[parts[i]];
					} else {
						txt = erg;
						break;
					}
				}
	
				// If the text type is not an object, a function or undefined.
				if(typeof txt != 'object' && typeof txt != 'function' && typeof txt != 'undefined') {
					erg = txt + '';
				}
				
				if(vars) {
					for(var i = 0; i < vars.length; i++) {
						var regex = new RegExp('%\\$' + (i + 1), 'g');
						erg = erg.replace(regex, vars[i] + '');
					}
				}
			}
			
			if(erg == name) {
				_this.con.log('Language.getText: No translation available for "' + name + '" in language ' + this.getLangName());
			}
			
			// Return the text.
			return erg;
		};
		
		/**
		 * Synonymous function for {@link IkariamCore~Language#getText}.<br>
		 * Return a string which is defined by its placeholder. If the string contains variables defined with %$nr,
		 * they are replaced with the content of the array at this index.
		 * 
		 * @instance
		 * 
		 * @param	{string}	name
		 *   The name of the placeholder.
		 * @param	{mixed[]}	vars
		 *   An array containing variables for replacing in the language string. (optional)
		 *
		 * @return	{string}
		 *   The text.
		 */
		this.$ = function(name, vars) {
			return this.getText(name, vars);
		};
	}
	
	/**
	 * Functions for localisation of the script.
	 * 
	 * @instance
	 * 
	 * @type	IkariamCore~Language
	 */
	this.Language = new Language;
	
	/**
	 * Instantiate the handler.
	 * 
	 * @inner
	 * 
	 * @class
	 * @classdesc	Handler for callbacks for processing DOM modification events.
	 */
	function Observer() {
		/*--------------------------------------------*
		 * Private variables, functions and settings. *
		 *--------------------------------------------*/
		
		/**
		 * Storage for MutationObserver.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	MutationObserver
		 */ 
		var _MutationObserver = _this.win.MutationObserver || _this.win.WebKitMutationObserver;
		
		/**
		 * If the MutationObserver can be used or if an workaround must be used.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	boolean
		 */ 
		var _canUseObserver = !!_MutationObserver;
		
		/**
		 * List to store the created observers.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	MutationObserver[]
		 */
		var _observerList = {};
		
		/*-------------------------------------------*
		 * Public variables, functions and settings. *
		 *-------------------------------------------*/
		
		/**
		 * Adds a new observer for DOM modification events. If it is possible use MutationObserver. More about the 
		 * Mutation observer can be found here: {@link https://developer.mozilla.org/en-US/docs/DOM/MutationObserver Mutation Observer on MDN}.<br>
		 * If it's not possible to use a MutationObserver a DOMSubtreeModified or DOMAttrModified event listener is used.
		 * 
		 * @instance
		 * 
		 * @param	{string}	id
		 *   The id to store the observer.
		 * @param	{element}	target
		 *   The target to observe.
		 * @param	{mixed[]}	options
		 *   Options for the observer. All possible options can be found here: {@link https://developer.mozilla.org/en-US/docs/DOM/MutationObserver#MutationObserverInit MutationObserver on MDN}
		 * @param	{function}	callback
		 *   The callback for the observer.<br>
		 *   Signature: <code>function(mutations : MutationRecord) : void</code>
		 * @param	{function}	noMutationObserverCallback
		 *   The callback if the use of the observer is not possible and DOMAttrModified / DOMSubtreeModified is used instead.<br>
		 *   Signature: <code>function() : void</code>
		 */
		this.add = function(id, target, options, callback, noMutationObserverCallback) {
			var observer;
			
			if(!!target) {
				// If the MutationObserver can be used, do so.
				if(_canUseObserver) {
					// Create the observer.
					observer = new _MutationObserver(callback);
					
					// Start the observation.
					observer.observe(target, options);
					
					// Store the observer if the id is unique.
					if(!_observerList[id]) {
						_observerList[id] = observer;
					
					// Otherwise: Alert that the id is already in use.
					} else {
						_this.con.log('Observer.add: Id "' + id + '" already used for observer, please choose another one!');
					}
				
				// Otherwise use the event listener.
				} else {
					// Add the event listener.
					if(options.attributes) {
						target.addEventListener('DOMAttrModified', noMutationObserverCallback, false);
					}
					
					if(options.characterData || options.childList || options.subtree) {
						target.addEventListener('DOMSubtreeModified', noMutationObserverCallback, false);
					}
				}
			} else {
				_this.con.log('Observer.add: Observer target not defined! id: ' + id);
			}
		};
		
		/**
		 * Removes the observer given by the id. If the use of MutationObserver is not possible, this function can not be used.
		 * 
		 * @instance
		 * 
		 * @param	{string}	id
		 *   The id of the observer to remove.
		 */
		this.remove = function(id) {
			// If the observer is set.
			if(_canUseObserver && _observerList[id]) {
				// Get the observer.
				var observer = _observerList[id];
				
				// Disconnect the observer if it is a MutationObserver.
				observer.disconnect();
				
				// Delete the observer data.
				delete _observerList[id];
			} else if(!_canUseObserver) {
				_this.con.log('Observer.remove: It is not possible to use MutationObservers so Observer.remove can not be used.');
			}
		};
	}
		
	/**
	 * Handler for callbacks after modification of DOM elements.
	 * 
	 * @instance
	 * 
	 * @type	IkariamCore~Observer
	 */
	this.Observer = new Observer;
	
	/**
	 * Instantiate a new set of refresh functions.
	 * 
	 * @inner
	 * 
	 * @class
	 * @classdesc	Handles functions that should run on ikariam popups and after actualisations of the page data.
	 */
	function RefreshHandler() {
		/*--------------------------------------------*
		 * Private variables, functions and settings. *
		 *--------------------------------------------*/
		
		/**
		 * Storage for the actualisation callbacks.<br>
		 * Architecture:<br>
		 * <code>_callbacks = {<br>
		 * &#09;popupId: {<br>
		 * &#09;&#09;callbackId: callback<br>
		 * &#09;}<br>
		 * }</code>
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	function[][]
		 */
		var _callbacks = {};
		
		/**
		 * Handles the call of the callback functions for the actualisation.
		 * 
		 * @private
		 * @inner
		 */
		var _handleActualisation = function() {
			// Callbacks for every reload.
			if(_callbacks['*']) {
				_this.myGM.forEach(_callbacks['*'], function(key, callback) {
					callback();
				});
			}
			
			// If the script was already executed on this popup.
			var isAlreadyExecutedPopup = !!_this.myGM.$('#' + _this.myGM.prefix() + 'alreadyExecutedPopup');
			
			// Get the popup.
			var popup = _this.myGM.$('.templateView');
	
			// Get the popup id.
			var popupId = popup ? popup.id.replace('_c', '') : '';
	
			// If a popup exists, add the hint, that the popup script was executed.
			if(popup && !isAlreadyExecutedPopup) {
				var alreadyExecuted		= _this.myGM.addElement('input', _this.myGM.$('.mainContent', popup), 'alreadyExecutedPopup');
				alreadyExecuted.type	= 'hidden';
				
				// Call all callbacks which are set.
				if(_callbacks[popupId]) {
					_this.myGM.forEach(_callbacks[popupId], function(key, callback) {
						callback();
					});
				}
			}
		};
		
		/**
		 * Callback for MutationObserver for calling the popup handler.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{MutationRecord}	mutations
		 *   All recorded mutations.
		 */
		var _callback = function(mutations) {
			mutations.forEach(function(mutation) {
				// If the style.display is set to none.
				if(mutation.target.getAttribute('style').search(/display: none/i) != -1) {
					// Timeout to have access to GM_ funtions.
					setTimeout(_handleActualisation, 0);
				}
			});
		};
		
		/**
		 * Callback for calling the popup handler if the MutationObserver could not be used.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{event}	e
		 *   The called event.
		 */
		var _callbackNoMutationObserver = function(e) {
			// If the attribute was changed.
			if(e.attrChange == MutationEvent.MODIFICATION) {
				// If the style.display is set to none.
				if(e.attrName.trim() == 'style' && e.newValue.search(/display: none/i) != -1) {
					// Timeout to have access to GM_ funtions.
					setTimeout(_handleActualisation, 0);
				}
			}
		};
		
		/*-------------------------------------------*
		 * Public variables, functions and settings. *
		 *-------------------------------------------*/
		
		/**
		 * Add a new popup handler.
		 * 
		 * @instance
		 * 
		 * @param	{string}	popupId
		 *   The id of the popup where the callback schould be called (without '_c' at the end).<br>
		 *   Set to '*' for calling at every actualisation, not just popups.
		 * @param	{string}	callbackId
		 *   The id of the callback. This must be unique for a popup.
		 * @param	{function}	callback
		 *   The callback which should be called.<br>
		 *   Signature: <code>function() : void</code>
		 */
		this.add = function(popupId, callbackId, callback) {
			// If no entry for the popup exists, create it.
			if(!_callbacks[popupId]) {
				_callbacks[popupId] = {};
			}
			
			// If no entry for the callback existst, create one.
			if(!_callbacks[popupId][callbackId]) {
				_callbacks[popupId][callbackId] = callback;
			
			// Otherwise: Show an error to the user (programmer).
			} else {
				_this.con.log('RefreshHandler.add: Id set "' + popupId + '|' + callbackId + '" already used for observer, please choose another one!');
			}
		};
		
		/**
		 * Removes a popup handler.
		 * 
		 * @instance
		 * 
		 * @param	{string}	popupId
		 *   The id of the popup where the callback was called (without '_c' at the end).
		 *   Set to '*' for allbacks which have been called at every actualisation, not just popups.
		 * @param	{string}	callbackId
		 *   The id of the callback. This must be unique for a popup.
		 */
		this.remove = function(popupId, callbackId) {
			// Remove the callback if it is set.
			if(_callbacks[popupId] && _callbacks[popupId][callbackId]) {
				delete	_callbacks[popupId][callbackId];
			}
		};
		
		/*----------------------------------------------------*
		 * Register the observer and handle popups on startup *
		 *----------------------------------------------------*/
		
		// Add the observer for the popups.
		_this.Observer.add('actualisationHandler', _this.myGM.$('#loadingPreview'), { attributes: true, attributeFilter: ['style'] }, _callback, _callbackNoMutationObserver);
		
		// Execute the handler on popups which are shown on startup.
		setTimeout(_handleActualisation, 0);
	}
	
	/**
	 * Handler for functions that should run on ikariam popups.
	 * 
	 * @instance
	 * 
	 * @type	IkariamCore~RefreshHandler
	 */
	this.RefreshHandler = new RefreshHandler;
	
	/**
	 * Instantiate a new set of options / settings functions.
	 * 
	 * @inner
	 * 
	 * @class
	 * @classdesc	Handles options the user can set, provides a "panel" for them to change them.
	 */
	function Options() {
		/*--------------------------------------------*
		 * Private variables, functions and settings. *
		 *--------------------------------------------*/
		
		/**
		 * Storage for option wrapper visibility.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	boolean[]
		 */
		var _optionWrapperVisibility = {
			moduleOptions:	true
		};
		
		/**
		 * Storage for option wrappers.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	mixed[]
		 */
		var _wrapper		= {};
		
		/**
		 * Storage for option wrapper order. (Order in which the wrappers are shown)
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	string[]
		 */
		var _wrapperOrder	= new Array();
		
		/**
		 * Storage for the saved options. Gets filled on startup.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	mixed[]
		 */
		var _savedOptions	= _this.myGM.getValue('optionPanel_options', {});
		
		/**
		 * Storage for the options.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	mixed[]
		 */
		var _options		= {};
		
		/**
		 * Storage for the id of the next hr.
		 * 
		 * @private
		 * @inner
		 * 
		 * @type	int
		 */
		var _hrId = 0;
		
		/**
		 * Add a element to a wrapper. ("generic function")
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{string}		id
		 *   The id of the element.
		 * @param	{string}		wrapperId
		 *   The id of the wrapper for the element.
		 * @param	{string || int}	table
		 *   The id of the table in the wrapper where the element should be added.
		 * @param	{mixed[]}		options
		 *   Options for the element.
		 * @param	{mixed}			defaultValue
		 *   Default value for the option.
		 * @param	{function}		create
		 *   Callback to create the element.<br>
		 *   Signature: <code>function(parentTable : element, elementId : string, value : mixed, options : mixed) : void</code>
		 * @param	{function}		save
		 *   Callback for saving the option value. Returns the value for this option.<br>
		 *   Signature: <code>function(id : string) : mixed</code>
		 * @param	{int}			position
		 *   Position of the element in the element array.
		 */
		var _addElement = function(id, wrapperId, table, options, defaultValue, create, save, position) {
			if(_wrapper[wrapperId]) {
				if(_wrapper[wrapperId].elements[id]) {
					_this.con.log('Options.addElement: Element with id "' + id + '" already defined. Wrapper id: ' + wrapperId);
				} else {
					_wrapper[wrapperId].elements[id]	= { table: table + '', create: create };
					_wrapper[wrapperId].elementOrder.insert(id, position);
					
					if(options != null) {
						_wrapper[wrapperId].elements[id].options = options;
					}
					
					if(defaultValue != null) {
						_wrapper[wrapperId].elements[id].defaultValue	= defaultValue;
						
						if(_savedOptions[wrapperId] && (_savedOptions[wrapperId][id] || _savedOptions[wrapperId][id] == false)) {
							_options[wrapperId][id]	= _savedOptions[wrapperId][id];
						} else {
							_options[wrapperId][id]	= defaultValue;
						}
					}
					
					if(save != null) {
						_wrapper[wrapperId].elements[id].save	= save;
					}
				}
			} else {
				_this.con.log('Options.addElement: Wrapper with id "' + wrapperId + '" not defined. Element id: ' + id);
			}
		};
		
		/**
		 * Save the content of <code>_options</code>.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{boolean}	showNoSuccessHint
		 *   If the success hint should not be shown.
		 */
		var _saveOptions = function(showNoSuccessHint) {
			// Set the value of saved options.
			_savedOptions = _options;
			
			// Save the options.
			_this.myGM.setValue('optionPanel_options', _options);
			
			// Show success hint if enabled.
			if(!showNoSuccessHint) {
				_this.Ikariam.showTooltip('cityAdvisor', 'confirm', _this.Language.$('general_successful'));
			}
		};
		
		/**
		 * Store the actual value of each option to <code>_option</code> and call <code>_saveOptions</code>.
		 * 
		 * @private
		 * @inner
		 */
		var _savePanelOptions = function() {
			// Store the value of each option element.
			_this.myGM.forEach(_wrapper, function(wrapperId, wrapper) {
				_this.myGM.forEach(wrapper.elements, function(elementId, element) {
					if(element.save) {
						_options[wrapperId][elementId] = element.save(wrapperId + elementId);
					}
				});
			});
			
			// Save the options.
			_saveOptions();
		};
		
		/**
		 * Scroll the tabmenu in the options popup.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{string}	direction
		 *   The direction to scroll. Possible values are "left" and "right".
		 */
		var _scrollOptionsTab = function(direction) {
			// Get the tabmenu and the tabs.
			var tabmenu			= _this.myGM.$('#scriptTabmenuWrapper .tabmenu');
			var tabs			= _this.myGM.$$('.tab', tabmenu);
			var firstVisible	= -1;
			var lastVisible		= -1;
			
			// Store the first and last visible tab id.
			for(var i = 0; i < tabs.length; i++) {
				if(!tabs[i].classList.contains('invisibleTab')) {
					if(firstVisible == -1) {
						firstVisible = i;
					}
					
					lastVisible = i;
				}
			}
			
			// Store the id of the tab to show / to hide.
			var toShow;
			var toHide;
			
			if(direction == 'left') {
				toShow = firstVisible - 1;
				toHide = lastVisible;
			} else {
				toShow = lastVisible + 1;
				toHide = firstVisible;
			}
			
			// Scroll.
			if(toShow >= 0 && toShow < tabs.length) {
				tabs[toShow].classList.remove('invisibleTab');
				tabs[toHide].classList.add('invisibleTab');
			}
			
			// Deactivate the scroll left button if it is not possible to scroll left.
			if(toShow <= 0) {
				_this.myGM.$('#scriptTabmenuScrollLeft').classList.add('deactivated');
			} else {
				_this.myGM.$('#scriptTabmenuScrollLeft').classList.remove('deactivated');
			}
			
			// Deactivate the scroll right button if it is not possible to scroll right.
			if(toShow >= tabs.length - 1) {
				_this.myGM.$('#scriptTabmenuScrollRight').classList.add('deactivated');
			} else {
				_this.myGM.$('#scriptTabmenuScrollRight').classList.remove('deactivated');
			}
		};
		
		/**
		 * Initializes the options tab for the script and adds the scroll function to the tabmenu.
		 * 
		 * @private
		 * @inner
		 * 
		 * @return	{element}
		 *   The options tab for the script.
		 */
		var _initializeOptionsTab = function() {
			// Get the tabmenu.
			var tabMenuWrapper	= _this.myGM.$("#scriptTabmenuWrapper");
			var tabmenu			= _this.myGM.$('.tabmenu');
			
			// If the tabmenu was not modified, add the scroll function.
			if(!tabMenuWrapper) {
				// Add the scroll buttons.
				tabMenuWrapper		= _this.myGM.addElement('div', tabmenu.parentNode, 'scriptTabmenuWrapper', null, null, false, tabmenu);
				var scrollLeft		= _this.myGM.addElement('div', tabMenuWrapper, 'scriptTabmenuScrollLeft', 'deactivated', null, false);
				var scrollRight		= _this.myGM.addElement('div', tabMenuWrapper, 'scriptTabmenuScrollRight', null, null, false);
				scrollLeft.addEventListener('click', function() { _scrollOptionsTab('left'); }, false);
				scrollRight.addEventListener('click', function() { _scrollOptionsTab('right'); }, false);
				tabMenuWrapper.insertBefore(tabmenu, scrollRight);
				
				// Set the styles.
				var style		=	'#scriptTabmenuWrapper					{ background: url("/skin/layout/bg_tabs.jpg") repeat-x scroll 0 50% transparent; position: relative; width: 680px; } \
									 #scriptTabmenuWrapper .tab				{ border-left: 1px solid transparent; border-right: 1px solid transparent; border-top: 1px solid transparent; height: 31px !important; padding: 1px 3px 0 2px !important; } \
									 #scriptTabmenuWrapper .invisibleTab	{ display: none !important; }';
				var useStyle	= _this.Options.getOption('optionPanelOptions', 'useStyle');
				
				if(useStyle == 'roundButton') {
					style +=	'#scriptTabmenuWrapper .tabmenu													{ left: 32px; position: relative; top: 0; width: 610px; } \
								 #scriptTabmenuScrollLeft, #scriptTabmenuScrollRight							{ background-image: url("/skin/pirateFortress/button_arrow_70x50_sprite.png"); height: 50px; position: absolute; top: -4px; width: 70px; cursor: pointer; transform: scale(0.7); -webkit-transform: scale(0.7); } \
								 #scriptTabmenuScrollLeft														{ left: -19px; background-position: 0px 0px; } \
								 #scriptTabmenuScrollLeft:hover													{ background-position: 0px -50px; } \
								 #scriptTabmenuScrollLeft:active												{ background-position: 0px -100px; } \
								 #scriptTabmenuScrollRight														{ right: -18px; background-position: -70px -0px; } \
								 #scriptTabmenuScrollRight:hover												{ background-position: -70px -50px; } \
								 #scriptTabmenuScrollRight:active 												{ background-position: -70px -100px; } \
								 #scriptTabmenuScrollLeft.deactivated, #scriptTabmenuScrollRight.deactivated	{ display: none; }';
				} else {
					style +=	'#scriptTabmenuWrapper .tabmenu													{ left: 15px; position: relative; top: 0; width: 644px; } \
								 #scriptTabmenuScrollLeft, #scriptTabmenuScrollRight							{ background-image: url("/skin/friends/button_up.png"); height: 13px; position: absolute; top: 15px; width: 35px; cursor: pointer; } \
								 #scriptTabmenuScrollLeft														{ left: -10px; transform: rotate(-90deg) scale(0.8); -webkit-transform: rotate(-90deg) scale(0.8); } \
								 #scriptTabmenuScrollRight														{ right: -10px; transform: rotate(90deg) scale(0.8); -webkit-transform: rotate(90deg) scale(0.8); } \
								 #scriptTabmenuScrollLeft:hover, #scriptTabmenuScrollRight:hover				{ background-position: 0 -13px; } \
								 #scriptTabmenuScrollLeft.deactivated, #scriptTabmenuScrollRight.deactivated	{ background-position: 0 -26px; }';
				}
				
				_this.myGM.addStyle(style, 'scriptTabmenu', true);
			}
			
			// Get the options tab.
			var tabScriptOptions = _this.myGM.$('#tab' + _this.myGM.prefix() + 'ScriptOptions');
			
			// If the script options tab doesn't exists, create it.
			if(!tabScriptOptions) {
				// Set the styles.
				_this.myGM.addStyle(
						"#tab" + _this.myGM.prefix() + "Options hr					{ margin: 0; } \
						 #tab" + _this.myGM.prefix() + "Options .scriptTextArea		{ resize: none; width: calc(100% - 2px); height: 75px; } \
						 #tab" + _this.myGM.prefix() + "Options .scriptTextField	{ width: 173px;	} \
						 #tab" + _this.myGM.prefix() + "Options .cbWrapper			{ margin: 0 0 0 10px; }",
					'scriptOptionsTab', true);
				
				// Add the script options tab link to the tabmenu.
				var jsTabScriptOptions			= _this.myGM.addElement('li', tabmenu, 'js_tab' + _this.myGM.prefix() + 'Options', ['tab', 'invisibleTab'], null, false);
				jsTabScriptOptions.innerHTML	= '<b class="tab' + _this.myGM.prefix() + 'Options">' + scriptInfo.name + '</b>';
				jsTabScriptOptions.setAttribute('onclick', "$('#js_tab" + _this.myGM.prefix() + "Options').removeClass('selected'); switchTab('tab" + _this.myGM.prefix() + "Options');");
				
				// Add the content wrapper for the script options tab to the tabmenu.
				var mainContent					= _this.myGM.$('#tabGameOptions').parentNode;
				tabScriptOptions				= _this.myGM.addElement('div', mainContent, 'tab' + _this.myGM.prefix() + 'Options', null, new Array(['display', 'none']), false);
			}
			
			// Get the option wrapper visibility.
			_optionWrapperVisibility = _this.myGM.getValue('optionPanel_optionWrapperVisibility', _optionWrapperVisibility);
			
			// Return the script options tab.
			return tabScriptOptions;
		};
		
		/**
		 * Add a wraper for options elements to the script option tab.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{element}	tab
		 *   The tab to add the wrapper to.
		 * @param	{string}	id
		 *   The id of the wrapper.
		 * @param	{string || string[]}	headerText
		 *   The text for the wrapper header. If the element is defined within the IkariamCore initialisation,
		 *   the translation string is not set. Then you can pass an object containing the string id.<br>
		 *   Object signature: <code>{ id: 'idValue' }</code>
		 * 
		 * @return	{element}
		 *   The wrapper.
		 */
		var _createOptionsWrapper = function(tab, id, headerText) {
			// Get the header text, if not set yet.
			if(headerText.id) {
				if(headerText.args) {
					headerText = _this.Language.$(headerText.id, headerText.args);
				} else {
					headerText = _this.Language.$(headerText.id);
				}
			}
			
			// Get the content show status.
			var showContent = !!_optionWrapperVisibility[id];
			
			// Create the wrapper.
			var optionsWrapper	= _this.myGM.addElement('div', tab, id, 'contentBox01h');
			
			// Create the header.
			var optionsHeader		= _this.myGM.addElement('h3', optionsWrapper, null, 'header');
			optionsHeader.innerHTML	= headerText;
			
			// Add the show / hide button.
			var btn = _this.myGM.addElement('div', optionsHeader, null, showContent ? 'minimizeImg' : 'maximizeImg', new Array(['cssFloat', 'left']));
			
			/*
			 * Function to toggle the visibility of an wrapper.
			 */
			var toggle = function() {
				// Toggle the button.
				_this.myGM.toggleShowHideButton(this);
				
				// Toggle the visibility of the content.
				_this.myGM.$('.content', this.parentNode.parentNode).classList.toggle('invisible');
				
				// Store the visibility.
				var optionId = this.parentNode.parentNode.id.replace(_this.myGM.prefix(), '');
				_optionWrapperVisibility[optionId] = !_optionWrapperVisibility[optionId];
				_this.myGM.setValue('optionPanel_optionWrapperVisibility', _optionWrapperVisibility);
	
				// Adjust the size of the Scrollbar.
				_this.ika.controller.adjustSizes();
			};
			
			// Add the toggle function.
			btn.addEventListener('click', toggle, false);
			btn.title = showContent ? _this.Language.$('general_fold') : _this.Language.$('general_expand');
			
			// Create the content wrapper.
			var optionsWrapperContent	= _this.myGM.addElement('div', optionsWrapper, null, showContent ? 'content' : ['content', 'invisible']);
			
			// Create the footer.
			_this.myGM.addElement('div', optionsWrapper, null, 'footer');
			
			// Return the content wrapper.
			return optionsWrapperContent;
		};
		
		/**
		 * Show the option script tab.
		 * 
		 * @private
		 * @inner
		 */
		var _showOptionPanel = function() {
			// Create the options tab, if not existing.
			var tab = _initializeOptionsTab();
			
			// Add all wrappers with elements.
			for(var i = 0; i < _wrapperOrder.length; i++) {
				// Create the wrapper.
				var wrapperId		= _wrapperOrder[i];
				var wrapperOptions	= _wrapper[wrapperId];
				var wrapper 		= _createOptionsWrapper(tab, wrapperId, wrapperOptions.headerText);
				var tables			= {};
				
				// Add all elements to the wrapper.
				for(var j = 0; j < wrapperOptions.elementOrder.length; j++) {
					// Get the element id and the element options
					var elemId	= wrapperOptions.elementOrder[j];
					var elemOpt	= wrapperOptions.elements[elemId];
					
					// Create table and tablebody if not existing.
					if(!tables[elemOpt.table]) {
						var table				= _this.myGM.addElement('table', wrapper, null, ['moduleContent', 'table01']);
						tables[elemOpt.table]	= _this.myGM.addElement('tbody', table);
					}
					
					// Create the element.
					var value = (_options[wrapperId] && (_options[wrapperId][elemId] || _options[wrapperId][elemId] == false)) ? _options[wrapperId][elemId] : null;
					var options = elemOpt.options ? elemOpt.options : null;
					elemOpt.create(tables[elemOpt.table], wrapperId + elemId, value, options);
				}
				
				// Add the save button to the wrapper.
				_this.myGM.addButton(wrapper, _this.Language.$('default_optionPanel_save'), function() { setTimeout(_savePanelOptions, 0); });
			}
		};
		
		/**
		 * Show the notification for exporting the options.
		 * 
		 * @private
		 * @inner
		 */
		var _exportOptionsShowNotification = function() {
			// Get the options as json string.
			var optionString = _this.win.JSON.stringify(_options);
			
			// Set the notification text.
			var notificationText = {
				header:		_this.Language.$('default_optionPanel_section_optionPanelOptions_label_exportNotification_header'),
				bodyTop:	_this.Language.$('default_optionPanel_section_optionPanelOptions_label_exportNotification_explanation'),
				bodyBottom:	optionString
			};
			
			// Show the notification.
			_this.myGM.notification(notificationText, null, true);
		};
		
		/**
		 * Callback for importing the options.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{element}	textarea
		 *   The textarea with the options string to import.
		 */
		var _importOptionsCallback = function(textarea) {
			// Get the option string.
			var optionString = textarea.value;
			
			if(optionString) {
				// Function for safer parsing.
				var safeParse = function(key, value) {
					// If the value is a function, return just the string, so it is not executable.
					if(typeof value === 'function' || Object.prototype.toString.apply(value) === '[object function]') {
						return value.toString();
					}
	
					// Return the value.
					return value;
				};
				
				try {
					// Parse the option string.
					var parsed = _this.win.JSON.parse(optionString, safeParse);
					
					// Store the values in the script.
					_this.myGM.forEach(parsed, function(wrapperKey, elements) {
						_this.myGM.forEach(elements, function(elementKey, setting) {
							if(_options[wrapperKey] && (_options[wrapperKey][elementKey] || _options[wrapperKey][elementKey] == false) && typeof setting != 'array' && typeof setting != 'object') {
								_options[wrapperKey][elementKey] = setting;
							}
						});
					});
					
					// Save the options.
					_saveOptions();
				} catch(e) {
					// Set the notification text.
					var notificationText = {
						header:	_this.Language.$('default_optionPanel_section_optionPanelOptions_label_importError_header'),
						body:	_this.Language.$('default_optionPanel_section_optionPanelOptions_label_importError_explanation')
					};
					
					// Log the error message an show the notification to the user.
					_this.con.log(e);
					_this.myGM.notification(notificationText);
				}
			}
		};
		
		/**
		 * Show the notification for importing the options.
		 * 
		 * @private
		 * @inner
		 */
		var _importOptionsShowNotification = function() {
			// Set the notification text.
			var notificationText = {
				header:		_this.Language.$('default_optionPanel_section_optionPanelOptions_label_importNotification_header'),
				bodyTop:	_this.Language.$('default_optionPanel_section_optionPanelOptions_label_importNotification_explanation')
			};
	
			// Set the notification callback.
			var notificationCallback = {
				confirm:	_importOptionsCallback
			};
			
			// Show the notification.
			_this.myGM.notification(notificationText, notificationCallback, true);
		};
		
		/**
		 * Callback for resetting the options.
		 * 
		 * @private
		 * @inner
		 */
		var _resetOptionsCallback = function() {
			// Clear the options.
			_options = {};
			
			// Store the default values.
			_this.myGM.forEach(_wrapper, function(wrapperKey, wrapper) {
				_options[wrapperKey] = {};
				
				_this.myGM.forEach(wrapper.elements, function(elementKey, element) {
					if(element.defaultValue || element.defaultValue == false) {
						_options[wrapperKey][elementKey] = element.defaultValue;
					}
				});
			});
			
			// Save the options.
			_saveOptions();
		};
		
		/**
		 * Show the notification for resetting the options.
		 * 
		 * @private
		 * @inner
		 */
		var _resetOptionsShowNotification = function() {
			// Set the notification text.
			var notificationText = {
				header:	_this.Language.$('default_optionPanel_section_optionPanelOptions_label_resetNotification_header'),
				body:	_this.Language.$('default_optionPanel_section_optionPanelOptions_label_resetNotification_explanation')
			};
	
			// Set the notification callback.
			var notificationCallback = {
				confirm:	_resetOptionsCallback,
				abort:		function() { return; }
			};
			
			// Show the notification.
			_this.myGM.notification(notificationText, notificationCallback);
		};
		
		/**
		 * Create the export options link.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{element}	_this
		 *   Reference to <code>_this</code>.
		 * @param	{element}	parent
		 *   Parent element for the link.
		 */
		var _exportOptions = function(_this, parent) {
			// Create the export link.
			var exportLink			= _this.myGM.addElement('a', parent);
			exportLink.href			= 'javascript:;';
			exportLink.innerHTML	= _this.Language.$('default_optionPanel_section_optionPanelOptions_label_export');
			exportLink.addEventListener('click', _exportOptionsShowNotification, false);
		};
		
		/**
		 * Create the import options link.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{element}	_this
		 *   Reference to <code>_this</code>.
		 * @param	{element}	parent
		 *   Parent element for the link.
		 */
		var _importOptions = function(_this, parent) {
			// Create the import link.
			var importLink			= _this.myGM.addElement('a', parent);
			importLink.href			= 'javascript:;';
			importLink.innerHTML	= _this.Language.$('default_optionPanel_section_optionPanelOptions_label_import');
			importLink.addEventListener('click', _importOptionsShowNotification, false);
		};
		
		/**
		 * Create the reset options link.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{element}	_this
		 *   Reference to <code>_this</code>.
		 * @param	{element}	parent
		 *   Parent element for the link.
		 */
		var _resetOptions = function(_this, parent) {
			// Create the reset link.
			var resetLink		= _this.myGM.addElement('a', parent);
			resetLink.href		= 'javascript:;';
			resetLink.innerHTML	= _this.Language.$('default_optionPanel_section_optionPanelOptions_label_reset');
			resetLink.addEventListener('click', _resetOptionsShowNotification, false);
		};
		
		/*-------------------------------------------*
		 * Public variables, functions and settings. *
		 *-------------------------------------------*/
		
		/**
		 * Add a wrapper to the list.
		 * 
		 * @instance
		 * 
		 * @param	{string}				id
		 *   The id of the wrapper.
		 * @param	{string || string[]}	headerText
		 *   The text for the wrapper header. If the element is defined within the IkariamCore initialisation,
		 *   the translation string is not set. Then you can pass an object containing the string id.<br>
		 *   Object signature: <code>{ id: 'idValue' }</code>
		 * @param	{int}					position
		 *   The position of the wrapper on the options tab.
		 */
		this.addWrapper = function(id, headerText, position) {
			// If a wrapper with this id already exists, log it.
			if(_wrapper[id]) {
				_this.con.log('Options.addWrapper: Wrapper with id "' + id + '" defined two times.');
			
			// Otherwise: Store the wrapper.
			} else {
				_wrapper[id]	= { headerText: headerText, elements: {}, elementOrder: new Array() };
				_options[id]	= {};
				_wrapperOrder.insert(id, position);
			}
		};
		
		/**
		 * Add a new checkbox to the options tab.
		 * 
		 * @instance
		 * 
		 * @param	{string}				id
		 *   The id of the checkbox.
		 * @param	{string}				wrapperId
		 *   The id of the wrapper.
		 * @param	{string || int}			block
		 *   The block of the wrapper, the checkbox belongs to.
		 * @param	{boolean}				defaultChecked
		 *   If the checkbox is checked by default.
		 * @param	{string || string[]}	label
		 *   The text for the label. If the element is defined within the IkariamCore initialisation,
		 *   the translation string is not set. Then you can pass an object containing the string id.<br>
		 *   Object signature: <code>{ id: 'idValue' }</code>
		 * @param	{int}					position
		 *   The position of the checkbox in the wrapper.
		 */
		this.addCheckbox = function(id, wrapperId, block, defaultChecked, label, position) {
			/*
			 * Function to save the checkbox value.
			 */
			var save = function(elementId) {
				// Get the value and return it.
				return _this.myGM.$('#' + _this.myGM.prefix() + elementId + 'Cb').checked;
			};
			
			/*
			 * Function to create the checkbox.
			 */
			var create = function(parentTable, elementId, value, options) {
				// Get the label text, if not set yet.
				if(options.label.id) {
					if(options.label.args) {
						options.label = _this.Language.$(options.label.id, options.label.args);
					} else {
						options.label = _this.Language.$(options.label.id);
					}
				}
				
				// Create table row.
				var tr	= _this.myGM.addElement('tr', parentTable);
			
				// Create cell.
				var parent		= _this.myGM.addElement('td', tr);
				parent.colSpan	= 2;
				parent.classList.add('left');
				
				// Add checkbox.
				_this.myGM.addCheckboxes(parent, [{ id: elementId, label: options.label, checked: value }]);
			};
			
			// Add the checkbox to the option panel.
			_addElement(id, wrapperId, block, { label: label }, defaultChecked, create, save, position);
		};
		
		/**
		 * Add a new select field to the options tab.
		 * 
		 * @instance
		 * 
		 * @param	{string}				id
		 *   The id of the select field.
		 * @param	{string}				wrapperId
		 *   The id of the wrapper.
		 * @param	{string || int}			block
		 *   The block of the wrapper, the select field belongs to.
		 * @param	{mixed}					defaultSelected
		 *   The value of the option selected by default.
		 * @param	{string || string[]}	label
		 *   The text for the label. If the element is defined within the IkariamCore initialisation,
		 *   the translation string is not set. Then you can pass an object containing the string id.<br>
		 *   Object signature: <code>{ id: 'idValue' }</code>
		 * @param	{mixed[]}				opts
		 *   An array with the names an values of the options.
		 *   Signature: <code>[{ value: 'val', name: 'name' }]</code>
		 *   If the element is defined within the IkariamCore initialisation, the translation string for name is not set.
		 *   Then you can pass an object containing the string id for name.<br>
		 *   Object signature: <code>{ id: 'idValue' }</code>
		 * @param	{int}					position
		 *   The position of the select field in the wrapper.
		 */
		this.addSelect = function(id, wrapperId, block, defaultSelected, label, opts, position) {
			/*
			 * Function to save the select value.
			 */
			var save = function(elementId) {
				// Get value and return it.
				return _this.myGM.getSelectValue(elementId);
			};
			
			/*
			 * Function to create the select.
			 */
			var create = function(parentTable, elementId, value, options) {
				// Get the label text, if not set yet.
				if(options.label.id) {
					if(options.label.args) {
						options.label = _this.Language.$(options.label.id, options.label.args);
					} else {
						options.label = _this.Language.$(options.label.id);
					}
				}
				
				var opts = options.opts;
				
				// Get the option names, if not set yet.
				for(var i = 0; i < opts.length; i++) {
					if(opts[i].name && opts[i].name.id) {
						if(opts[i].name.args) {
							opts[i].name = _this.Language.$(opts[i].name.id, opts.name.args);
						} else {
							opts[i].name = _this.Language.$(opts[i].name.id);
						}
					}
				}
				
				// Add select field.
				_this.myGM.addSelect(parentTable, elementId, value, opts, options.label);
			};
			
			// Add the select field to the option panel.
			_addElement(id, wrapperId, block, { label: label, opts: opts }, defaultSelected, create, save, position);
		};
		
		/**
		 * Add a new textfield to the options tab.
		 * 
		 * @instance
		 * 
		 * @param	{string}				id
		 *   The id of the textfield.
		 * @param	{string}				wrapperId
		 *   The id of the wrapper.
		 * @param	{string || int}			block
		 *   The block of the wrapper, the textfield belongs to.
		 * @param	{boolean}				defaultValue
		 *   Default value of the textfield.
		 * @param	{string || string[]}	label
		 *   The text for the label. If the element is defined within the IkariamCore initialisation,
		 *   the translation string is not set. Then you can pass an object containing the string id.<br>
		 *   Object signature: <code>{ id: 'idValue' }</code>
		 * @param	{int}					position
		 *   The position of the textfield in the wrapper.
		 */
		this.addTextField = function(id, wrapperId, block, defaultValue, label, position) {
			/*
			 * Function to save the textfield value.
			 */
			var save = function(elementId) {
				// Get value and return it.
				return _this.myGM.$('#' + _this.myGM.prefix() + elementId + 'TextField').value;
			};
			
			/*
			 * Function to create the textfield.
			 */
			var create = function(parentTable, elementId, value, options) {
				// Get the label text, if not set yet.
				if(options.label.id) {
					if(options.label.args) {
						options.label = _this.Language.$(options.label.id, options.label.args);
					} else {
						options.label = _this.Language.$(options.label.id);
					}
				}
				
				// Create table row.
				var tr	= _this.myGM.addElement('tr', parentTable);
				
				// Create cells.
				var labelCell		= _this.myGM.addElement('td', tr);
				var textFieldCell	= _this.myGM.addElement('td', tr, null, 'left');
				
				// Create label.
				var tfLabel			= _this.myGM.addElement('span', labelCell);
				tfLabel.innerHTML	= options.label;
				
				// Add textfield.
				var tf		= _this.myGM.addElement('input', textFieldCell, elementId + 'TextField', ['textfield', 'scriptTextField']);
				tf.type		= 'text';
				tf.value	= value;
			};
			
			// Add the textfield to the option panel.
			_addElement(id, wrapperId, block, { label: label }, defaultValue, create, save, position);
		};
		
		/**
		 * Add a new textarea to the options tab.
		 * 
		 * @instance
		 * 
		 * @param	{string}				id
		 *   The id of the textarea.
		 * @param	{string}				wrapperId
		 *   The id of the wrapper.
		 * @param	{string || int}			block
		 *   The block of the wrapper, the textarea belongs to.
		 * @param	{boolean}				defaultValue
		 *   Default value of the textarea.
		 * @param	{string || string[]}	label
		 *   The text for the label. If the element is defined within the IkariamCore initialisation,
		 *   the translation string is not set. Then you can pass an object containing the string id.<br>
		 *   Object signature: <code>{ id: 'idValue' }</code>
		 * @param	{int}					position
		 *   The position of the textarea in the wrapper.
		 */
		this.addTextArea = function(id, wrapperId, block, defaultValue, label, position) {
			/*
			 * Function to save the textarea value.
			 */
			var save = function(elementId) {
				// Get value and return it.
				return _this.myGM.$('#' + _this.myGM.prefix() + elementId + 'TextArea').value;
			};
			
			/*
			 * Function to create the textarea.
			 */
			var create = function(parentTable, elementId, value, options) {
				// Get the label text, if not set yet.
				if(options.label.id) {
					if(options.label.args) {
						options.label = _this.Language.$(options.label.id, options.label.args);
					} else {
						options.label = _this.Language.$(options.label.id);
					}
				}
				
				// Create label table row.
				var labelRow	= _this.myGM.addElement('tr', parentTable);
				
				// Create cell.
				var labelCell		= _this.myGM.addElement('td', labelRow);
				labelCell.colSpan	= 2;
				labelCell.classList.add('left');
				
				// Create label.
				var taLabel			= _this.myGM.addElement('p', labelCell);
				taLabel.innerHTML	= options.label;
				
				// Create textarea table row.
				var taRow	= _this.myGM.addElement('tr', parentTable);
				
				// Create cell.
				var taCell		= _this.myGM.addElement('td', taRow);
				taCell.colSpan	= 2;
				taCell.classList.add('left');
				
				// Add the textarea.
				var textArea	= _this.myGM.addElement('textarea', taCell, elementId + 'TextArea', ['textfield', 'scriptTextArea']);
				textArea.value	= value;
			};
			
			// Add the textarea to the options panel.
			_addElement(id, wrapperId, block, { label: label, className: className }, defaultValue, create, save, position);
		};
		
		/**
		 * Add HTML content to the options tab.
		 * 
		 * @instance
		 * 
		 * @param	{string}		id
		 *   The id of the HTML content.
		 * @param	{string}		wrapperId
		 *   The id of the wrapper.
		 * @param	{string || int}	block
		 *   The block of the wrapper, the HTML content belongs to.
		 * @param	{string}	html
		 *   HTML string to add to the wrapper.
		 * @param	{function}		callback
		 *   Callback to run after setting the HTML string. Can also be used to create the HTML content.
		 *   Gets the this reference and the parent element passed as arguments.<br>
		 *   Signature: <code>function(thisReference : object, parent : element) : void</code>
		 * @param	{mixed}			thisReference
		 *   Reference to an object which should be referenced in the callback, because in the callback it is not possible to use some objects. (e.g. _this)
		 * @param	{int}			position
		 *   The position of the textarea in the wrapper.
		 */
		this.addHTML = function(id, wrapperId, block, html, callback, thisReference, position) {
			var create = function(parentTable, elementId, value, options) {
				// Create html table row.
				var htmlRow	= _this.myGM.addElement('tr', parentTable);
				
				// Create cell.
				var htmlCell		= _this.myGM.addElement('td', htmlRow);
				htmlCell.colSpan	= 2;
				htmlCell.classList.add('center');
				
				// Add the HTML.
				if(options.html) {
					htmlCell.innerHTML = options.html;
				}
				
				// Run the callback.
				if(options.callback) {
					options.callback(options.thisReference, htmlCell);
				}
			};
			
			// Add the HTML text.
			_addElement(id, wrapperId, block, { html: html, thisReference: thisReference, callback: callback }, null, create, null, position);
		};
		
		/**
		 * Add a new horizontal line to the options tab.
		 * 
		 * @instance
		 * 
		 * @param	{string}				wrapperId
		 *   The id of the wrapper.
		 * @param	{string || int}			block
		 *   The block of the wrapper, the horizontal line belongs to.
		 * @param	{int}					position
		 *   The position of the horizontal line in the wrapper.
		 */
		this.addHr = function(wrapperId, block, position) {
			/*
			 * Function to create the horizontal line.
			 */
			var create = function(parentTable, elementId, value, options) {
				// Create label table row.
				var tr	= _this.myGM.addElement('tr', parentTable);
				
				// Create cell.
				var lineCell		= _this.myGM.addElement('td', tr);
				lineCell.colSpan	= 2;
				lineCell.classList.add('left');
				
				// Add the line.
				_this.myGM.addElement('hr', lineCell);
			};
			
			// Add the line.
			_addElement('hr' + _hrId, wrapperId, block, null, null, create, null, position);
			
			// Raise the counter.
			_hrId++;
		};
		
		/**
		 * Deletes an wrapper with all option elements contained in it.
		 * 
		 * @instance
		 * 
		 * @param	{string}	id
		 *   Id of the wrapper to delete.
		 */
		this.deleteWrapper = function(id) {
			// No wrapper with this id => log.
			if(!_wrapper[id]) {
				_this.con.log('Options.deleteWrapper: Wrapper with id "' + id + '" does not exist.');
			} else {
				// Delete the wrapper.
				delete _wrapper[id];
				delete _options[id];
				
				var position = -1;
				
				for(var i = 0; i < _wrapperOrder.length; i++) {
					if(_wrapperOrder[i] == id) {
						position = i;
						break;
					}
				}
				
				_wrapperOrder.remove(position);
			}
		};
		
		/**
		 * Deletes an option element.
		 * 
		 * @instance
		 * 
		 * @param	{string}	wrapperId
		 *   The id of the wrapper containing the element.
		 * @param	{string}	elementId
		 *   The id of the element to delete.
		 */
		this.deleteElement = function(wrapperId, elementId) {
			if(!_wrapper[wrapperId] && _wrapper[wrapperId].elements[elementId]) {
				_this.con.log('Options.deleteElement: Element with id "' + wrapperId + '_' + elementId + '" does not exist.');
			} else {
				delete _wrapper[wrapperId].elements[elementId];
				delete _options[wrapperId][elementId];
				
				var position = -1;
				
				for(var i = 0; i < _wrapper[wrapperId].elementOrder.length; i++) {
					if(_wrapper[wrapperId].elementOrder[i] == elementId) {
						position = i;
						break;
					}
				}
				
				_wrapper[wrapperId].elementOrder.remove(position);
			}
		};
		
		/**
		 * Get the stored value of an option.
		 * 
		 * @instance
		 * 
		 * @param	{string}	wrapperId
		 *   Id of the wrapper of the option element.
		 * @param	{string}	optionId
		 *   Id of the option element.
		 * 
		 * @return	{mixed}
		 *   The stored value.
		 */
		this.getOption = function(wrapperId, optionId) {
			var option = null;
			
			// Get the option.
			if(_options[wrapperId] && (_options[wrapperId][optionId] || _options[wrapperId][optionId] == false)) {
				option = _options[wrapperId][optionId];
			} else {
				_this.con.log('Options.getOption: Option with id "' + wrapperId + '_' + optionId + '" not defined.');
			}
			
			// Return the option.
			return option;
		};
		
		/**
		 * Set the stored value of an option.
		 * 
		 * @instance
		 * 
		 * @param	{string}	wrapperId
		 *   Id of the wrapper of the option element.
		 * @param	{string}	optionId
		 *   Id of the option element.
		 * @param	{mixed}		value
		 *   The value to store.
		 */
		this.setOption = function(wrapperId, optionId, value) {
			// Set the option value.
			if(_options[wrapperId] && (_options[wrapperId][optionId] || _options[wrapperId][optionId] == false)) {
				_options[wrapperId][optionId] = value;
			} else {
				_this.con.log('Options.setOption: Option with id "' + wrapperId + '_' + optionId + '" not yet defined. Value "' + value + '" not stored.');
			}
			
			// Save the options.
			_saveOptions(true);
		};
		
		/*----------------------------------------*
		 * Register the show option panel handler *
		 *----------------------------------------*/
		
		// Register the option handler to show the options in the option panel.
		_this.RefreshHandler.add('options', 'showOptionPanel', _showOptionPanel);
		
		/*-------------------------------*
		 * Add the option panel options. *
		 *-------------------------------*/
		
		this.addWrapper('optionPanelOptions', { id: 'default_optionPanel_section_optionPanelOptions_title' });
		var opts = new Array(
				{ value: 'roundButton',		name: { id: 'default_optionPanel_section_optionPanelOptions_label_useStyle_option_roundButton'			}	},
				{ value: 'rectangleButton',	name: { id: 'default_optionPanel_section_optionPanelOptions_label_useStyle_option_rectangularButton'	}	}
			);
		this.addSelect('useStyle', 'optionPanelOptions', 'selects', 'roundButton', { id: 'default_optionPanel_section_optionPanelOptions_label_useStyle_description' }, opts);
		this.addHTML('exportOptions', 'optionPanelOptions', 'links', null, _exportOptions, _this);
		this.addHTML('importOptions', 'optionPanelOptions', 'links', null, _importOptions, _this);
		this.addHTML('resetOptions', 'optionPanelOptions', 'links', null, _resetOptions, _this);
	}
	
	/**
	 * Handler for options the user can set / change.
	 * 
	 * @instance
	 * 
	 * @type	IkariamCore~Options
	 */
	this.Options = new Options;
	
	/**
	 * Instantiate a new set of updating functions and start an initial update check.
	 * 
	 * @inner
	 * 
	 * @class
	 * @classdesc	Functions for checking for updates for the script.
	 */
	function Updater() {
		/*--------------------------------------------*
		 * Private variables, functions and settings. *
		 *--------------------------------------------*/
		
		/**
		 * Stores if the update was instructed by the user.
		 * 
		 * @private
		 * @inner
		 * 
		 * @default false
		 * 
		 * @type	boolean
		 */ 
		var _manualUpdate = false;
	
		/**
		 * Compares two versions and returns if there is a new version.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{string}	versionOld
		 *   The old version number.
		 * @param	{string}	versionNew
		 *   The new version number.
		 * @param	{int}		maxPartsToCompare
		 *   The number of parts to compare at most. (optional, default "compare all parts")
		 *
		 * @return	{boolean}
		 *   If a new version is available.
		 */
		var _newerVersion = function(versionOld, versionNew, maxPartsToCompare) {
			// Stores if a new version is available.
			var newVersion = false;
	
			// Force both versions to be a string.
			versionOld += '';
			versionNew += '';
	
			// The parts of the versions.
			var versionOldParts = versionOld.split('.');
			var versionNewParts = versionNew.split('.');
	
			// The bigger number of parts of the versions.
			var biggerNumberOfParts = versionOldParts.length > versionNewParts.length ? versionOldParts.length : versionNewParts.length;
	
			// If all parts should be compared, set maxPartsToCompare to all parts.
			if(!maxPartsToCompare || maxPartsToCompare < 1) {
				maxPartsToCompare = biggerNumberOfParts + 1;
			}
	
			// Loop over all parts of the version with less parts.
			for(var i = 0; i < biggerNumberOfParts; i++) {
				// Get the value of the parts.
				var versionPartOld = parseInt(versionOldParts[i] || 0);
				var versionPartNew = parseInt(versionNewParts[i] || 0);
	
				// If the old part is smaller than the new, return true.
				if(versionPartOld < versionPartNew) {
					newVersion = true;
					break;
	
				// Else if the old part is bigger than the new it is now new version; return false.
				} else if(versionPartOld > versionPartNew || i == maxPartsToCompare - 1) {
					newVersion = false;
					break;
				}
			}
	
			// Return if there is a new version.
			return newVersion;
		};
		
		/**
		 * Extract the update history from the metadata.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{string[]}	metadata
		 *   Array with the formated metadata.
		 *
		 * @return	{mixed[]}
		 *   The extracted update history.
		 */
		var _extractUpdateHistory = function(metadata) {
			// Create variable to store the update history.
			var updateHistory = {};
	
			// Loop over all update history data.
			for(var i = 0; i < metadata['history'].length; i++) {
				// Get the information from the update history data.
				var tmp = metadata['history'][i].match(/^(\S+)\s+(\S+)\s+(.*)$/);
	
				// If there is no array for this version create one.
				if(!updateHistory[tmp[1]]) {
					updateHistory[tmp[1]] = {};
				}
				
				switch(tmp[2].trim(':').toLowerCase()) {
					case 'release':
						updateHistory[tmp[1]].release = tmp[3];
					  break;
					
					case 'feature':
						if(!updateHistory[tmp[1]].feature) {
							updateHistory[tmp[1]].feature = new Array(tmp[3]);
						} else {
							updateHistory[tmp[1]].feature.push(tmp[3]);
						}
					  break;
					
					case 'change':
						if(!updateHistory[tmp[1]].change) {
							updateHistory[tmp[1]].change = new Array(tmp[3]);
						} else {
							updateHistory[tmp[1]].change.push(tmp[3]);
						}
					  break;
					
					case 'bugfix':
						if(!updateHistory[tmp[1]].bugfix) {
							updateHistory[tmp[1]].bugfix = new Array(tmp[3]);
						} else {
							updateHistory[tmp[1]].bugfix.push(tmp[3]);
						}
					  break;
					
					case 'language':
						if(!updateHistory[tmp[1]].language) {
							updateHistory[tmp[1]].language = new Array(tmp[3]);
						} else {
							updateHistory[tmp[1]].language.push(tmp[3]);
						}
					  break;
					
					case 'core':
						if(!updateHistory[tmp[1]].core) {
							updateHistory[tmp[1]].core = new Array(tmp[3]);
						} else {
							updateHistory[tmp[1]].core.push(tmp[3]);
						}
					  break;
					
					default:
						if(!updateHistory[tmp[1]].other) {
							updateHistory[tmp[1]].other = new Array(tmp[2] + " " + tmp[3]);
						} else {
							updateHistory[tmp[1]].other.push(tmp[2] + " " + tmp[3]);
						}
					  break;
				}
			}
	
			// Return the update history.
			return updateHistory;
		};
		
		/**
		 * Format the update history using some HTML codes.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{mixed[]}	updateHistory
		 *   The update history.
		 *
		 * @return	{string}
		 *   The formated update history.
		 */
		var _formatUpdateHistory = function(updateHistory) {
			// Create a variable for the formated update history.
			var formatedUpdateHistory = '';
			
			// Loop over all versions.
			for(var version in updateHistory) {
				if(Object.prototype.hasOwnProperty.call(updateHistory, version)) {
					// Create a headline for each version and start a table.
					formatedUpdateHistory += '<h2>v ' + version + '</h2><span class="smallFont">' + updateHistory[version].release + '</span></small><br><table class="' + _this.myGM.prefix() + 'updateTable"><tbody>';
		
					// Loop over all types.
					for(var type in updateHistory[version]) {
						if(Object.prototype.hasOwnProperty.call(updateHistory[version], type) && type != 'release') {
							// Create a table row for each type and start a list for the elements.
							formatedUpdateHistory += '<tr><td class="' + _this.myGM.prefix() + 'updateDataType">' + _this.Language.$('default_update_possible_type_' + type) + '</td><td class="' + _this.myGM.prefix() + 'updateDataInfo"><ul>';
			
							// Loop over the elements and add them to the list.
							for(var i = 0 ; i < updateHistory[version][type].length; i++) {
								formatedUpdateHistory += '<li>' + updateHistory[version][type][i] + '</li>';
							}
			
							// End the list.
							formatedUpdateHistory += '</ul></td></tr>';
						}
					}
		
					// End the table.
					formatedUpdateHistory += '</tbody></table><br>';
				}
			}
	
			// Return the formated update history.
			return formatedUpdateHistory;
		};
		
		/**
		 * Show the update information panel.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{mixed[]}	metadata
		 *   Array with formated metadata
		 */
		var _showUpdateInfo = function(metadata) {
			// Get the update history.
			var updateHistory = _extractUpdateHistory(metadata);
	
			// Set the notification text.
			var notificationText = {
				header:		_this.Language.$('default_update_possible_header'),
				bodyTop:	_this.Language.$('default_update_possible_text', ['<a href="http://userscripts.org/scripts/show/' + scriptInfo.id + '" target="_blank" >' + scriptInfo.name + '</a>', scriptInfo.version, metadata.version]) + '<br>&nbsp;&nbsp;<b><u>' + _this.Language.$('default_update_possible_history') + '</u></b>',
				bodyBottom:	_formatUpdateHistory(updateHistory),
				confirm:	_this.Language.$('default_update_possible_button_install'),
				abort:		_this.Language.$('default_update_possible_button_hide')
			};
	
			// Set the notification callback.
			var notificationCallback = {
				confirm:	function() { _this.win.top.location.href = 'http://userscripts.org/scripts/source/' + scriptInfo.id + '.user.js'; },
				abort:		function() { _this.myGM.setValue('updater_hideUpdate', metadata.version + ''); }
			};
	
			// Show a notification.
			_this.myGM.notification(notificationText, notificationCallback);
		};
	
		/**
		 * Format the given metadata.
		 * 
		 * @private
		 * @inner
		 * 
		 * @param	{string}	metadata
		 *   The metadata to format.
		 *
		 * @return	{string[]}
		 *   The formatted metadata as array.
		 */
		var _formatMetadata = function(metadataIn) {
			// Create an array for the formated metadata.
			var metadataOut = new Array();
	
			// Extract the tags from the metadata.
			var innerMeta = metadataIn.match(/\/\/ ==UserScript==((.|\n|\r)*?)\/\/ ==\/UserScript==/)[0];
	
			// If there are some tags.
			if(innerMeta) {
				// Extract all tags.
				var tags = innerMeta.match(/\/\/ @(.*?)(\n|\r)/g);
	
				// Loop over all tags.
				for(var i = 0; i < tags.length; i++) {
					// Extract the data from the tag.
					var tmp = tags[i].match(/\/\/ @(.*?)\s+(.*)/);
	
					// If there is no data with this tag create a new array to store all data with this tag.
					if(!metadataOut[tmp[1]]) {
						metadataOut[tmp[1]] = new Array(tmp[2]);
	
					// Otherwise add the data to the existing array.
					} else {
						metadataOut[tmp[1]].push(tmp[2]);
					}
				}
			}
	
			// Return the formated metadata.
			return metadataOut;
		};
		
		/*-------------------------------------------*
		 * Public variables, functions and settings. *
		 *-------------------------------------------*/
		
		/**
		 * Check for updates for the script. Automatically done on every instantiation of {@link IkariamCore}
		 * if the period from the last update is bigger than the check interval.
		 * 
		 * @instance
		 */
		this.checkForUpdates = function() {
			// Send a request to the userscripts.org server to get the metadata of the script to check if there is a new Update.
			var notPossible = _this.myGM.xhr({
					method: 'GET',
					url: 'http://userscripts.org/scripts/source/' + scriptInfo.id + '.meta.js',
					headers: {'User-agent': 'Mozilla/5.0', 'Accept': 'text/html'},
					onload: function(response) {
						// Extract the metadata from the response.
						var metadata = _formatMetadata(response.responseText);
						
						// If a new Update is available and the update hint should be shown.
						if(_newerVersion(scriptInfo.version, metadata.version, _this.Options.getOption('updateOptions', 'updateNotifyLevel')) && (_this.myGM.getValue('updater_hideUpdate', scriptInfo.version) != metadata.version) || _manualUpdate) {
							// Show update dialogue.
							_showUpdateInfo(metadata);
	
						// If there is no new update and it was a manual update show hint.
						} else if(_manualUpdate)	{
							// Set the notification text.
							var notificationText = {
								header:	_this.Language.$('default_update_noNewExists_header'),
								body:	_this.Language.$('default_update_noNewExists_text', ['<a href="http://userscripts.org/scripts/show/' + scriptInfo.id + '" target="_blank" >' + scriptInfo.name + '</a>', scriptInfo.version])
							};
	
							// Show a notification.
							_this.myGM.notification(notificationText);
						}
					}
				});
			
			if(notPossible && notPossible == true) {
				// Set the update interval to max.
				_this.Options.setOption('updateOptions', 'updateInterval', 2419200);

				// Set the notification text.
				var notificationText = {
					header:	_this.Language.$('default_update_notPossible_header'),
					body:	_this.Language.$('default_update_notPossible_text', ['<a href="http://userscripts.org/scripts/show/' + scriptInfo.id + '" target="_blank" >' + scriptInfo.name + '</a>', scriptInfo.version])
				};

				// Show a notification.
				_this.myGM.notification(notificationText);
			}
		};
		
		/**
		 * Search manually for updates. Forces to search for updates. Even shows a popup if no new update is available.
		 * 
		 * @instance
		 */
		this.doManualUpdate = function() {
			// Manual Update.
			_manualUpdate = true;
			
			// Check for Updates.
			_this.Updater.checkForUpdates();
	
			// Set the time for the last update check to now.
			_this.myGM.setValue('updater_lastUpdateCheck', (new Date()).getTime() + '');
		};
		
		/*------------------------*
		 * Add the updater styles *
		 *------------------------*/
		
		// Set the updater style.
		_this.myGM.addStyle(
				"." + _this.myGM.prefix() + "updateTable			{ border-collapse: separate; border-spacing: 2px; } \
				 ." + _this.myGM.prefix() + "updateDataType			{ width: 100px; padding: 5px 0px 5px 5px; border: 1px solid #D2A860; } \
				 ." + _this.myGM.prefix() + "updateDataInfo			{ width: 300px; padding: 5px 5px 5px 20px; border: 1px solid #D2A860; } \
				 ." + _this.myGM.prefix() + "updateDataInfo ul li	{ list-style: disc outside none; }",
				'updater', true
			);
		
		/*----------------------*
		 * Register the options *
		 *----------------------*/
		
		_this.Options.addWrapper('moduleOptions', { id: 'default_optionPanel_section_module_title' }, 0);
		_this.Options.addCheckbox('updateActive', 'moduleOptions', 1, true, { id: 'default_optionPanel_section_module_label_updateActive' });
		_this.Options.addHr('moduleOptions', 1);
		
		// Array for update interval values and names.
		var _updateIntervalOpts = new Array(
				{ value: 3600,		name: { id: 'default_optionPanel_section_update_label_interval_option_hour'		}	},
				{ value: 43200,		name: { id: 'default_optionPanel_section_update_label_interval_option_hour12'	}	},
				{ value: 86400,		name: { id: 'default_optionPanel_section_update_label_interval_option_day'		}	},
				{ value: 259200,	name: { id: 'default_optionPanel_section_update_label_interval_option_day3'		}	},
				{ value: 604800,	name: { id: 'default_optionPanel_section_update_label_interval_option_week'		}	},
				{ value: 1209600,	name: { id: 'default_optionPanel_section_update_label_interval_option_week2'	}	},
				{ value: 2419200,	name: { id: 'default_optionPanel_section_update_label_interval_option_week4'	}	}
			);
		
		var _updateNotifyLevelOpts = new Array(
				{ value: 0,	name: { id: 'default_optionPanel_section_update_label_notifyLevel_option_all'	}	},
				{ value: 1,	name: { id: 'default_optionPanel_section_update_label_notifyLevel_option_major'	}	},
				{ value: 2,	name: { id: 'default_optionPanel_section_update_label_notifyLevel_option_minor'	}	},
				{ value: 3,	name: { id: 'default_optionPanel_section_update_label_notifyLevel_option_patch'	}	}
			);
		
		var _searchUpdates = function(_this, parent) {
			var updateLink			= _this.myGM.addElement('a', parent);
			updateLink.href			= 'javascript:;';
			updateLink.innerHTML	= _this.Language.$('default_optionPanel_section_update_label_manual', new Array(scriptInfo.name));
			updateLink.addEventListener('click', _this.Updater.doManualUpdate, false);
		};
		
		_this.Options.addWrapper('updateOptions', { id: 'default_optionPanel_section_update_title' }, 1);
		_this.Options.addSelect('updateInterval', 'updateOptions', 'generalOptions', 3600, { id: 'default_optionPanel_section_update_label_interval_description' }, _updateIntervalOpts);
		_this.Options.addSelect('updateNotifyLevel', 'updateOptions', 'generalOptions', 0, { id: 'default_optionPanel_section_update_label_notifyLevel_description' }, _updateNotifyLevelOpts);
		_this.Options.addHTML('manualUpdateLink', 'updateOptions', 'generalOptions', null, _searchUpdates, _this);
		
		/*-------------------------------------*
		 * Check automatically for new updates *
		 *-------------------------------------*/
		
		// Get the difference between now and the last check.
		var _lastCheck	= _this.myGM.getValue('updater_lastUpdateCheck', 0);
		var _millis		= (new Date()).getTime();
		var _diff		= _millis - _lastCheck;
		
		// If the module is active and the period from the last update is bigger than the check interval, check for updates.
		if(_this.Options.getOption('moduleOptions', 'updateActive') && _diff > _this.Options.getOption('updateOptions', 'updateInterval') * 1000) {
			// No manual Update.
			_manualUpdate = false;

			// Check for Updates.
			this.checkForUpdates();

			// Set the time for the last update check to now.
			_this.myGM.setValue('updater_lastUpdateCheck', _millis + '');
		}
	}
	
	/**
	 * Updater to check for updates on Userscripts.org.
	 * 
	 * @instance
	 * 
	 * @type	IkariamCore~Updater
	 */
	this.Updater = new Updater;
}