// ----------------------------------------------------------------------
//
// layout.js
//
// ----------------------------------------------------------------------

// Informal type: size-spec
//   size-spec ::= (number) + 'px'
//     | (number < 100) + '%'
//     | '*'
//     | 'children'

// Methods supported by the Layout interface:
//   RenderHTML() : string
//   SetVisible(boolean) : void
//   IsVisible() : boolean
//   DetermineSize() : size-spec
//   SetSuggestedSize(size-spec) : void
//   GetSuggestedSize() : size-spec
//   GetName() : string
//   SetName(string) : void
//   FindChild(name) : LayoutElement or null

var LAYOUT_REFRESH_CALLS_UPDATE = true;
var LAYOUT_USE_SRC_SPEEDUP = false;

function SizeToHTML(sizestring) {
	var size = new String(sizestring);
	if ( size.substr(size.length-1, size.length-1) == "%" ) {
		return size;
	} else if ( size == '*' ) {
		return size;
	} else if ( size == 'children' ) {
		beos.dprintf("SizeToHTML: warning: 'children' invalid here (use GetSize() to convert to a number)");
		return '*';
	} else { // hope it's a number
		return size;
	}
}


// Visibility coupling
// The following two functions use the Observer support in
// modes.js to create automatic hiding/showing of layout objects
// in response to mode and feature changes.
function CoupleLayoutObjectToFeature(o, f, state) {
	beos.globals.features.AddFeatureObserver(f, o);
	o.m_coupled_features[f] = state;
	o.m_use_coupling = true;
}

function CoupleLayoutObjectToMode(o, c, m) {
	beos.globals.modes.AddClusterObserver(c, o);
	o.m_coupled_modes[c] = m;
	o.m_use_coupling = true;
}

// ======================================================================
// ** LayoutBag class **

// static constants
var LayoutBag_SPLIT_VERTICAL = 1;
var LayoutBag_SPLIT_HORIZONTAL = 2;

// LayoutBag.LayoutBag() - Constructor
function LayoutBag(initProperties)
{
	var init = (initProperties != null)?initProperties:(new Object());
	this.m_orientation = (null != init.orientation)?init.orientation:LayoutBag_SPLIT_HORIZONTAL;
	this.m_children = new Array(0);
	// this.m_location = init.location?init.location:"";
	this.m_name = (null != init.name)?init.name:"";
	this.m_visible = (null != init.visible)?init.visible:true;
	this.m_size = (null != init.size)?init.size:null;

	this.m_zeroPixelHiding = (null != init.zeroPixelHiding)
		?init.zeroPixelHiding:false;

	this.m_coupled_modes = new Object();
	this.m_coupled_features = new Object();
	this.m_use_coupling = false;

	this.RefreshHook = null;
}

function LayoutBag__toString() {
	return '[object LayoutBag: "'+this.m_name+'"]';
}; LayoutBag.prototype.toString = LayoutBag__toString;

// LayoutBag.RenderHTML(document)
// Recursive HTML rendering function.  
function LayoutBag__RenderHTML()
{
	// this is taken care of us by our parent in the recursion
	// if (! this.IsVisible() && !this.m_zeroPixelHiding) 
	//	return '';
		
	// Text: <frameset {rows,cols}="
	var output = '<frameset ' + 
		((this.m_orientation == LayoutBag_SPLIT_HORIZONTAL)
			?"rows"
			:"cols")
		+ '="';

	// sizes (comma-separated list of size-spec)
	var index; var sizeText;
	var first = true;
	var frameOutput = "";
	for (index = 0; index < this.m_children.length; index++) {
		var subelement = this.m_children[index];
		//Rely upon the subelement's routines to return text or not.
		//if (subelement.IsVisible()) {
		sizeText = SizeToHTML(subelement.GetSize());
		if ( subelement.IsVisible() || subelement.m_zeroPixelHiding ) {
			if ( first )
				first = false;
			else
				output += ', ';
		
			output += sizeText;
			frameOutput += subelement.RenderHTML();
		}
	}

	output += '" name="' + this.m_name + '" border=0 frameborder=0 framespacing=0>\n'
		+ frameOutput + '</frameset>\n';
	
	return output;
}; LayoutBag.prototype.RenderHTML = LayoutBag__RenderHTML;


function LayoutBag__AddChild(element) {
	this.m_children[this.m_children.length] = element;
	return element;
}; LayoutBag.prototype.AddChild = LayoutBag__AddChild;


