/*****************************************************************************
 nixWidgets library
 ==================
 
 
 
 

 (c) 2006-07 Nick Moon (Software Services) www.nixsoft.co.uk
******************************************************************************/

/*****************************************************************************
 Create instance of global namespace for this library.
******************************************************************************/

var nix = new Object();
nix.Widgets = new Object();

/*****************************************************************************
 Useful routines.
******************************************************************************/


/*****************************************************************************
 $ accepts element or id of element - returns element.
******************************************************************************/

nix.$ = function(el) {
  if (typeof(el) == 'string') {
    el = document.getElementById(el);
  }
  return el;
}

/*****************************************************************************
 log routine
 -----------

 Used to send a message to the log. This should probably be implemented
 by the user adding a handler for this kind of event.

******************************************************************************/

// constants for the various log options

nix.LOGNORMAL = 1;
nix.LOGDEBUG  = 2;

nix.logOptions = nix.LOGNORMAL;
nix.log = function(option, msg) {
  if (option & nix.logOptions) {
    alert(msg);
  }
}


/*****************************************************************************
  event handling routines
******************************************************************************/

/*****************************************************************************
  addEvent
  --------

  Add an event handler to an element - in a browser independent
  way.

******************************************************************************/

nix.addEvent = function(element, event, handler) {
  element = nix.$(element);
  if (element.addEventListener) {  //DOM2
    element.addEventListener(event, handler, true);
  }
  else if (element.attachEvent) { //IE
    element.attachEvent('on'+event, handler);
  }
  else { //old
    element['on'+event] = handler; // limited to only one handler on any one event.
  }
}

/*****************************************************************************
  removeEvent
  -----------

  Removes an event handler of an element - in a browser independent
  way.

******************************************************************************/

nix.removeEvent = function(element, event, handler) {
  element = nix.$(element);
  if (element.removeEventListener) { //DOM2
    element.removeEventListener(event, handler, true);
  }
  else if (element.detachEvent) {
    element.detachEvent('on'+event, handler);
  }
  else { //old but don't bother to do anything...
  }
}

/*****************************************************************************
  stopEvent
  -----------

  Stops an event being propagated(/bubbled).

******************************************************************************/

nix.stopEvent = function(event) {
  if (event.stopPropagation) { //DOM2
    event.stopPropagation();
  }
  else {
    event.cancelBubble = true;
  }
}


/*****************************************************************************
  getSender
  ---------

  Get the element that originated the event. The value of 'this'
  is a bit (understatement!) unreliable.

******************************************************************************/

nix.getSender = function(event) {
  var sender = null;
  if (event) {
    if (event.currentTarget) {        //DOM2
      sender = event.currentTarget;
    }
    else if (event.srcElement) {      //IE
      sender = event.srcElement;
    }
  }
  return sender;
}

/*****************************************************************************

  Screen position routines
  ========================


******************************************************************************/

nix.isAbsolute = function(el) {
  if (document.defaultView) {
    return (document.defaultView.getComputedStyle(el, null).getPropertyValue('position') == 'absolute');
  }
  else {
    return (el.currentStyle.position == 'absolute');
  }
}

nix.elementToScreenLeft = function(el) {
  el = nix.$(el);
  var x = 0;
  while (el != null) {
    x += el.offsetLeft;
    el = el.offsetParent;
  }
  return x;
}

nix.elementToScreenTop = function(el) {
  el = nix.$(el);
  var x = 0;
  while (el != null) {
    x += el.offsetTop;
    el = el.offsetParent;
  }
  return x;
}




/******************************************************************************

 Emulation of standard DOM/XML/XSLT classes for IE
 =================================================

 The following classes are defined for IE and emulate (partially) the behaviour
 of the Firefox/W3C standard. In other words IE is patched so it looks like
 Firefox.

 Classes emulate:

  * DOMParser
  * XMLSerializer
  * XSLTProcessor

*******************************************************************************/

