/**************************************************************************
*
*  @@@BUILDINFO@@@ 35omvUI-2.jsx 2.0.1.74   12-June-2007
*  Copyright 2006-2007 Adobe Systems Incorporated
*  All Rights Reserved.
*
* NOTICE:  All information contained herein is, and remains the property of
* Adobe Systems Incorporated  and its suppliers,  if any.  The intellectual 
* and technical concepts contained herein are proprietary to  Adobe Systems 
* Incorporated  and its suppliers  and may be  covered by U.S.  and Foreign 
* Patents,patents in process,and are protected by trade secret or copyright 
* law.  Dissemination of this  information or reproduction of this material
* is strictly  forbidden  unless prior written permission is  obtained from 
* Adobe Systems Incorporated.
**************************************************************************/

// Object Model Viewer.
// Current limitations:
// 1) The first TOC level is ignored.
// 2) Namespace and interface declarations are not interpreted.
// 3) Only the first <package> is interpreted.

// Create an OMV viewer. Takes the BridgeTalk ID of the target.
// Properties:
// data			the OMVData object containing XML.
// w			the window
// classes		the Classes listbox
// types		the Element Types listbox
// elements		the Properties and Methods listbox
// display		the HTML display
// classesItems	an object with class names as properties and the class list items as values

function OMV (data)
{
	this.data = data;
	this.classesItems = {};
	this.classHrefs = {
//		Object			:		"$COMMON/javascript.xml#/",
		Array			:		"$COMMON/javascript.xml#/",
		Math			:		"$COMMON/javascript.xml#/",
		Date			:		"$COMMON/javascript.xml#/",
		Function		:		"$COMMON/javascript.xml#/",
		String			:		"$COMMON/javascript.xml#/",
		Number			:		"$COMMON/javascript.xml#/",
		Boolean			:		"$COMMON/javascript.xml#/",
		RegExp			:		"$COMMON/javascript.xml#/",
		Error			:		"$COMMON/javascript.xml#/",
		File			:		"$COMMON/javascript.xml#/",
		Folder			:		"$COMMON/javascript.xml#/",
		Socket			:		"$COMMON/javascript.xml#/",
		ReflectionInfo	:		"$COMMON/javascript.xml#/",
		Reflection		:		"$COMMON/javascript.xml#/",
		Dictionary		:		"$COMMON/javascript.xml#/",
		QName			:		"$COMMON/javascript.xml#/",
		Namespace		:		"$COMMON/javascript.xml#/",
		XML				:		"$COMMON/javascript.xml#/",
		UnitValue		:		"$COMMON/javascript.xml#/",

		ScriptUI		:		"$COMMON/scriptui.xml#/",
		Window			:		"$COMMON/scriptui.xml#/",
		LayoutManager	:		"$COMMON/scriptui.xml#/",
		ScriptUIGraphics:		"$COMMON/scriptui.xml#/",
		ScriptUIPen		:		"$COMMON/scriptui.xml#/",
		ScriptUIBrush	:		"$COMMON/scriptui.xml#/",
		ScriptUIPath	:		"$COMMON/scriptui.xml#/",
		ScriptUIFont	:		"$COMMON/scriptui.xml#/",
		ScriptUIImage	:		"$COMMON/scriptui.xml#/",
		DrawState		:		"$COMMON/scriptui.xml#/",
		StaticText		:		"$COMMON/scriptui.xml#/",
		Button			:		"$COMMON/scriptui.xml#/",
		IconButton		:		"$COMMON/scriptui.xml#/",
		EditText		:		"$COMMON/scriptui.xml#/",
		ListBox			:		"$COMMON/scriptui.xml#/",
		DropDownList	:		"$COMMON/scriptui.xml#/",
		ListItem		:		"$COMMON/scriptui.xml#/",
		Checkbox		:		"$COMMON/scriptui.xml#/",
		Scrollbar		:		"$COMMON/scriptui.xml#/",
		RadioButton		:		"$COMMON/scriptui.xml#/",
		Slider			:		"$COMMON/scriptui.xml#/",
		Progressbar		:		"$COMMON/scriptui.xml#/",
		TreeView		:		"$COMMON/scriptui.xml#/",
		FlashPlayer		:		"$COMMON/scriptui.xml#/",
		Group			:		"$COMMON/scriptui.xml#/",
		Panel			:		"$COMMON/scriptui.xml#/",
		Point			:		"$COMMON/scriptui.xml#/",
		Dimension		:		"$COMMON/scriptui.xml#/",
		Bounds			:		"$COMMON/scriptui.xml#/",
		UIEvent			:		"$COMMON/scriptui.xml#/",
		MenuElement		:		"$COMMON/scriptui.xml#/"
	};

	// The preferred size is == the default size of a doc window
	this.w = new Window (
		"documentwindow {											        \
			preferredSize   : [470, 510],									\
			orientation     : 'column',										\
			margins         : 2,                                            \
			spacing         : 2,                                            \
			properties      :                                               \
			{                                                               \
			    minimumSize     : [350, 180],                               \
				closeOnKey		: 'OSCmnd+W',								\
			},																\
			grp				: Group {										\
				orientation     : 'row',									\
				size			: [ 100, 300 ],								\
				alignment		: ['fill', 'top' ],							\
				classes			: Group {									\
					orientation     : 'column',								\
					size			: [ 100, 300 ],							\
					alignment		: ['fill', 'top' ],						\
					label			: StaticText {							\
						alignment		: 'left',							\
						text			: '$$$/ESToolkit/OMV/Classes=Classes',	\
					},														\
					classes			: ListBox {								\
						properties		: {									\
							multiselect	: true,								\
						    helpTip     : '$$$/ESToolkit/OMV/htClasses=Classes',	\
						}													\
						alignment	: ['fill', 'fill' ]						\
					},														\
				},															\
				elements		: Group {									\
					orientation     : 'column',								\
					size			: [ 100, 300 ],							\
					alignment		: ['fill', 'top' ],						\
					label			: StaticText {							\
						alignment	: 'left',								\
						text		: '$$$/ESToolkit/OMV/Elements=Properties and Methods',	\
					},														\
					types		: ListBox {									\
						size		: [ 100, 70 ],							\
						alignment	: ['fill', 'top' ],						\
    				    helpTip     : '$$$/ESToolkit/OMV/htTypes=Types',	\
					},														\
					elements	: ListBox {									\
						properties	: {										\
							multiselect	: true								\
						},													\
						alignment	: ['fill', 'fill' ],						\
					    helpTip     : '$$$/ESToolkit/OMV/htElements=Elements',	\
					}														\
				}															\
			},																\
			find			: Group {										\
				orientation     : 'row',									\
				alignment		: 'fill',									\
				btns			: Group {									\
					margins		: 0,										\
					spacing		: 2,										\
					back		: IconButton {								\
						preferredSize: [24, 24],							\
						enabled		: false,									\
					    helpTip     : '$$$/ESToolkit/OMV/htBack=Go to previous entry.',	\
					},														\
					fwd			: IconButton {								\
						preferredSize: [24, 24],							\
						enabled		: false,									\
					    helpTip     : '$$$/ESToolkit/OMV/htNext=Go to next entry.',	\
					},														\
					copy		: IconButton {								\
						preferredSize: [24, 24],							\
						enabled		: false,									\
					    helpTip     : '$$$/ESToolkit/OMV/htCopy=Copy description to clipboard',	\
					},														\
				},															\
				label			: StaticText {								\
					alignment	: ['left', 'center' ],						\
					text		: '$$$/ESToolkit/FindReplaceDlg/FindLbl=Find:',	\
				},															\
				find			: EditText {								\
					preferredSize: [100,20],								\
					alignment	: ['fill', 'top' ],							\
				    helpTip     : '$$$/ESToolkit/OMV/htFind=Type in the element you are looking for here.',	\
				}															\
			},																\
			dispBorder		: Panel {										\
				properties		: {											\
					borderStyle		: 'sunken',								\
				},															\
				alignment		: ['fill', 'fill' ],						\
				display			: FlashPlayer {								\
					minimumSize		: [10, 10],								\
					alignment		: ['fill', 'fill' ]						\
				}															\
			}																\
		}");

	this.classes  = this.w.grp.classes.classes;
	this.types	  = this.w.grp.elements.types;
	this.elements = this.w.grp.elements.elements;
	this.display  = this.w.dispBorder.display;
	this.find	  = this.w.find.find;
	this.findLabel= this.w.find.label;
	this.fwd	  = this.w.find.btns.fwd;
	this.back	  = this.w.find.btns.back;
	this.copy	  = this.w.find.btns.copy;

	this.w.omv		  =
	this.classes.omv  =
	this.types.omv	  =
	this.elements.omv =
	this.display.omv  =
	this.find.omv	  =
	this.fwd.omv	  =
	this.back.omv	  =
	this.copy.omv	  = this;

	OMV.windows.push (this.w);

	this.w.text = this.data.xml.map.@title;

	this.fwd.icon			= ScriptUI.newImage ('#BrowseRight_R', '#BrowseRight_N');
	this.back.icon			= ScriptUI.newImage ('#BrowseLeft_R',  '#BrowseLeft_N');
	this.copy.icon			= ScriptUI.newImage ('#Copy_R',        '#Copy_N');

	this.copy.enabled = true;
	this.fwd.enabled  = false;
	this.back.enabled = false;

	// This is set to nonzero if only a list box selection changes,
	// and no data is supposed to be reloaded.
	this.ignoreChanges = 0;

	this.w.onNotify = function( reason )
	{
		if( reason == 'shutdown' )
		{
		    globalBroadcaster.unregisterClient( this );
			this.omv.detachHandlers();
			this.hide();
		}
	}

	this.w.onClose = function()
	{
		// no XML = no use for this window
		if (!this.omv.data.xml)
			return true;
		// otherwise, just hide it
		this.hide();
		return false;
	}

	globalBroadcaster.registerClient( this.w );
	
	this.w.onResize = function ()
	{
		this.layout.resize();
	}

	this.setupHTMLControl (this.display);
	var ok = true;

	// LAYOUT BUG: need to show now already
	this.w.show();
	this.startHTML();

	if (this.data.showProgress)
	{
		this.dlg = new ProgressBox ('$$$/ESToolkit/OMV/SetupUI=Initializing the UI...');
		ok = this.loadTOC();
		this.dlg.stop();
		this.dlg = null;
	}
	else
		ok = this.loadTOC();
/*	if (ok)
	{
		// LAYOUT BUG: enable this statement
		this.w.show();	
		this.startHTML();
	}
*/
}

