// Copyright (c) 2008 Avi Blackmore and Agri ImaGIS Technologies, Inc.  All rights reserved.

// This file constitutes the Divwin Javascript GUI library.

// Divwin is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Divwin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public License
// along with Divwin.  If not, see <http://www.gnu.org/licenses/>.

// This library depends upon the wz_dragdrop.js library by Walter Zorn
// <http://www.walterzorn.com/dragdrop/dragdrop_e.htm>; see the file
// wz_dragdrop.js for its license information.



//Class dwelement is a mixin base class, not instantiable.  All DW objects are descended from dwelement.
function dwelement() {

     var obj = this;

     obj.__node.dwobject = this;

     //All DW objects have a "type" property
     obj.type = 'dwelement';

     //Sets DOM node ID for the element.
     obj.setID = function (id) {

	  obj.__node.id = id;
     }

     //Returns DOM node ID for the element.
     obj.getID = function () {

	  return obj.__node.id;
     }

     //Sets CSS class for the element.
     obj.setCSSClass = function (classname) {

	  obj.__node.className = classname;
     }

     //Sets CSS style properties for the element; must be in proper Javascript format.
     obj.setStyle = function (property, value) {

	  obj.__node.style[property] = value;
     }

     //Attach element to the document tree as a child of parentnode.  Parentnode can be any DOM object,
     //not just one wrapped by a DW object.
     obj.attach = function (parentnode) {

	  parentnode.appendChild(obj.__node);
     }

     //Detach element from the document tree.  Object can then be deleted.
     obj.detach = function () {

	  obj.__node.parentNode.removeChild(obj.__node);
     }

     //Make dwelement visible
     obj.show = function () {

	  obj.__node.style.visibility = 'visible';
     }

     //Make dwelement invisible
     obj.hide = function () {

	  obj.__node.style.visibility = 'hidden';
     }

     //Get current visibility
     obj.visible = function () {

	  if (obj.__node.style.visibility = 'visible') {

	       return true;

	  } else {

	       return false;
	  }
     }

     //Change the function called when element is clicked.
     obj.setClick = function (clickevent) {

	  obj.__node.onclick = clickevent;
     }
}

//Class dwcontainer is a mixin which defines DW objects which can contain other objects.
function dwcontainer() {

     //Derive from the base object
     dwelement.call(this);
	 
     var obj = this;

     obj.type = 'dwcontainer';
	 
     //Contains IDs for inner content, so that it need not pollute the global namespace.
     obj.contents = new Array();

     //Return number of objects in container
     obj.count = function() {

	  return obj.contents.length;
     }

     //Add DW objects to the container's content.  Objects are addressable as properties of the containing object.
     obj.add = function (id, newnode) {
	   
	  if (!obj[id]) {

	       obj[id] = newnode;

	       obj.contents.push(id);

	       if(newnode.type) {

		    newnode.attach(obj.__node);
	       }
	  }
     }

     //Add raw HTML to the container's content.  Not necessarily a good idea, but necessary for now.
     obj.addHTMLNode = function (nodetype) {

	  obj.__node.appendChild(document.createElement(nodetype));
     }

     //Removes the named DW object from the container.
     obj.remove = function (id) {

	  if (obj[id]) {
			   
	       obj[id].detach()

	       for (var i = 0; i < obj.contents.length; i++) {

		    if (obj.contents[i] == id) {

			 obj.contents.splice(i, 1);
			 break;
		    }
	       }
	       
	       delete obj[id];
	  }
     }

     //Removes all DW objects from the container.
     obj.clear = function() {

	  while(id = obj.contents.pop()) {

	       obj.remove(id);
	  }
     }

     //Removes all DW objects and clears any other content.
     obj.clearHTML = function () {

	  obj.clear();

	  obj.__node.innerHTML = '';
     }
}