if (typeof DOMParser == 'undefined') {

  /* if IE need to define DOMParser */

  DOMParser = function() {

  }

  DOMParser.prototype.parseFromString = function(text, contentType) {
    var doc = new ActiveXObject("MSXML2.DOMDocument");
    doc.loadXML(text);
    if (doc.xml == '') {
      var pe = doc.parseError;
      doc.loadXML("<parsererror>Error in XML:  " + pe.reason + "\n in line:"
        + pe.line +' col:' + pe.linepos + " " + EscapeXML(pe.srcText) + "</parsererror>");
      alert(doc.documentElement.xml);
    };
    return doc;
  }

}


if (typeof XMLHttpRequest == 'undefined') {

  XMLHttpRequest = function() {
    return new ActiveXObject("Microsoft.XMLHTTP");
  }

}



if (typeof XMLSerializer == 'undefined') {

  /* if IE need to define XMLSerializer */
  XMLSerializer = function() {

  }

  XMLSerializer.prototype.serializeToString = function(node) {
    if (node.xml) {
      return node.xml;
    }
    else {
      throw "XMLSerializer not implemented.";
    }
  }

}

if (typeof XSLTProcessor == 'undefined') {

  /* if IE need to define XSLTProcessor */

  /* not most methods aren't implemented. A more complete example would include
  the relevant ActiveX processor and wrap that... */

  XSLTProcessor = function() {
    this._xslt = null;
  }

  XSLTProcessor.prototype.clearParameters = function() {
    //Not implemented
  }

  XSLTProcessor.prototype.getParameter = function(namespaceURI, localName) {
    //Not implemented
  }

  XSLTProcessor.prototype.importStylesheet = function(stylesheet) {
    this._xslt = stylesheet;
  }

  XSLTProcessor.prototype.removeParameter = function(namespaceURI, localName) {
    //Not implemented
  }

  XSLTProcessor.prototype.reset = function() {
    //Not implemented
  }

  XSLTProcessor.prototype.setParameter = function(namespaceURI, localName, value) {
    //Not implemented
  }

  XSLTProcessor.prototype.transformToDocument = function(sourceNode) {
    var result = new ActiveXObject('MSXML2.DOMDocument');
    sourceNode.transformNodeToObject(this._xslt, result);
    return result;
  }

  XSLTProcessor.prototype.transformToFragment = function(sourceNode, ownerDocument) {
    //Not implemented
  }

}



/*****************************************************************************

 nix.Widgets.Pages class
 =======================

 Provides behaviour for a set of 'pages' (typically divs) where only one is
 visible at a time.

 DONE
 ----
 - autocreate entries in pages[] from a passed in parent element.

 TO-DO
 -----

 1) check in IE7 &amp; IE6.
 2) optional autocreate tabs[] from a passed in parent tab element.


HTML
----
Typically will be:

<div class="nixPages">
  <div class="nixPageVisible">

  </div>
  <div class="nixPage">

  </div>
  <div class="nixPage">

  </div>
  ...
</div>

CSS
---
The following classes need to be defined:

.nixPages {

}

.nixPageVisible {
  visibility: visible;
  height: 100%;
}

.nixPage {
  visibility: hidden;
  height: 0px;
}

.nixTab {

}

.nixTabSelected {

}

Usage
-----

Create an instance of Pages and then add the page (and optionally tab elements
to the arrays pages[] and tabs[]). If tabs[] are used the number should match
the number of pages.



******************************************************************************/

nix.Widgets.Pages = function(el) {

  function Walk(el, sender) {
    for (var i = 0; i < el.childNodes.length; i++) {
      var child = el.childNodes[i];
      if (child.nodeType == 1 /* ELEMENT */) {
        if ((child.className == sender.classPage)
                 || (child.className == sender.classPageVisible)) {
          sender.pages[sender.pages.length] = child;
        }
        else {
          Walk(child, sender);
        }
      }
    }
  }

  el = nix.$(el);
  this.classPage = 'nixPage';
  this.classPageVisible = 'nixPageVisible';
  this.page = -1;
  this.pages = new Array();
  this.tabs = new Array();
  if (el) {
    Walk(el, this);
  }
}