// This is the array of all OMV windows.

OMV.windows = [];

// This is the default font size of the OMV HTML viewer.

OMV.htmlFontSize = 11;

// The OMV icons are here, addressed by their names.

OMV.icons = {};

// The list of OMV that need to unpdate their HTML.
// HTML is updated delayed.

OMV.update = [];

// Set up all OMV entries in the Help menu.

OMV.setup = function()
{
	// Set up the OMVData
	OMVData.setup();

	// Set up the icons
	this.icons.ENUMERATION	= ScriptUI.newImage ("#Enumeration");
	this.icons.CLASS		= ScriptUI.newImage ("#Class");
	this.icons.METHOD		= ScriptUI.newImage ("#Method");
	this.icons.ROPROPERTY	= ScriptUI.newImage ("#PropertyRO");
	this.icons.RWPROPERTY	= ScriptUI.newImage ("#PropertyRW");
}

//////////////////////////////////////////////////////////////////
//
//	onChange Handlers
//	These handlers are attached to the list and edit boxes.
//	The Classes onChange handler is attached after the
//	list box has been filled to avoid unnecessary calls.
//
//////////////////////////////////////////////////////////////////

OMV.prototype.attachHandlers = function()
{
	// Classes change notification.
	this.classes.onChange = function()
	{
		if (this.omv.ignoreChanges)
			return;
		var item = this.selection;
		if (item)
		{
			var xml = null;
			if (item.length == 1)
			{
				if (!item[0].xml)
					item[0].xml = this.omv.data.findXML (item[0].clsName);
				if (!item[0].xml)
					return;
				this.omv.loadClass (item[0].xml);
				return;
			}
			else
			{
				xml = XML("<list/>").children();
				for (var i = 0; i < item.length; i++)
				{
					if (!item[i].xml)
						item[i].xml = this.omv.data.findXML (item[i].clsName);
					xml += item[i].xml;
				}
			}
		}
		this.omv.ignoreChanges++;
		this.omv.types.xml = null;
		this.omv.types.removeAll();
		this.omv.elements.xml = null;
		this.omv.elements.removeAll();
		this.omv.ignoreChanges--;
		this.omv.displayXML (xml);
	}

	// Element types change notification.
	this.types.onChange = function()
	{
		if (this.omv.ignoreChanges)
			return;
		var item = this.selection;
		if (item)
			this.omv.loadClassElements (item.xml);
		else
		{
			this.omv.ignoreChanges++;
			this.omv.elements.xml = null;
			this.omv.elements.removeAll();
			this.omv.setHTML (null);
			this.omv.ignoreChanges--;
		}
	}

	// Methods/properties change notification
	this.elements.onChange = function()
	{
		if (this.omv.ignoreChanges)
			return;
		var item = this.selection;
		var xml = null;

		if (item)
		{
			if (item.length > 1)
			{
				xml = XML("<list/>").children();
				for (var i = 0; i < item.length; i++)
					xml += item[i].xml;
			}
			else
				xml = item[0].xml;
		}
		this.omv.displayXML (xml);
	}

	this.find.onChanging = function()
	{
		if (!this.ignoreChanges)
			this.omv.findText (this.text);
	}
}