//Class dwpane defines "window panes", which are the basic building
//blocks of divwindows.  A dwpane can contain any dwelement, including
//other dwpanes.
function dwpane(width, height) {

     var obj = this;

     //Create DOM node
     obj.__node = document.createElement("div");

     //Derive from dwcontainer
     dwcontainer.call(this);
	 
     obj.type = 'dwpane';

     //Basic setup
     obj.setStyle("width", width);
     obj.setStyle("height", height);
     obj.setStyle("backgroundColor", '#cccccc');
     obj.setStyle("fontSize", '10pt');
     obj.setStyle('top', 0);
     obj.setStyle('left', 0);

     //Change position of the pane within its containing element.
     obj.setPosition = function (x, y) {
	   
	  obj.setStyle("top", y);
	  obj.setStyle("left", x);

     }

     //Change size of the pane.
     obj.setSize = function (width, height) {

	  obj.setStyle("width", width);
	  obj.setStyle("height", height);
     }
} 

//Class dwlist is a mixin for list types.  Lists are containers, but
//all contained items are wrapped in <li> elements.
function dwlist() {

     var obj = this;

     //Derive from dwcontainer
     dwcontainer.call(this);

     obj.type = 'dwlist';

     //Adds the named DW object to the list.
     obj.add = function (id, newnode) {

	  if (!obj[id]) {

	       obj[id] = newnode;

	       obj.contents.push(id);

	       var listnode = document.createElement('li');
	       
	       obj.__node.appendChild(listnode);
	       
	       newnode.attach(listnode);
	  }
     }

     //Removes the named DW object from the list.
     obj.remove = function (id) {

	  if (obj[id]) {
	
	       obj.__node.removeChild(obj[id].__node.parentNode);

	       obj[id].detach()
	   
	       for (var i = 0; i < obj.contents.length; i++) {

		    if (obj.contents[i] == id) {

			 obj.contents.splice(i, 1);
			 break;
		    }
	       }
	       
	       delete obj[id];
	  }
     }

     //Removes all DW objects from the list.
     obj.clear = function() {

	  while(id = obj.contents.pop()) {

	       obj.remove(id);
	  }
     }
}

//Class dwnumlist implements ordered lists.
function dwnumlist() {

     var obj = this;
     
     obj.__node = document.createElement("ol");

     //Derive from dwlist
     dwlist.call(this);

     obj.type = 'dwnumlist';
}
     
//Class dwbulletlist implements unordered, "bulleted" lists.
function dwbulletlist() {

     var obj = this;
     
     obj.__node = document.createElement("ul");

     //Derive from dwlist
     dwlist.call(this);

     obj.type = 'dwbulletlist';
}


//Class dwinputgroup defines a group of inputs, which can be controlled and queried as a group.  
//It is restricted to contain only input objects.
function dwinputgroup() {

     var obj = this;

     obj.__node = document.createElement("span");

     //Derive from dwcontainer
     dwcontainer.call(this);

     obj.type = 'dwinputgroup';

     var parentadd = obj.add;

     obj.add = function (id, newnode) {

	  if (newnode.getValue != null) {

	       parentadd(id, newnode);

	  } else {

	       throw("TypeError: '" + id + "' is not an input object.");
	  }
     }

     obj.getValues = function () {

	  var vals = new Array();

	  for(var i = 0; i < obj.contents.length; i++) {

	       var theid = obj.contents[i];

	       if (obj[theid].type == 'dwcheckbox' || obj[theid].type == 'dwradiobutton') {

		    if (obj[theid].checked()) {

			 vals.push(obj[theid].getValue());
		    }

	       } else {

		    if (obj[theid].getValue() != null && obj[theid].getValue() != '') {

			 vals.push(obj[theid].getValue());
		    }
	       }
	  }	

	  return vals;
     }
}

//Class dwheader is a type of dwpane designed specifically as a window header.
function dwheader(headertext) {
  
     //Derive from dwpane
     dwpane.call(this, "100%", 20);

     var obj = this;

     obj.type = 'dwheader';

     obj.setStyle('textAlign', 'center');
     obj.setStyle('fontWeight', 'bold');
     obj.setStyle('color', 'white');
     obj.setStyle('backgroundColor', 'darkblue');

     thetext = new dwtextspan(headertext);

     obj.add('headertext', thetext);

}