nix.Widgets.Pages.prototype.showPage = function() {
  for (var i in this.pages) {
    this.pages[i].className = this.classPage;
    if (this.tabs.length > 0) {
      this.tabs[i].disabled = false;
    }
  }
  if ((this.page >= 0) && (this.page < this.pages.length)) {
    this.pages[this.page].className = this.classPageVisible;
    if (this.tabs.length > 0) {
      this.tabs[this.page].disabled = true;
    }
  }
}

nix.Widgets.Pages.prototype.setPage = function(i) {
  this.page = i;
  if (this.page < 0) { this.page = 0; }   //>
  if (this.page >= this.pages.length) { this.page = this.pages.length - 1; }
  this.showPage();
}




/*

Untested PagesWizard - allows addition of next/back buttons

function PagesWizard() {
  Pages.call(this);
}

PagesWizard.prototype.showPage = Pages.prototype.showPage;

Pages.prototype.setPage = function(i) {
  Pages.prototyp.setPage.call(this);
  this.setButtons();
}

PagesWizard.prototype.setButtons = function() {
  if (this.page == 0) {
    this.btnBack.disabled = true;
  }
  else {
    this.btnBack.disabled = false;
  }
  if (this.page >= 3) { this.btnNext.disabled = true;
  }
  else {
    this.btnNext.disabled = false;
  }
}

Pages.prototype.back = function() {
  switch (this.page) {
    default :
      this.setPage(this.page-1);
      break;
  }
}

Pages.prototype.next = function() {
  switch (this.page) {
    default :
      this.setPage(this.page + 1);
      break;
  }
}

*/




/*****************************************************************************

 nix.Widgets.Wiper class
 =======================

 Provides behaviour for a draggable 'wiper' to allow the dynamic resizing of
 panels either side of the 'wiper'. The wiper should typically be a button, the
 panels div's. Wiping may be either left-right (LR) or top-bottom (TB).

 TO-DO
 -----

 1) get working.
 2) get working for static or absolute positioning
 3) get working in IE7/6

 HTML
 ----
 Typically will be:

<div>
  <div id="panelA">

  </div>
  <button id="wiper" class="nixWiper"></button>
  <div id="panelB">

  </div>
</div>

CSS
---
The following classes need to be defined:

button.nixWiper {

}


Usage
-----

Create an instance of Wiper passing the two panels and the wiper button.


  className - string
  wiperset  - element
  wiper     - element
  wiped     - element
  btn       - elemnt
  selected  - integer
  min       - CSS size
  max       - CSS size
  direction - LR or TB
  -------------------
  WiperSet(wiperset) - constructor
  OnMouseMove()  - integer
  OnMouseUp(index) - boolean

******************************************************************************/

nix.Widgets._WiperInDrag = null; // global variable stores Wiper while doing a drag
                                 // this is common to all wipers - but you can only
                                 // drag one at a time anyway.

nix.Widgets.Wiper = function(panelA, btn, panelB) {
  //this.className = 'nixWiper';
  this.btn = nix.$(btn);
  this.btn.wiper = this;
  this.panelA = nix.$(panelA);
  this.panelB = nix.$(panelB);
  this.LR = 'LR';
  this.TB = 'TB';
  this.direction = this.LR;
  this.min = 0;
  this.max = -1;
  this.isMove = false;
  nix.addEvent(this.btn, 'click', this.OnClick);
  nix.addEvent(this.btn, 'mousedown', this.MouseDownHandler);
}

nix.Widgets.Wiper.prototype.Min =
  function() {
    return this.min;
  }

nix.Widgets.Wiper.prototype.Max =
  function() {
    var max = this.max;
    if (max <= 0) {  //>
      if (this.direction == this.LR) {
        max = this.panelA.offsetWidth + this.panelB.offsetWidth;
      }
      else {
        max = this.panelA.offsetHeight + this.panelB.offsetHeight;
      }
    }
    return max;
  }

nix.Widgets.Wiper.prototype.InRange =
  function(x) {
    return (x>this.Min()) && (x<this.Max());
  }