OMV.prototype.detachHandlers = function()
{
	delete this.classes.onChange;
	delete this.types.onChange;
	delete this.elements.onChange;
	delete this.find.onChanging;
}

//////////////////////////////////////////////////////////////////
//
//	Deleting
//	Called when a dynamic OMV was reloaded 
//
//////////////////////////////////////////////////////////////////

OMV.prototype.detach = function()
{
	for (var i = 0; i < OMV.windows.length; i++)
	{
		if (OMV.windows [i] == this.w)
		{
			OMV.windows.splice (i, 1);
			break;
		}
	}
	this.w.hide();
	this.w = null;
	this.data.omv = null;
	this.data.menu.omv = null;
	this.data.menu.enabled = true;
}

//////////////////////////////////////////////////////////////////
//
//	Disabling
//
//////////////////////////////////////////////////////////////////

// Disable this UI due to errors.
// Also a callback from the OMVData instance in case on errors.

OMV.prototype.disable = function()
{
	this.data.badXML();
	this.data.menu.enabled = false;
	this.data.xml = null;
	if (this.w)
		this.w.close();
}

//////////////////////////////////////////////////////////////////
//
//	Data Loading
//
//////////////////////////////////////////////////////////////////

// Load the TOC into the Classes listbox. Returns false if aborted.

OMV.prototype.loadTOC = function()
{
	var list = this.sortedXML (this.data.map.children(), "navtitle");
	if (this.dlg)
		this.dlg.setProgress (0, list.length);

	for (var i = 0; i < list.length; i++)
	{
		if (this.dlg && !this.dlg.setProgress (i))
		{
			this.disable();
			return false;
		}
		var href = this.data.splitHref (list[i].@href.toString());
		// no packages!
		var xpath = 'classdef[@name = "' + href.cls + '"]';
		var xml   = null;
		if (this.data.xml['package'])
		{
			xml = this.data.xml['package'].xpath (xpath);
			if (!xml.length())
			{
				this.disable();
				return false;
			}
		}
		var item = this.classes.add ("item", list [i].@navtitle);
		this.classesItems [href.cls] = item;
		this.classHrefs   [href.cls] = "#/";
		item.xml	 = xml;
		item.omv	 = this;
		item.clsName = href.cls;
		if (item.xml)
			item.icon = ((item.xml.@enumeration == "true") ? OMV.icons.ENUMERATION : OMV.icons.CLASS);
	}
	// Attach onChange handlers here
	this.attachHandlers();
	return true;
}