//Class dwtext is a mixin for text-holding objects.
function dwtext(text) {

     //Derive from the base object
     dwelement.call(this);

     var obj = this;

     obj.type = 'dwtext';

     obj.setText = function (newtext) {

	  obj.__thetext.data = newtext;
     }

     obj.addText = function (newtext) {

	  obj.__thetext.data += " " + newtext;
     }

     obj.clearText = function () {

	  obj.__thetext.data = " ";
     }
}

//Class dwtextspan puts text in a SPAN element.
function dwtextspan(text) {

     var obj = this;
 
     obj.__node = document.createElement("span");

     //Derive from the base object
     dwtext.call(this);

     obj.type = 'dwtextspan';

     obj.__thetext = document.createTextNode(text);

     obj.__node.appendChild(obj.__thetext);
}

//Class dwparagraph puts text in a P element.
function dwparagraph(text) {

     var obj = this;

     obj.__node = document.createElement("p");

     //Derive from the base object
     dwtext.call(this);

     obj.type = 'dwparagraph';

     obj.__thetext = document.createTextNode(text);

     obj.__node.appendChild(obj.__thetext);
}

//Class dwlink implements an anchor.
function dwlink(label, href) {

     var obj = this;

     obj.__node = document.createElement("a");

     dwelement.call(this);

     obj.__node.innerHTML = label;
     obj.__node.href = href;

     //Gets the "link text" of the link.
     obj.getLabel = function () {

	  return obj.__node.innerHTML;
     }

     //Sets the link text.
     obj.setLabel = function (label) {

	  obj.__node.innerHTML = label;
     }

     //Gets the href this link points to.
     obj.getLink = function () {

	  return obj.__node.href;
     }

     //Sets the href.
     obj.setLink = function (href) {

	  obj.__node.href = href;
     }

     //Gets the target window for this link.
     obj.getTarget = function () {

	  return obj.__node.target;
     }

     //Sets the target window.
     obj.setTarget = function (target) {

	  obj.__node.target = target;
     }
}

//Class dwimage implements an image element.  It is not an input,
//although it can act as a button by calling setClick.
function dwimage(imgurl, alttext) {

     var obj = this;

     obj.__node = document.createElement("img");

     dwelement.call(this);

     obj.__node.src = imgurl;
     obj.__node.alt = alttext;

     //Get image src.
     obj.getImage = function () {

	  return obj.__node.src;
     }

     //Set image src.
     obj.setImage = function(imgurl) {

	  obj.__node.src = imgurl;
     }
}

//Class dwbutton implements a button object.
function dwbutton(text, clickevent) {

     var obj = this;

     obj.__node = document.createElement("input");

     //Derive from the base object
     dwelement.call(this);

     obj.__node.type = "button";
     obj.__node.value = text;
     obj.__node.onclick = clickevent;

     obj.type = 'dwbutton';

     //Change the button's label.
     obj.setLabel = function (text) {

	  obj.__node.value = text;
     }

     //Checks or sets whether the button is disabled.
     obj.disabled = function (flag) {

	  if (flag != null) {

	       obj.__node.disabled = flag;

	  } else {

	       return obj.__node.disabled;
	  }
     }
}

//Class dwimagebutton implements an image input.  This control is best
//used to represent simple clickable images; for complex controls
//which change appearance and value based on an interntal state when
//clicked, see instead dwmultistateimagebutton.
function dwimagebutton(name, value, imgurl, clickevent) {

     var obj = this;

     obj.__node = document.createElement("input");

     //Derive from the base object
     dwelement.call(this);

     obj.__node.type = 'image';
     obj.__node.name = name;
     obj.__node.src = imgurl;
     obj.__node.onclick = clickevent;

     obj.type = 'dwimagebutton';

     obj.value = value;

     //Get the button's value.  This is needed to use the imagebutton in an inputgroup.
     obj.getValue = function () {

	  return obj.value;
     }

     //Get the button's image.
     obj.getImage = function () {

	  return obj.__node.src;
     }
     
     //Change the button's image.
     obj.setImage = function (imgurl) {

	  obj.__node.src = imgurl;
     }

     //Checks or sets whether the button is disabled.
     obj.disabled = function (flag) {

	  if (flag != null) {

	       obj.__node.disabled = flag;

	  } else {

	       return obj.__node.disabled;
	  }
     }
}