nix.Widgets.Wiper.prototype.MoveByX =
  function(delta) {
    if (nix.isAbsolute(this.panelB)) {
      //  awl.log(awl.LOGDEBUG,delta);
      if (this.InRange(parseInt(this.btn.style.left)+delta)) {
        // to improve performance don't update the two panels during move
        //this.panelA.style.width = (parseInt(this.panelA.style.width)+delta) + 'px';
        this.btn.style.left = (parseInt(this.btn.style.left)+delta) + 'px';
        //this.panelB.style.left = (parseInt(this.panelB.style.left)+delta) + 'px';
      }
    }
    else {
      nix.log(nix.LOGDEBUG,this.panelA.offsetWidth + ' ' + this.panelB.offsetWidth);
      if (this.InRange(this.panelA.offsetWidth + delta)
            && ((this.panelB.offsetWidth - delta)>0)) {
        var max = this.Max();
        // this.panelA.style.width = (this.panelA.offsetWidth + delta) + 'px';
        // //this.panelB.style.width = (this.panelB.offsetWidth - delta) + 'px';
        // this.panelB.style.width = (max - this.panelA.offsetWidth - delta) + 'px';
        var widthA = this.panelA.offsetWidth + delta;
        if (widthA<0) { widthA = 0; }
        if (widthA > max) { widthA = max; }
        var widthB = max - widthA;
        this.panelA.style.width = (widthA) + 'px';
        this.panelB.style.width = (widthB) + 'px';
      }
    }
  }

nix.Widgets.Wiper.prototype.MoveByY =
  function(delta) {
    if (nix.isAbsolute(this.panelB)) {
      //awl.log(awl.LOGDEBUG,delta);
      if (this.InRange(parseInt(this.btn.style.top)+delta)) {
        // to improve performance don't update the two panels during move
        //this.panelA.style.height = (parseInt(this.panelA.style.height)+delta) + 'px';
        this.btn.style.top = (parseInt(this.btn.style.top)+delta) + 'px';
        //this.panelB.style.top = (parseInt(this.panelB.style.top)+delta) + 'px';
      }
    }
    else {
      nix.log(nix.LOGDEBUG,this.panelA.offsetHeight + ' ' + this.panelB.offsetHeight);
      if (this.InRange(this.panelA.offsetHeight + delta)
            && ((this.panelB.offsetHeight - delta)>0)) {
        this.panelA.style.height = (this.panelA.offsetHeight + delta) + 'px';
        this.panelB.style.height = (this.panelB.offsetHeight - delta) + 'px';
      }
    }
  }

nix.Widgets.Wiper.prototype.MoveToX =
  function(x) {
    if (nix.isAbsolute(this.panelB)) {
      this.panelA.style.width = (x - nix.elementToScreenLeft(this.panelA)) + 'px';
      this.btn.style.left = (x - nix.elementToScreenLeft(this.panelA)) + 'px';
      this.panelB.style.left = (x - nix.elementToScreenLeft(this.panelA) + this.btn.offsetWidth) + 'px';
    }
    else {
      var widthA = x - nix.elementToScreenLeft(this.panelA);
      if (widthA<0) { widthA = 0; }
      if (widthA > this.Max()) { widthA = this.Max(); }
      //var widthB = this.panelB.offsetWidth + this.panelA.offsetWidth - widthA;
      var widthB = this.Max() - widthA;
      this.panelA.style.width = (widthA) + 'px';
      this.panelB.style.width = (widthB) + 'px';
    }
  }

nix.Widgets.Wiper.prototype.MoveToY =
  function(y) {
    if (nix.isAbsolute(this.panelB)) {
      this.panelA.style.height = (y - nix.elementToScreenTop(this.panelA)) + 'px';
      this.btn.style.top = (y - nix.elementToScreenTop(this.panelA)) + 'px';
      this.panelB.style.top = (y - nix.elementToScreenTop(this.panelA) + this.btn.offsetHeight) + 'px';
    }
    else {
      var heightA = y - nix.elementToScreenTop(this.panelA);
      if (heightA<0) { heightA = 0; }
      var heightB = this.panelB.offsetHeight + this.panelA.offsetHeight - heightA;
      this.panelA.style.height = (heightA) + 'px';
      this.panelB.style.height = (heightB) + 'px';
    }
  }