// Load the next-level class info (which is the elements group).

OMV.prototype.loadClass = function (xml, noStack)
{
	if (xml == this.types.xml)
		return;

	var currentItem = this.types.selection ? this.types.selection.text : "";

	this.types.removeAll();
	this.types.xml = xml;
	var item;
	var selItem = null;
	var types = [ "constructor", "class", "instance", "prototype", "event" ];
	var typeNames = [ 
		"$$$/ESToolkit/OMV/Types/Constructors=Constructors", 
		"$$$/ESToolkit/OMV/Types/ClassElements=Class Elements", 
		"$$$/ESToolkit/OMV/Types/InstanceElements=Instance Elements",
		"$$$/ESToolkit/OMV/Types/PrototypeElements=Prototype Elements", 
		"$$$/ESToolkit/OMV/Types/Events=Events" ];
	this.ignoreChanges++;
	for (var i = 0; i < types.length; i++)
	{
		var container = xml.xpath ("elements[@type='" + types [i] + "']");
		if (container.length() > 0)
		{
			item = this.types.add ("item", localize (typeNames [i]));
			item.xml = container;
			item.omv = this;
			// Select either the first occurence, the last type, or instance elements
			if ((!selItem && types [i] == "instance") || item.text == currentItem)
				selItem = item;
		}
	}
	if (!selItem)
		selItem = this.types.items [0];
	this.ignoreChanges--;
	this.types.selection = selItem;
	// and load the HTML
	if (!noStack)
		this.pushHTMLStack (xml);
	this.setHTML (this.getHTML (xml));
}

// Fill in the elements for a class. The parent is the 2nd level.

OMV.prototype.loadClassElements = function (xml)
{
	// this is the <elements> container
	if (this.elements.xml == xml)
		return;
	this.elements.xml = xml;
	
	this.ignoreChanges++;
	this.elements.removeAll();
	var href = xml.parent().@name.toString() + "/" + xml.@type + "/";
	var isEnum = (xml.parent().@enumeration == "true");
	var props = this.sortedXML (xml.property, "name");
	var meths = this.sortedXML (xml.method, "name");
	var name, type;
	for (var i = 0; i < props.length; i++)
	{
		name = this.makeElementName (props [i]);
		var item = this.elements.add ("item", name);
		item.xml = props [i];
		item.omv = this;
		if (props[i].@name == "activeDocument")
			item.yes = true;
		if (isEnum)
			item.icon = OMV.icons.ENUMERATION;
		else
			item.icon = (props[i].@rwaccess == "readonly") ? OMV.icons.ROPROPERTY : OMV.icons.RWPROPERTY;
	}
	for (var i = 0; i < meths.length; i++)
	{
		name = this.makeElementName (meths [i]);
		item = this.elements.add ("item", name);
		item.xml = meths [i];
		item.omv = this;
		item.icon = OMV.icons.METHOD;
	}
	this.ignoreChanges--;
}

// Create an element name. Properties get a colon and the data type,
// and methods get the parameter list and the return type. If a data
// type is linkable, makeTypeInfo() creates a link.

OMV.prototype.makeElementName = function (xml)
{
	var name = xml.@name;
	var type = this.makeTypeInfo (xml.datatype, false);
	if (xml.localName() != "method")
	{
		if (type == "")
			type = "any";
		name += ": " + type;
	}
	else
	{
		var args = "(";
		var params = xml.parameters.children();
		for (var j = 0; j < params.length(); j++)
		{
			if (j)
				args += ", ";
			args += params [j].@name;
		}
		args += ")";
		if (args.length > 2)
			name += " ";
		name += args;
		if (type != "")
			name += ": " + type;
	}
	return name;
}

//////////////////////////////////////////////////////////////////
//
//	HTML Generation
//
//////////////////////////////////////////////////////////////////

// Generate HTML for a given XML. This XML could be one or more
// class-level entries, or one or more element level entries.

OMV.prototype.getHTML = function (xml)
{
	var html = null;
	if (!xml)
		html = XML ('<html><body/></html>');
	else
	{
		if (xml.length() == 1)
		{
			switch (xml.localName())
			{
				case "classdef":
					html = this.getClassHTML (xml);
					break;
				case "method":
					html = this.getMethodHTML (xml);
					break;
				case "property":
					html = this.getPropertyHTML (xml);
					break;
			}
		}
		if (html == null)
		{
			html = new XML ('<html><body/></html>');
			if (xml)
			{
				var br = new XML ('<br/>');
				var sorted = this.sortedXML (xml, "name");

				for (var i = 0; i < sorted.length; i++)
				{
					if (sorted [i].localName() == "classdef")
						html.body.appendChild (this.makeLink (this.data.getHrefForXML (sorted [i]), sorted [i].@name.toString()));
					else
					{
						var name;
						if (sorted[i].parent().@type == "constructor")
							name = "new " + this.makeElementName (sorted [i]);
						else
							name = sorted [i].parent().parent().@name + "." + this.makeElementName (sorted [i]);
						html.body.appendChild (this.makeLink (this.data.getHrefForXML (sorted [i]), name));
					}
					html.body.appendChild (br);
				}
			}
		}
	}
	return html;
}