//Class dwmultistateimagebutton implements an image input button which
//switches between multiple states.  NAME defines a name for the input,
//STATEDICT is an object which defines the states this input can take,
//and GLOBALCLICKFN is an optional function called on every click.
//GLOBALCLICKFN can take one argument, the click event.
//
// STATEDICT has the following format: {statename: {nextstate: 'statename', 
// 	                                         imgurl: 'imgurl', 
// 	                                         clickfn: function () { ... }, 
// 	                             ...}.
//
//STATENAME gives the name of each state; the first state defined in
//STATEDICT will be the starting, default state.  When the control is
//clicked while in STATENAME state, it switches to NEXTSTATE.  IMGURL
//sets the image representing STATENAME state.  CLICKFN is an optional
//function which is called when the control enters STATENAME state.
//If CLICKFN and GLOBALCLICKFN are both defined, CLICKFN will be
//called first.  CLICKFN can take one argument, the click event.
//
//It is an error if NEXTSTATE is not defined, or if NEXTSTATE
//specifies a state which is not defined.  However, if NEXTSTATE is the
//same as STATENAME, this will result in no state change, but will
//still call CLICKFN and GLOBALCLICKFN if defined (this may be useful
//if CLICKFN or GLOBALCLICKFN has side effects).  It is also an error
//if IMGURL is not defined for each state.
function dwmultistateimagebutton(name, statedict, globalclickfn) {

     var obj = this;

     obj.__node = document.createElement("input");

     //Inherit from the base object.
     dwelement.call(this);

     obj.__node.type = "image";
     obj.__node.name = name;

     obj.type = 'dwmultistateimagebutton';
     
     //Setup code to set the initial state.
     var states = [];

     for(var val in statedict) {

	  states.push(val);
     }

     obj.__node.src = statedict[states[0]].imgurl;

     //While this is externally accessible, it should not be changed
     //by assignment.  Call switchState() method instead.
     obj.currentstate = states[0];

     //Keep the state dictionary around for easy access.  Use caution
     //when changing the state dictionary after initalizing.  In
     //particular, if the new statedict doesn't contain the current
     //state, call switchState to set to the new default state before
     //returning control to the user.
     obj.statedict = statedict;

     //Main state change function.  This should not be changed!  The
     //proper way to customize behavior for state changes is to define
     //CLICKFN for a state, or GLOBALCLICKFN for all states.
     obj.__node.onclick = function (event) {

	  obj.currentstate = obj.statedict[obj.currentstate].nextstate;

	  obj.__node.src = obj.statedict[obj.currentstate].imgurl;

	  if (obj.statedict[obj.currentstate].clickfn) {

	       obj.statedict[obj.currentstate].clickfn(event);
	  }

	  if(globalclickfn) {

	       globalclickfn(event);
	  }
     }

     //Implements getValue for use in an inputgroup.
     obj.getValue = function () {

	  return obj.currentstate;
     }

     //Sets current state to NEWSTATE.  Also calls any clickfn or
     //globalclickfn defined.  Ideally this should be used sparingly.
     obj.switchState = function (newstate) {

	  if (obj.statedict[newstate]) {

	       obj.currentstate = newstate;

	       obj.__node.src = obj.statedict[obj.currentstate].imgurl;

	       if (obj.statedict[obj.currentstate].clickfn) {

		    obj.statedict[obj.currentstate].clickfn();
	       }

	       if(globalclickfn) {

		    globalclickfn();
	       }

	  } else {

	       throw("InvalidState: State '" + newstate + "' is not defined in state dictionary.");
	  }
     }
}