nix.Widgets.Wiper.prototype.MoveTo =
  function(event) {
    if (this.direction == this.LR) {
      this.MoveToX(event.clientX);
    }
    else {
      this.MoveToY(event.clientY);
    }
}

nix.Widgets.Wiper.prototype.OnClick =
  function(event) {
    if (!event) { event = window.event; }
    if (event) {
      var wiper = nix.getSender(event).wiper;
      if (wiper.isMove) {
        wiper.isMove = false;
      }
      else {
        var delta = parseInt((wiper.Max() - wiper.Min())/6);
        if (wiper.direction == wiper.LR) {
          if ((wiper.panelA.offsetWidth + delta) >= wiper.Max()) {
            wiper.MoveToX(nix.elementToScreenLeft(wiper.panelA) + wiper.Min());
          }
          else {
            wiper.MoveToX(nix.elementToScreenLeft(wiper.panelA) + wiper.panelA.offsetWidth + delta);
          }
        }
        else {
          if ((wiper.panelA.offsetHeight + delta) >= wiper.Max()) {
            wiper.MoveToY(nix.elementToScreenTop(wiper.panelA) + wiper.Min() + 1);
          }
          else {
            wiper.MoveToY(nix.elementToScreenTop(wiper.panelA) + wiper.panelA.offsetHeight + delta);
          }
        }
      }
    }
  }

nix.Widgets.Wiper.prototype.MouseDownHandler =
  function(event) {
    if (!event) { event = window.event; }
    nix.log(nix.LOGDEBUG,"mouse down " + this);
    nix.Widgets._WiperInDrag = nix.getSender(event).wiper;
    nix.addEvent(document, 'mousemove', nix.Widgets._WiperInDrag.MouseMoveHandler);
    nix.addEvent(document, 'mouseup', nix.Widgets._WiperInDrag.MouseUpHandler);
    nix.stopEvent(event);
  }

nix.Widgets.Wiper.prototype.MouseUpHandler =
  function(event) {
    if (!event) { event = window.event; }
    nix.log(nix.LOGDEBUG,"mouse up");
    nix.Widgets._WiperInDrag.MoveTo(event);
    nix.removeEvent(document, 'mousemove', nix.Widgets._WiperInDrag.MouseMoveHandler);
    nix.removeEvent(document, 'mouseup', nix.Widgets._WiperInDrag.MouseUpHandler);
    nix.stopEvent(event);
    nix.Widgets._WiperInDrag = null;
  }

nix.Widgets.Wiper.prototype.MouseMoveHandler = function(event) {
  if (!event) { event = window.event; }
  if (nix.Widgets._WiperInDrag !=  null) {
    nix.Widgets._WiperInDrag.isMove = true;
    nix.log(nix.LOGDEBUG,'mouse move: ' + event.clientX + ',' + event.clientY);
    nix.stopEvent(event);
    if (nix.Widgets._WiperInDrag.direction == nix.Widgets._WiperInDrag.LR) {
      nix.Widgets._WiperInDrag.MoveByX(event.clientX - nix.elementToScreenLeft(nix.Widgets._WiperInDrag.btn));
    }
    else {
      nix.Widgets._WiperInDrag.MoveByY(event.clientY - nix.elementToScreenTop(nix.Widgets._WiperInDrag.btn));
    }
  }
}



/*****************************************************************************

 nix.Widgets.RTEditor class
 ==========================

 Adds behaviour to support the IE/Moz rich-edit control.

 TO-DO
 -----

 - Look for code to clean up XHTML in and out.



 HTML
 ----
 Typically will be:

<iframe id="rte" name="rte" src="about:blank"
  onload="if (rteditor) rteditor.AfterLoad();"></iframe>
  
The onload event can trigger before rteditor has been created - hence this code.

The iframe will typically have a toolbar assoaciated with it.


CSS
---

Usage
-----

Create an instance of RTEditor passing the iframe name to it. The editor is
loaded from a string using LoadFromString(). The source should be valid HTML/XHTML



  frameName   - string
  el          - element
  modified    - boolean
  isIE        - boolean
  isSupported - boolean
  -------------------
  RTE(frameName) - constructor



******************************************************************************/