// Generate and return HTML for a class.

OMV.prototype.getClassHTML = function (xml)
{
	var html = XML ('<html><body/></html>');
	html.body.appendChild (XML('<p><font size="+2"><b>' 
							+ xml.@name + '</b></font></p>'));
	if (xml.superclass.length() > 0)
	{
		var p = XML ("<p/>");
		p.appendChild (XML ("<b>Base Class: </b>"));
		var href = xml.superclass.@href.toString();
		if (href == "")
			href = this.data.getHrefForXML (xml.superclass);
		p.appendChild (this.makeLink (href, xml.@name.toString()));
		html.body.appendChild (p);
	}
	this.appendHelpText (html.body, xml);
	this.appendExamples (html.body, xml);
	return html;
}

// Generate and return HTML for a property.

OMV.prototype.getPropertyHTML = function (propXML)
{
	var html = XML ('<html><body/></html>');
	var type = this.makeTypeInfo (propXML.datatype, true);
	if (type == "")
		type = "any";
	var rw = "";
	switch (propXML.@rwaccess.toString())
	{
		case "readonly":	rw = "(Read Only)"; break;
		case "writeonly":	rw = "(Write Only)"; break;
	}
	html.body.appendChild (XML('<p><font size="+2"><b>' 
		+ propXML.parent().parent().@name + '.' + propXML.@name + '</b></font>&#160;' + rw + '</p>'));
	html.body.appendChild (XML('<p><b>Data Type: </b> '
							+ this.makeFullTypeInfo (propXML.datatype) + '</p>'));
	this.appendHelpText (html.body, propXML);
	this.appendExamples (html.body, propXML);
	return html;
}

// Generate and return HTML for a method.

OMV.prototype.getMethodHTML = function (methXML)
{
	var html = XML ('<html><body/></html>');
	// create the bold method name
	var block = XML('<b/>');
	block.appendChild (methXML.parent().parent().@name  + '.' + methXML.@name + " (");
	var params = methXML.parameters.children();
	for (var j = 0; j < params.length(); j++)
	{
		var text = (j > 0) ? ", " : "";
		text += params [j].@name + ":";
		block.appendChild (text);
		var typeXML = this.makeTypeInfo (params [j].datatype, true, true);
		if (typeXML.length() == 0)
			typeXML = "any";
		block.appendChild (typeXML);
	}
	block.appendChild (")");
	var type = this.makeTypeInfo (methXML.datatype, true);
	if (type.toString() != "")
	{
		block.appendChild (":");
		block.appendChild (type);
	}
	block.normalize();
	var p = XML ('<p><font size="+2"/></p>');
	p.font.appendChild (block);
	html.body.appendChild (p);
	this.appendHelpText (html.body, methXML);
	for (var j = 0; j < params.length(); j++)
	{
		var param = params [j];
		var type = this.makeFullTypeInfo (param.datatype);
		if (type == "")
			type = "any";
		var tempXML = '<p><b>' + param.@name;
		if (param.@optional == "true" || param.datatype.value != "")
			tempXML += " (optional)";
		tempXML += ': </b></p>';
		var p = XML (tempXML);
		p.appendChild ("Data Type: ");
		p.appendChild (type);
		html.body.appendChild (p);
		this.appendHelpText (html.body, param);
	}
	this.appendExamples (html.body, methXML);
	return html;
}

// Error message for bad HTML, or missing HTML display.

OMV.prototype.badHTML = function()
{
	errorBox ('$$$/ESToolkit/Alerts/BadFlash=Cannot display HTML!\nHelp is not available.');
	this.disable();
}

// Sort the given XML list and return an Array obbject wth the sorted elements.
// The sort is case insensitive.

OMV.prototype.sortedXML = function (xml, attrname)
{
	var arr = [];
	for (var i = 0; i < xml.length(); i++)
	{
		var s = xml [i]['@' + attrname].toString().toUpperCase();
		arr.push (s + '\n' + i);
	}
	arr.sort();
	for (i = 0; i < arr.length; i++)
		arr [i] = xml [arr [i].split ('\n')[1]];
	return arr;
}

// Create a type info string out of a <datatype> element
// datatypeXML - the <datatype> element
// doXML       - return XML rather than a string
// onlyType    - return type only, no details