function LayoutBag__FindChild(name)
{
	var index;
	var element;
	
	// Check elements at this level first
	for (index = 0; index < this.m_children.length; index++)
		if (this.m_children[index].m_name == name)
			return this.m_children[index];

	// Check children
	for (index = 0; index < this.m_children.length; index++) {
		if ( this.m_children[index].FindChild != null ) {
			element = this.m_children[index].FindChild(name);
			if (element != null)
				return element;
		}
	}

	return null;
}; LayoutBag.prototype.FindChild = LayoutBag__FindChild;


function LayoutBag__RemoveChild(name)
{
	var index;
	var element;
	
	// Check elements at this level first
	for (index = 0; index < this.m_children.length; index++) {
		if (this.m_children[index].m_name == name) {
			delete this.m_children[index];
			var move;
			for (move = index; move < this.m_children.length - 1; move++)
				this.m_children[move] = this.m_children[move + 1];
				
			this.m_children.length--;
			return true;			
		}
	}

	// Check children
	for (index = 0; index < this.m_children.length; index++)
		if (this.m_children[index].RemoveElement(name))
			return true;

	return false;
}; LayoutBag.prototype.RemoveChild = LayoutBag__RemoveChild;


function LayoutBag__SetSize(size)
{
	this.m_size = size;
}; LayoutBag.prototype.SetSize = LayoutBag__SetSize;


/* 
function LayoutBag__RenderSizeHTML() {
	if ( this.m_zeroPixelHiding && ! this.IsVisible() ) {
		return '';
	} else {
		return this.GetSize();
	}
}; LayoutBag.prototype.RenderSizeHTML = LayoutBag__RenderSizeHTML;
*/

function LayoutBag__GetSize() {
	// beos.dprintf('LAYOUTBAG__GETSIZE');
	var returnedSize = 0;
	if ( ! this.IsVisible() ) {
		returnedSize = 0;
	} else if ( this.m_size == 'children' ) { // alpha code
		for (var index = 0; index < this.m_children.length; index++) {
			if (this.m_children[index].IsVisible()) {
				var childSize = this.m_children[index].GetSize();
				// beos.dprintf("CHILDSIZE: " + childSize.valueOf());
				if ( childSize.valueOf() > 0 ) {
					// convert to int
					returnedSize = (-1)*(0 - returnedSize.valueOf() - childSize.valueOf()); 
				} else { // bail out if we have non-absolute children
					return '*';
				}
			}
		}
	} else {
		returnedSize = this.m_size;
	}
	return returnedSize;
}; LayoutBag.prototype.GetSize = LayoutBag__GetSize;


function LayoutBag__SetVisible(vis)
{
	this.m_visible = vis;
}; LayoutBag.prototype.SetVisible = LayoutBag__SetVisible;


function LayoutBag__IsVisible()
{
	// old functionality: you could show a pane just by showing any
	// of its children.  This was a Bad Thing and has been removed.
	return this.m_visible;
}; LayoutBag.prototype.IsVisible = LayoutBag__IsVisible;


function LayoutBag__DeleteAllChildren()
{
	this.m_children = new Array(0);
}; LayoutBag.prototype.DeleteAllChildren = LayoutBag__DeleteAllChildren;


function LayoutBag__Refresh()
{
	if (LAYOUT_REFRESH_CALLS_UPDATE && (this.Update != null))
		this.Update();

	var continueRefresh = true;
	if (this.RefreshHook != null) {
		this.RefreshHook(this);
	}
	
	if (continueRefresh) {
		var index;
		for (index = 0; index < this.m_children.length; index++) {
			var subelement = this.m_children[index];
			subelement.Refresh();
		}
	}
	return true;
}; LayoutBag.prototype.Refresh = LayoutBag__Refresh;


// ======================================================================
// ** LayoutFrame class **

// LayoutFrame.LayoutFrame() - Constructor
function LayoutFrame(initProperties)
{
	var init = (null != initProperties)?initProperties:(new Object());
	this.m_location = (null != init.location)?init.location:"";
	this.m_name = (null != init.name)?init.name:"";
	this.m_visible = (null != init.visible)?init.visible:true;
	this.m_size = (null != init.size)?init.size:null;
	this.m_scrolling = (null != init.scrolling)?init.scrolling:true;
	// this is for one-time SRC= initialization of the frame
	this.m_addSrc = true;
	this.RefreshHook = null;
	
	this.m_zeroPixelHiding = (null != init.zeroPixelHiding)
		?init.zeroPixelHiding:false;

	this.m_coupled_modes = new Object();
	this.m_coupled_features = new Object();

}