// constructor

nix.Widgets.RTEditor = function(frameName) {
  this.frameName = frameName;  // name of the iframe element in the html
  this.el = frames[frameName];
  this.el.js = this;
  this.modified = false;       // set when content has changed
  this.isIE = false;           // set if browser is IE
  this.isSupported = true;     // unset if browser doesn't support rich-text editing
  this.detectBrowser();
  this.onStateChange = null;
}

nix.Widgets.RTEditor.prototype.detectBrowser =  function() {
 	var ua = navigator.userAgent.toLowerCase();
  this.isIE = ((ua.indexOf("msie") != -1) && (ua.indexOf("opera") == -1) && (ua.indexOf("webtv") == -1));
 	//var isGecko = (ua.indexOf("gecko") != -1);
  var isSafari = (ua.indexOf("safari") != -1);
  var isKonqueror = (ua.indexOf("konqueror") != -1);
  this.isSupported = (document.getElementById && document.designMode && !isSafari && !isKonqueror);
}

nix.Widgets.RTEditor.prototype.execCommand = function(sCmd,Value) {
  this.el.focus();
  this.el.document.execCommand(sCmd, false, Value);
  this.el.focus();
  if ((sCmd != 'styleWithCSS') && (sCmd != 'selectall') && (sCmd != 'contentReadOnly')) {
    this.setState(true);
  }
}

nix.Widgets.RTEditor.prototype.queryCommandEnabled = function(sCmd) {
 	try {
    return this.el.document.queryCommandEnabled(sCmd);
 	}
  catch (e) {
 	}
}

nix.Widgets.RTEditor.prototype.queryCommandState = function(sCmd) {
  return this.el.document.queryCommandState(sCmd);
}

nix.Widgets.RTEditor.prototype.queryCommandValue = function(sCmd) {
  return this.el.document.queryCommandValue(sCmd);
}

nix.Widgets.RTEditor.prototype.Cut = function() {
  try {
    this.execCommand('cut',null);
  } catch(e) {
    alert('Browser does not allow use of clipboard without modifying security settings');
  }
}

nix.Widgets.RTEditor.prototype.Copy = function() {
  try {
    this.execCommand('copy',null);
  } catch(e) {
    alert('Browser does not allow use of clipboard without modifying security settings');
  }
}

nix.Widgets.RTEditor.prototype.Paste = function() {
  try {
    this.execCommand('paste',null);
  } catch(e) {
    alert('Browser does not allow use of clipboard without modifying security settings');
  }
}

nix.Widgets.RTEditor.prototype.LoadFromString = function(sHTML) {
  var doc = this.el.document;
  doc.designMode = "On";
  doc.open();
  doc.writeln(sHTML);
  doc.close();
  if (!this.isIE) {
   	this.execCommand("styleWithCSS", false);
    this.setState(false);
  }
  else {
    this.setState(true); // Haven't intercept IE key-presses so make file modified immediately
  }
  this.el.js = this;  //The iframe window seems to forget the js property when reloaded!
}

nix.Widgets.RTEditor.prototype.AfterLoad = function() {
  //nix.removeEvent(this.el, "keypress", this.KeyPressed); //should this be here?
  nix.addEvent(this.el, "keypress", this.KeyPressed);
}


nix.Widgets.RTEditor.prototype.processText = function(ss) {

  function normaliseSpace(s) {
    s = s.replace(/[\s]+/g, " ");
    return s;
  }

    ss = normaliseSpace(ss);    
    switch (this.context[this.context.length - 1]) {
      case 'body':
        if (ss != '') {
          this.s += "\r\n<p>";
          this.s += ss;
          this.context.push('bodyp');
        }
        break;
      default:
        this.s += ss;
        break;
    }
  }