OMV.prototype.makeTypeInfo = function (datatypeXML, doXML, onlyType)
{
	// make sure that we process only the first element of a possible list
	if (datatypeXML.length() > 1)
		datatypeXML = datatypeXML [0];

	// we don't want localization here for now.
	var xml = XML ('<list/>');
	var type = datatypeXML.type.toString();
	// small fixup
	if (type == "bool")
		type = "boolean";
	var elem = datatypeXML.array;
	var fmt;
	if (elem.length())
		fmt = (elem.@size == "")
			  ? "Array of %1"
			  : "Array [%2] of %1";
	else
		fmt = "%1";
	if (doXML)
	{
		var href = datatypeXML.type.@href.toString();
		if (!href.length && datatypeXML.parent())
		{
			// no HREF? Make sure that the datatype is not the same as the class def
			var tempxml = datatypeXML.parent().parent();
			while (tempxml && tempxml.localName() != "classdef")
				tempxml = tempxml.parent();
			if (tempxml && tempxml.@name != type)
			{
				// This is a different data type; try to find one based on the text
				href = this.classHrefs [type];
				if (!href)
					href = "";
				else
					// Apend the text
					href += type;
			}
		}
		if (href.length)
		{
			// check either for a valid file reference, or a known class
			var split = this.data.splitHref (href);
			if ((split.file.length && File (split.file).exists)
			 || this.classesItems [type])
			{
				// Create a two-element array with the class name set to \n
				// so we can split the message at the class name
				var s = localize (fmt, "\n", elem.@size);
				s = s.split ("\n");
				// 1st part of the message
				xml.appendChild (s [0]);
				xml.appendChild (this.makeLink (href, type));
				xml.appendChild (s [1]);
				// show that we dont need to append anything
				fmt = null;
			}
		}
	}
	if (fmt)
		xml.appendChild (localize (fmt, type, elem.@size));
	if (!onlyType)
	{
		elem = datatypeXML.minimum;
		if (elem.length())
			xml.appendChild (", " + localize ("Minimum: %1", elem.toString()));
		elem = datatypeXML.maximum;
		if (elem.length())
			xml.appendChild (", " + localize ("Maximum: %1", elem.toString()));
		elem = datatypeXML.value.toString();
		if (elem != "")
		{
			// If the type is Number, check for possible display as 4-byte const
			if (type == "number")
			{
				var n = Number (elem);
				if (n >= 0x20202020)
				{
					var chars = "";
					for (var i = 0; i < 4; i++, n <<= 8)
					{
						var ch = String.fromCharCode ((n >> 24) & 0xFF);
						if (ch >= " " && ch <= "~")
							chars += ch;
					}
					if (chars.length == 4)
						elem += " ('" + chars + "')";
				}
			}
			fmt = (datatypeXML.parent().@rwaccess == "readonly")
				? "Value: %1"
				: "Default Value: %1";
			xml.appendChild (", " + localize (fmt, elem));
		}
	}
	xml = xml.children();
	xml.normalize();
	return doXML ? xml : xml.toString();
}

// Create type info XML out of a <datatype> element list
// If the data type is a list, createXML like "XXX or YYY"
// datatypeXML - the <datatype> element - maybe a list

OMV.prototype.makeFullTypeInfo = function (datatypeXML)
{
	// root element is ignored
	var s = XML ('<type/>');
	for (var i = 0; i < datatypeXML.length(); i++)
	{
		if (i > 0)
			s.appendChild ("or");
		s.appendChild (this.makeTypeInfo (datatypeXML [i], true));
	}
	return s;
}

// Append the short and/or the full description to given HTML.

OMV.prototype.appendHelpText = function (html, xml, usePara)
{
	var text = xml.shortdesc;
	var p;
	if (text.length())
	{
		p = text.copy();
		p.setName ("p");
		this.expandLinks (p);
		html.appendChild (p);
	}
	text = xml.description;
	if (text.length())
	{
		p = text.copy();
		p.setName ("p");
		this.expandLinks (p);
		html.appendChild (p);
	}
}

// Append any examples to the given HTML.

OMV.prototype.appendExamples = function (html, xml)
{
	var ex = xml.example;
	if (ex.length() > 0)
		html.appendChild (XML (ex.length() == 1 ? "<p><b>Example:</b></p>" : "<p><b>Examples:</b></p>"));

	for (var i = 0; i < ex.length(); i++)
	{
		var p;
		var elem = ex [i];
		var href = elem.@href.toString();
		if (href)
		{
			var f = this.data.splitHref (href);
			f = new File (f.file);
			if (f.open())
			{
				p = XML('<p><font face="_typewriter"/></p>');
				var body = p.font;
				while (!f.eof)
				{
					var text = f.readln();
					// Change leading whitespace to the non-breaking variant
					var text2 = "";
					var nbsp = String.fromCharCode (160);
					for (var ch = 0; ch < text.length; ch++)
					{
						switch (text [ch])
						{
							case "\t":	text2 += nbsp + nbsp + nbsp + nbsp; break;
							case  ' ':	text2 += nbsp; break;
							default:	text2 += text.substr (ch); text = "";
						}
					}
					p.font.appendChild (text2);
					p.font.appendChild (XML ("<br/>"));
				}
				f.close();
				html.appendChild (p);
			}
		}
		else
		{
			html.appendChild (elem.children().copy());
			this.expandLinks (html);
		}
	}
}

// Extend all <a> elements in the given HTML to
// <a href="event:xxxx"><font color="#0000FF">link text</font></a>

OMV.prototype.expandLinks = function (html)
{
	if (html.localName() == "a")
	{
		var href = html.@href.toString();
		// Do not insert "event:" if the Href starts with http: or file:
		if (href.indexOf ("http:") < 0 && href.indexOf ("file:") < 0)
			href = "event:" + href;
		return new XML('<a href="' + href + '"><font color="#0000FF">' + html.toString() + '</font></a>');
	}
	else if (html.nodeKind() == "element")
	{
		var kids = html.children();
		var newKids = new XML('<></>');
		for (var i = 0; i < kids.length(); i++)
			newKids += this.expandLinks (kids [i]);
		html.setChildren (newKids);
	}
	return html;
}