function LayoutFrame__toString() {
	return '[object LayoutFrame: "'+this.m_name+'"]';
}; LayoutFrame.prototype.toString = LayoutFrame__toString;

function LayoutFrame__RenderHTML() {
	// Write a single frame

	var output = '<frame';
	
	if (this.m_location.length > 0 && (!LAYOUT_USE_SRC_SPEEDUP || this.m_addSrc)) {
		output += ' src="' + this.m_location + '"';
		this.m_addSrc = false;	// Frames are persistent (that's cool!)
	}
	
	output += ' name="' + this.m_name 
		+ '" noresize scrolling=' + (this.m_scrolling?"yes":"no")
		+ ' marginwidth=0 marginheight=0 frameborder=0>\n';

	return output;
}; LayoutFrame.prototype.RenderHTML = LayoutFrame__RenderHTML;

function LayoutFrame__SetSize(size)
{
	this.m_size = size;
}; LayoutFrame.prototype.SetSize = LayoutFrame__SetSize;


function LayoutFrame__GetSize() {
	if ( ! this.IsVisible() && this.m_zeroPixelHiding ) 
		return 0;
	else 
		return this.m_size;
}; LayoutFrame.prototype.GetSize = LayoutFrame__GetSize;


/* 
function LayoutFrame__RenderSizeHTML() {
	if (this.m_zeroPixelHiding && ! this.m_visible) {
		return '';
	} else {
		return this.m_size;
	}
}; LayoutFrame.prototype.RenderSizeHTML = LayoutFrame__RenderSizeHTML;
*/

function LayoutFrame__SetVisible(vis)
{
	// If we make a frame visible that wasn't visible before, we'll
	// need to add the SRC attribute again.
	if ( ! this.m_visible && vis )
		this.m_addSrc = true;
	this.m_visible = vis;
}; LayoutFrame.prototype.SetVisible = LayoutFrame__SetVisible;


function LayoutFrame__IsVisible()
{
	return this.m_visible;
}; LayoutFrame.prototype.IsVisible = LayoutFrame__IsVisible;


function LayoutFrame__SetLocation(loc)
{
	this.m_location=loc;
}; LayoutFrame.prototype.SetLocation = LayoutFrame__SetLocation;


function LayoutFrame__GetLocation()
{
	return this.m_location;
}; LayoutFrame.prototype.GetLocation = LayoutFrame__GetLocation;


function LayoutFrame__Refresh()
{
	if (LAYOUT_REFRESH_CALLS_UPDATE && (this.Update != null))
		this.Update();

	if (this.RefreshHook != null) {
		return this.RefreshHook(this);
	} else {
		return true;
	}
}; LayoutFrame.prototype.Refresh = LayoutFrame__Refresh;

// ======================================================================
// LayoutObject "class"
// methods included in the prototypes of LayoutBag and LayoutFrame
// ======================================================================

// LayoutObject.Update()
// client of Observer pattern for modes and features
// allows easy access to layout hiding based on active modes, etc.
function LayoutObject__Update() {
	if (!this.m_use_coupling)
		return;
	var opt;
	var vis = true;
	if (this.m_coupled_modes != null)
		for(var cluster in this.m_coupled_modes) {
			beos.dprintf('LayoutObject.Update(): cluster='+cluster);
			vis = (vis && 
				(beos.globals.modes.Get(cluster) == 
					this.m_coupled_modes[cluster]));
		}
	if (this.m_coupled_features != null)
		for(var feature in this.m_coupled_features) {
			var desiredState = this.m_coupled_features[feature];
			var realState = beos.globals.features.Get(feature);
			vis = vis && ( desiredState == realState );
		}
	this.SetVisible(vis);
}
LayoutBag.prototype.Update = LayoutObject__Update;
LayoutFrame.prototype.Update = LayoutObject__Update;


// a positional-parameter shortcut, until {name:var} constructors exist
function MakeLayoutFrame(_name, _location, _scrolling, _size, _visible) {
	var config = new Object();
	config.name = _name;
	config.location = _location;
	config.scrolling = _scrolling;
	config.size = _size;
	config.visible = _visible;
	return new LayoutFrame(config);
}