//Class dwinput is a mixin for all input controls which can contain meaningful values.
function dwinput(label, type, value) {

     var obj = this;

     obj.__node = document.createElement("span");

     //Derive from the base object
     dwelement.call(this);

     obj.type = 'dwinput';

     obj.__label = document.createTextNode(label);

     obj.__node.appendChild(obj.__label);

     obj.__control = document.createElement("input");	
     obj.__control.type = type;  //set type here because of IE stupidity about changing input types.
     obj.__control.value = value;
     obj.__control.style.marginLeft = '2px';
     obj.__control.style.marginRight = '2px';

     obj.__node.appendChild(obj.__control);
	 
     //Checks or sets whether this input is readonly.
     obj.readonly = function (flag) {

	  if (flag != null) {

	       obj.__control.readonly = flag;

	  } else {

	       return obj.__control.readonly;
	  }
     }

     //Checks or sets whether this input is disabled.
     obj.disabled = function (flag) {

	  if (flag != null) {

	       obj.__control.disabled = flag;

	  } else {

	       return obj.__control.disabled;
	  }
     }

     //Get value of the input.
     obj.getValue = function () {

	  return obj.__control.value;
     }

     //Change label to new text.
     obj.setLabel = function (text) {
		  
	  obj.__label.nodeValue = text;
		 
     }

     //Sets label position to left or right side of the control element.
     obj.setLabelPosition = function (side) {

	  obj.__node.removeChild(obj.__control);
	  obj.__node.removeChild(obj.__label);

	  if (side == 'left') {

	       obj.__node.appendChild(obj.__label);
	       obj.__node.appendChild(obj.__control);

	  } else if (side == 'right') {

	       obj.__node.appendChild(obj.__control);
	       obj.__node.appendChild(obj.__label);
		   
	  } else {

	       throw("InvalidPosition: Left or right only.");
	  }
     }

     //Change value of the control element.
     obj.setValue = function (newvalue) {

	  obj.__control.value = newvalue;
     }

     //Set text field width
     obj.setWidth = function (width) {

	  obj.__control.style.width = width;
     }

     //Set text field height
     obj.setHeight = function (height) {

	  obj.__control.style.height = height;
     }
}

//Class dwtextfield implements a text entry field.  Text entry fields
//can optionally have browsable entry history; to enable, simply call
//"saveHistory" from another control to save that entry.  Up arrow
//scrolls backward in the history, down arrow scrolls forward.
function dwtextfield (label, value) {

     //Derive from dwinput
     dwinput.call(this, label, 'text', value);

     var obj = this;

     obj.type = 'dwtextfield';

     //Browseable history vars
     var history = [];
     var histmax = 10;
     var histidx = history.length;

     //Called when the enter key is pressed in the text field;
     //defaults to no action, but can be any function that takes no
     //arguments.
     obj.enterevent = function () { }

     //Browseable history support
     obj.__control.onkeydown = function (e) {

	  var evt = e || window.event;

	  if (evt.keyCode == 38) {  //Up arrow

	       //Don't go before the beginning of the history array
	       if (histidx > 0) {

		    histidx--;
		    
		    obj.__control.value = history[histidx];
	       }
	
	  } else if (evt.keyCode == 40) { //Down arrow

	       //Don't go past the end of the history array
	       if (histidx < history.length) {

		    histidx++;

		    if (histidx == history.length) {

			 obj.__control.value = '';

		    } else {

			 obj.__control.value = history[histidx];
		    }
	       }

	  } else if (evt.keyCode == 13) { //Enter key

	       obj.enterevent();
	  }
     }

     //Max length of history; history array will only save up to this number of entries.
     obj.setHistmax = function (max) {

	  histmax = max;
     }

     //Saves current value in the history; if this entry puts the history length above histmax, removes the oldest entry.
     obj.saveHistory = function () {

	  history.push(obj.__control.value);

	  if (history.length > histmax) {
	       
	       history.shift();
	  }
	  
	  histidx = history.length;
     }

     //Clear history.
     obj.clearHistory = function () {

	  history = [];
	  histidx = history.length;
     }
}

//Class dwpassfield implements a password input field.  Unlike dwtextfield, this does not implement history.
function dwpassfield(label) {

     //Derive from dwinput
     dwinput.call(this, label, 'password', '');

     var obj = this;

     obj.type = 'dwpassfield';

}

//Class dwcheckable is a mixin for "checkable" input controls, like
//checkboxes and radio buttons.  Both can have click events.
function dwcheckable(label, value, type) {

     //Derive from dwinput
     dwinput.call(this, label, type, value);

     var obj = this;

     obj.type = 'dwcheckable';

     //True if control is checked.
     obj.checked = function (set) { 

	  if (set === true) {

	       obj.__control.checked = true;

	  } else if (set === false) {

	       obj.__control.checked = false;
	  }
     

	  return obj.__control.checked; 
     }
	 
     //Set a click event for this control.  Overrides base class version.
     obj.setClick = function (clickevent) {

	  obj.__control.onclick = clickevent;
     } 
}