// Create a HREF attribute for the given href.
// This href has the form [file]#/Classname[/Containertype/Elementname]
// to be used as argument to findText(). 
// The first argument is either XML, or the raw link text.
// The return value of the complete <a> tag.

OMV.prototype.makeLink = function (href, text)
{
	if (href=="")
		// no HREF? Then no link
		return XML (text);

	href = this.data.splitHref (href);
	if (!text)
	{
		text = href.cls;
		if (href.name)
			text += '.' + href.name;
	}
	return XML ('<a href="event:' + this.data.joinHref (href) + '"><font color="#0000FF">' + text + '</font></a>');
}

//////////////////////////////////////////////////////////////////
//
//	Searching
//
//////////////////////////////////////////////////////////////////

// Find a string. If the string contains a dot, split into class name and 
// element name. If words is true, the search must be accurate (whole words).

OMV.prototype.findText = function (what, wholeWord)
{
	var xml = this.data.findXML (what, wholeWord);
	this.ignoreChanges++;
	this.displayXML (xml);
	this.setFindField (xml);
	this.ignoreChanges--;
}

// Find a HREF.

OMV.prototype.findHref = function (href)
{
	this.displayXML (this.data.getXMLForHref (href));
}

// Helper: find a list element that has the given XML appended.

OMV.prototype._getListItemForXML = function (parent, xml)
{
	var items = parent.items;
	for (var i = 0; i < items.length; i++)
	{
		var item = items [i];
		if (item.xml == xml)
			return item;
	}
	return null;
}

// Helper: Set a multiselection if it has changed.
// Both arrays are assumed to be sorted by item index.

OMV.prototype._setSelection = function (parent, newSel)
{
	var oldSel = parent.selection;
	if (!oldSel)
	{
		if (newSel)
			parent.selection = newSel;
	}
	else if (!newSel)
		parent.selection = newSel;
	else if (oldSel.length != newSel.length)
		parent.selection = newSel;
	else for (var i = 0; i < oldSel.length; i++)
	{
		if (oldSel[i].index != newSel[i].index)
		{
			parent.selection = null;
			parent.selection = newSel;
			break;
		}
	}
}

// Select the given XML in the appropriate lists.
// If the XML is multiple elements, select all elements.
// The XML may be a list of classes or a list of elements in 
// a single container, but not both classes and elements, or
// multiple element containers.

OMV.prototype.selectXML = function (xml)
{
	if (!this.w.visible)
		return;

	this.ignoreChanges++;

	if (xml)
	{
		var sel = [];
		var array = this.sortedXML (xml, "name");
		var isClass = xml[0].localName() == "classdef";
		if (isClass)
		{
			for (var i = 0; i < array.length; i++)
			{
				var item = this._getListItemForXML (this.classes, xml);
				if (item)
					sel.push (item);
			}
		}
		else if (xml.xpath ("../..").length() == 1)
			sel.push (this._getListItemForXML (this.classes, xml[0].parent().parent()));

		this._setSelection (this.classes, sel);
		if (sel.length != 1)
		{
			this.types.xml = null;
			this.elements.xml = null;
			this.types.removeAll();
			this.elements.removeAll();
		}
		else
		{
			sel = null;
			// select types
			var containerxml, elementxml;

			if (isClass)
			{
				this.loadClass (xml, true);
				// select a container
				containerxml = xml.elements;
				if (containerxml.length() != 1)
				{
					// if there are multiple, start with instance elements if possible
					containerxml = xml.xpath ('elements[@type="instance"]');
					if (containerxml.length() == 0)
						containerxml = xml.elements [0];
				}
			}
			else
				// use xpath to catch multiple parents
				containerxml = xml.xpath ("..");
			if (containerxml.length() > 0)
				this.loadClass (containerxml.parent(), true);
			if (containerxml.length() == 1)
			{
				item = this._getListItemForXML (this.types, containerxml);
				if (item)
				{
					this.types.selection = item;
					this.loadClassElements (containerxml);
				}
				else
				{
					this.types.selection = null;
					this.elements.xml = null;
					this.elements.removeAll();
				}
			}
			var elements = [];
			if (!isClass && this.types.selection)
			{
				for (var i = 0; i < array.length; i++)
				{
					var item = this._getListItemForXML (this.elements, xml);
					if (item)
						elements.push (item);
				}
			}

			this._setSelection (this.elements, elements);
		}
	}
	else
	{
		this.display.html = null;
		this.setHTML();
	}
	this.ignoreChanges--;
}

// Set the contents of the Find text field to the given text or XML. 

OMV.prototype.setFindField = function (what)
{
	if (typeof what == "xml")
	{
		if (what.length() != 1)
			return;
		var name = what[0].@name.toString();
		what = (what[0].localName() == "classdef")
			 ? name
			 : what.parent().parent().@name + '.' + name;
	}
	if (what != null && what != this.find.text)
	{
		this.ignoreChanges++;
		this.find.text = what;
		this.ignoreChanges--;
	}
}

// Display the given XML and select the appropriate lists.
// If the XML is multiple elements, select all elements and
// create a list of clickable elements in the HTML display.
// The XML may be a list of classes or a list of elements in 
// a single container, but not both classes and elements, or
// multiple element containers.