nix.Widgets.RTEditor.prototype.processElement = function(el, atts) {
    var trailer = '';
    if (el == 'br' || el == 'hr' || el == 'img' || el == 'input' || el == 'link' || el == 'meta') {
      trailer = ' /';
    }
    var omit = false;
    switch (this.context[this.context.length - 1]) {
      case 'body':
        switch (el) {
          case '/body':
            this.context.pop();
            break;
          case 'b':
          case 'i':
          case 'u':
          case 'a':
          case 'span':
          case 'strong':
          case 'em' :
            this.s += "\r\n<p>";
            this.context.push('bodyp');
            break;
          case 'p' :
          case 'div' :
          case 'h1':
          case 'h2':
          case 'h3':
          case 'h4':
          case 'h5':
          case 'h6':
            this.context.push('bodypp');
            break;
          case 'ol' :
          case 'ul' :
            this.context.push('xl');
            break;
          case 'li':
            this.context.push('li');
            break;
          case 'br':
            omit = true;
            break;
        }
        break;
      case 'bodyp':
        switch (el) {
          case '/body' :
            this.s += "</p>\r\n";
            this.context.pop();
            this.context.pop();
            break;
          case 'p':
          case 'div':
          case 'h1':
          case 'h2':
          case 'h3':
          case 'h4':
          case 'h5':
          case 'h6':
            this.s += "</p>\r\n";
            this.context.pop();
            this.context.push('bodypp');
            break;
          case 'ol':
          case 'ul':
            this.s += "</p>\r\n";
            this.context.pop();
            this.context.push('xl');
            break;
          case 'br':
            omit = true;
            break;
        }
        break;
      case 'bodypp':
        switch (el) {
          case '/p':
          case '/div':
          case '/h1':
          case '/h2':
          case '/h3':
          case '/h4':
          case '/h5':
          case '/h6':
            this.context.pop();
            break;
          case 'ol':
          case 'ul':
            this.context.push('xl');
            break;
          case 'br':
            omit = true;
            break;
        }
        break;
      case 'xl':
        switch (el) {
          case '/ol':
          case '/ul':
            this.context.pop();
            break;
          case 'li':
            this.context.push('li');
            break;
          case 'br':
            omit = true;
            break;
        }
        break;
      case 'li':
        switch (el) {
          case '/ol':
          case '/ul':
            this.s += '</li>';
            this.context.pop();
            this.context.pop();
            break;
          case 'li':
            this.s += '</li>';
            break;
          case '/li':
            this.context.pop();
            break;
          case 'br':
            omit = true;
            break;
        }
        break;
      default:
        switch (el) {
          case 'body':
            this.context.push('body');
            break;
        }
        break;
    }
    if (!omit) {
      this.s += '<' + el + atts + trailer + '>';
    }
  }



nix.Widgets.RTEditor.prototype.SaveAsHTML = function() {
  var sHTML = "<html>" + this.el.document.documentElement.innerHTML + "</html>";
  //alert(sHTML);
  this.context = [''];
  this.s = '';

  var re = /([^<]*)<([\w\:\/]*)([^>]*)>/g;
  var chunk;
  while ((chunk = re.exec(sHTML)) != null) {
    var text = chunk[1];
    var el = chunk[2].toLowerCase();
    var atts = chunk[3];
    atts = atts.replace(/([\w\:]+)=(?:[\"]([^>^\"]+)[\"]|[\']?([^>^\']+)[\']?|([^>^\"^\'^\s]+))/gi, "$1=\"$2\"");
    this.processText(text);
    this.processElement(el,atts);
  }
  //alert(this.s);
  s = this.s.replace(/&nbsp;/g, " ");    //IE inserts nbsp's we don't want.
  return s;
}

nix.Widgets.RTEditor.prototype.KeyPressed = function(e) {
  //alert(this);
  if (!e) {e = Window.event; }
  if (e.keyCode<37 || e.keyCode>40) {
    this.js.setState(true);
  }
}

nix.Widgets.RTEditor.prototype.setState = function(modified) {
  if (this.modified != modified) {
    this.modified = modified;
    if (this.onStateChange) {
      this.onStateChange(this);
    }
  }
}