//Class dwcheckbox provides a labelled checkbox.
function dwcheckbox(label, value) {

     //Derive from dwcheckable
     dwcheckable.call(this, label, value, 'checkbox');

     var obj = this;

     obj.type = 'dwcheckbox';
}

//Class dwradiobutton provides a labelled radio button.
function dwradiobutton(name, label, value) {

     //Derive from dwcheckable
     dwcheckable.call(this, label, value, 'radio');

     var obj = this;

     if (dd.ie) {
	  
	  obj.__node.removeChild(obj.__control);
	  obj.__control = document.createElement('<input type="radio" name="' + name + '" value="' + value + '">');
	  obj.__node.appendChild(obj.__control);
	  obj.__node.appendChild(obj.__label);

     } else {

	  obj.__control.name = name;
     }

     obj.type = 'dwradiobutton';
}	 

//Class dwselector implements a dropdown list.  Each option must have a unique value.
function dwselector() {

     var obj = this;

     obj.__node = document.createElement("select");

     //Derive from base class
     dwelement.call(this);

     obj.type = 'dwselector';

     //Called when selection changes.  By default does nothing, but can be any function.
     obj.changefn = function () {};

     //Get current number of options.
     obj.length = function () {

	  return obj.__node.length;
     }

     //Add an option to the selector.  If VALUE already exists in the
     //selector list, the option is replaced.
     obj.addOption = function (name, value) {

	  var idx = obj.findValue(value);

	  if (idx >= 0) {

	       obj.__node.options[idx] = new Option(name, value);

	  } else {

	       obj.__node.options[obj.__node.length] = new Option(name, value);
	  }
     }

     //Iterates over the object DICT, and for each property, adds an
     //option with name = property value, and value = property name.
     obj.addOptionsFromObject = function (dict) {

	  for(var i in dict) {

	       obj.addOption(dict[i], i);
	  }
     }

     //Gets the value of the option at IDX.  Does not change the
     //selected option.  It is an error if no option is defined at
     //IDX.
     obj.getOption = function (idx) {
	  
	  if (idx >= obj.__node.length) {

	       throw("InvalidIndex: No option at index " + idx + ".");
	  }

	  return [obj.__node.options[idx].value, obj.__node.options[idx].text];
     }

     //Set option at IDX to the given NAME and VALUE.  It is an error if IDX >= length.
     obj.setOption = function (idx, name, value) {
	  
	  if (idx >= obj.__node.length) {

	       throw("InvalidIndex: No option at index " + idx + ".");
	  }

	  obj.__node.options[idx] = new Option(name, value);
     }

     //Remove option at IDX.  It is an error of IDX >= length.
     obj.removeOption = function (idx) {

	  if (idx >= obj.__node.length) {

	       throw("InvalidIndex: No option at index " + idx + ".");
	  }

	  obj.__node.options[idx] = null;
     }

     //Remove all options.
     obj.clearOptions = function () {

	  obj.__node.options.length = 0;
     }

     //Selects the option at IDX.  It is an error if no option is defined at IDX.
     obj.setSelected = function (idx) {
	  
	  if (idx >= obj.__node.length) {

	       throw("InvalidIndex: No option at index " + idx + ".");
	  }

	  obj.__node.selectedIndex = idx;
	  obj.changefn();
     }

     //Gets the value of the currently selected option.
     obj.getValue = function () {

	  return obj.__node.options[obj.__node.selectedIndex].value;
     }

     //Sets selection to the option with value VALUE.
     obj.setValue = function (value) {

	  var idx = obj.findValue(value);

	  if (idx >= 0) {

	       obj.setSelected(idx);

	  } else {

	       throw("NotFound: Value '" + value + "' does not exist.");
	  }
     }

     //Gets index of the option with value VALUE.
     obj.findValue = function (value) {

	  for(var i = 0; i < obj.__node.options.length; i++) {

	       if (value == obj.__node.options[i].value) {

		    return i;
	       }
	  }

	  return -1;
     }

     //Sets change function to FN.
     obj.setChange = function (fn) {

	  obj.__node.onchange = fn;
	  obj.changefn = fn;
     }

     //Checks or sets whether this input is disabled.
     obj.disabled = function (flag) {

	  if (flag != null) {

	       obj.__node.disabled = flag;

	  } else {

	       return obj.__node.disabled;
	  }
     }
}