OMV.prototype.displayXML = function (xml)
{
	if (!this.w.visible)
		return;
	this.selectXML (xml);
	if (xml)
	{
		this.pushHTMLStack (xml);
		this.setHTML (this.getHTML (xml));
	}
}

//////////////////////////////////////////////////////////////////
//
//	HTML Control
//
//////////////////////////////////////////////////////////////////

OMV.prototype.setupHTMLControl = function (ctrl)
{
	/// This is the HTML page stack.
	this.htmlStack = [];
	this.htmlPos   = -1;

	/*	This function is called from the htmlViewer control via ExternalInterface::call
		to get the page content for the given 'link' */
	ctrl.getContentForLink = function (theCookie, href)
	{
		var html;
		// The link is a string created by makeLink() below.
		// The format is [file]#/classname[/containertype/elementname].
		var split = this.omv.data.splitHref (href);
		if (split.file.length)
		{
			// An external file; load the OMV for that file and display the link
			var omvData = OMVData.getByFile (split.file);
			if (omvData)
			{
				var xml = omvData.getXMLForHref (href);
				if (xml)
				{
					omvData.show();
					omvData.ui.selectXML (xml);
				}
			}
			// we do not want to change the HTML, so return the current HTML
			html = this.omv.prepareHTML (this.omv.display.html) + "<p></p>";
		}
		else
		{
			var xml = this.omv.data.getXMLForHref (href);
			this.omv.selectXML (xml);
			if (xml)
			{
				// add the XML to the HTML stack
				this.omv.pushHTMLStack (xml);
				html = this.omv.getHTML (xml);
			}
			else
				html = XML( '<html><body><b>Broken link:&#160;<font color = "#FF0000">' + href + '</font></b></body></html>');
			html = this.omv.prepareHTML (html);
		}
		return html;
	}

	// invoke SWF's copy-to-clipboard func
	this.copy.onClick = function()	
	{
		this.omv.display.invokePlayerFunction("copyToClipboard");
	}

	this.fwd.onClick = function()
	{
		if (this.omv.htmlPos < this.omv.htmlStack.length-1)
		{
			var xml = this.omv.htmlStack [this.omv.htmlPos+1];
			this.omv.htmlPos++;
			this.enabled = (this.omv.htmlPos < this.omv.htmlStack.length-1);
			this.omv.back.enabled = (this.omv.htmlPos > 0);
			this.omv.selectXML (xml);
			this.omv.setHTML (this.omv.getHTML (xml));
		}
	}

	this.back.onClick = function()
	{
		if (this.omv.htmlPos > 0)
		{
			var xml = this.omv.htmlStack [this.omv.htmlPos -1];
			this.omv.htmlPos--;
			this.enabled = (this.omv.htmlPos > 0);
			this.omv.fwd.enabled = (this.omv.htmlPos < this.omv.htmlStack.length-1);
			this.omv.selectXML (xml);
			this.omv.setHTML (this.omv.getHTML (xml));
		}
	}
}

// Start the HTML "movie"

OMV.prototype.startHTML = function()
{
	var movieToPlay = new File (app.requiredFolder + "/more/ESTK_HTML.swf");
	if (!movieToPlay.exists)
		this.badHTML();
	else 
	{
		try 
		{
			this.display.loadMovie (movieToPlay);
		}
		catch (e) 
		{
			this.badHTML();
		}
	}
}

// Prepare the given HTML (which is complete) for the HTML widget.
// The returned value is the HTML as string.

OMV.prototype.prepareHTML = function (html)
{
	if (!html)
		html = this.display.html;
	if (!html)
		return this.display.html = XML("<html><body/></html>");

	html = html.removeNamespace ("http://www.w3.org/2001/XMLSchema-instance");
	html = html.body.children();
	this.display.html = html;
	var s = html.toXMLString();
	s = s.replace (/\n/mg, "");
	XML.prettyPrinting = false;
	var newhtml = XML ('<font size="' + OMV.htmlFontSize + '"/>');
	newhtml.appendChild (html);
	html = newhtml.toXMLString();
	XML.prettyPrinting = true;
	return html;
}

// Set the HTML at the HTML widget.

OMV.prototype.setHTML = function (html)
{
	this.prepareHTML (html);
	OMV.update.push (this);
	addDelayedTask (updateOMV);
}

function updateOMV()
{
	for (var i = 0; i < OMV.update.length; i++)
	{
		var omv = OMV.update [i];
		omv.display.invokePlayerFunction("htmlViewerInitialize", "cookie", omv.display.html,
		{ 
			flexStyles: {
				fontGridFitType: "subpixel",
				fontThickness:	50,
				fontSharpness:	50
			}, 
			flexProperties: { 
				condenseWhite: true
			}
		});
	}
	OMV.update = [];
}

// Push an entry on the HTML stack. The entry is the XML to display.

OMV.prototype.pushHTMLStack = function (xml)
{
	if (!xml)
		return;

	// do not push the same XML twice
	if (this.htmlStack.length > 0 && this.htmlStack [this.htmlPos] == xml)
		return;
	this.htmlStack.splice (this.htmlPos+1);
	this.htmlStack.push (xml);
	this.htmlPos++;
	this.back.enabled = (this.htmlPos > 0);
	this.fwd.enabled = false;
}