//Class dwmultiselector implements a selection box with multiple
//selectable options.  Each option must have a unique value.
function dwmultiselector(size) {

     var obj = this;

     dwselector.call(this);

     obj.type = 'dwmultiselector';

     obj.__node.multiple = true;
     obj.__node.size = size;

     //Sets selection to each option with index in array IDXARR.  It is an error if any of IDXARR indexes are not set.
     obj.setSelected = function (idxarr) {

	  for (var i = 0; i < idxarr.length; i++) {

	       var idx = idxarr[i];

	       obj.__node.options[idx].selected = true;
	  }

	  obj.changefn();
     }

     //Returns array of values of all selected options.
     obj.getValue = function () {

	  var vals = new Array();

	  for (var i = 0; i < obj.__node.options.length; i++) {

	       if (obj.__node.options[i].selected) {

		    vals.push(obj.__node.options[i].value);
	       }
	  }

	  return vals;
     }

     //Sets all options with values in VALUES array.
     obj.setValue = function (values) {

	  for (var i = 0; i < values.length; i++) {

	       var idx = obj.findValue(values[i]);
	  
	       if (idx >= 0) {

		    obj.setSelected(idx);

	       } else {

		    throw("NotFound: Value '" + value + "' does not exist.");
	       }
	  }
     }
}


//Class divwindow implements the actual divwindow as a specialization of the dwpane.  All
//divwindows contain a header pane and a close button; this can be hidden or shown at will.
function divwindow(id, headertext, width, height, top, left) {

     if (!document.getElementById(id)) {

	  //divwindow subclasses dwpane
	  dwpane.call(this, width, height); 
  
	  //convenience handle
	  var obj = this;

	  obj.closeevent = function () {};

	  var header = new dwheader(headertext);

	  header.setStyle("border", "");

	  obj.setStyle("border", "2px ridge");
	  obj.setStyle("position", "absolute");
	  obj.setStyle("overflow", "hidden");
	  obj.setPosition(left, top);

	  obj.add('header', header);

	  var closebutton = new dwbutton('X', function () { obj.close(); });

	  closebutton.setStyle('fontSize', '8pt');
	  closebutton.setStyle('fontWeight', 'bold');
	  closebutton.setStyle('position', 'absolute');
	  closebutton.setStyle('right', '0px');
	  closebutton.setStyle('height', '20px');
	  closebutton.hide();
	  obj.header.add('closebutton', closebutton);

	  //This element alone needs a globally visible ID.
	  obj.__node.id = id;

	  obj.type = 'divwindow';

	  var parentattach = obj.attach;

	  //Methods

	  //Overrides attach in order to trigger dragdrop capability, and prevent duplication.
	  obj.attach = function (parentnode) {

	       if (!document.getElementById(id)) {
		    
		    parentattach(parentnode);
		    //obj.jqobj.draggable();
		    ADD_DHTML(id);
	  
	       }
	  }

	  //Closes this divwindow.  The object is not deleted, and the window could conceivably be reopened.  
	  obj.close = function () { 

	       dd.elements[obj.__node.id].del();

	       obj.closeevent();
		  
	       obj.detach();

	  }

	  //Shows or hides the close button.
	  obj.makeCloseable = function (flag) {

	       if (flag) {
			   
		    closebutton.show();
		    obj.closeable = true;

	       } else {

		    if (obj.closeable) {

			 closebutton.hide();
			 obj.closeable = false;
		    }
	       }
	  }

	  //Sets header title.
	  obj.setTitle = function (newtitle) {

	       obj.header.headertext.setText(newtitle);
	  }

     } else {

	  throw("DuplicateWindow: divwindows must have unique IDs.");
     }
}
