/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Selection List Properties Dialog.
 *
 * The Initial Developer of the Original Code is
 * Neil Rashbrook.
 * Portions created by the Initial Developer are Copyright (C) 2001
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s): Neil Rashbrook <neil@parkwaycc.co.uk>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// Global variables

var hasValue;
var oldValue;
var insertNew;
var globalArray;
var selectElement;
var currentObject = null;
var selectedOption = 0;
var selectedOptionCount = 0;

// Utility functions

function GetObjectForTreeItem(treeItem)
{
  return globalArray[parseInt(treeItem.getAttribute("index"))];
}

function InsertBefore(parent, before, after)
{
  var savedObject = currentObject;
  before.parentObject = parent;
  parent.element.insertBefore(before.element, after.element);
  parent.treeChildren.insertBefore(before.treeItem, after.treeItem);
  gDialog.tree.focus();
  gDialog.tree.selectItem(savedObject.treeItem);
  gDialog.tree.ensureElementIsVisible(savedObject.treeItem);
  gDialog.previousButton.disabled = !savedObject.canMoveUp();
  gDialog.nextButton.disabled = !savedObject.canMoveDown();
}

function AppendChild(parent, option)
{
  var savedObject = currentObject;
  option.parentObject = parent;
  parent.treeChildren.appendChild(option.treeItem);
  parent.element.appendChild(option.element);
  gDialog.tree.focus();
  gDialog.tree.selectItem(savedObject.treeItem);
  gDialog.tree.ensureElementIsVisible(savedObject.treeItem);
  gDialog.previousButton.disabled = !savedObject.canMoveUp();
  gDialog.nextButton.disabled = !savedObject.canMoveDown();
}

function UpdateSelectMultiple()
{
  if (selectedOptionCount > 1)
  {
    gDialog.selectMultiple.checked = true;
    gDialog.selectMultiple.disabled = true;
  }
  else
    gDialog.selectMultiple.disabled = false;
}

function createXUL(localName)
{
  return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", localName);
}

// OPTION element wrapper object

function optionObject(parent, option)
{
  if (option.hasAttribute("selected"))
    selectedOptionCount++;
  this.globalIndex = globalArray.length;
  globalArray[globalArray.length++] = this;
  this.parentObject = parent;
  this.element = option;
  this.treeItem = createXUL("treeitem");
  this.treeRow = createXUL("treerow");
  this.treeCellText = createXUL("treecell");
  this.treeCellValue = createXUL("treecell");
  this.treeCellSelected = createXUL("treecell");
  this.treeCellCheckbox = createXUL("checkbox");
  this.treeItem.setAttribute("index", this.globalIndex);
  this.treeItem.setAttribute("container", "true");
  this.treeItem.setAttribute("empty", "true");
  this.treeCellText.setAttribute("class", "treecell-indent");
  this.treeCellText.setAttribute("notwisty", "true");
  this.treeCellText.setAttribute("label", option.text);
  if (option.hasAttribute("value"))
    this.treeCellValue.setAttribute("label", option.value);
  else
    this.treeCellValue.setAttribute("label", option.text);
  this.treeCellCheckbox.setAttribute("allowevents", "true");
  this.treeCellCheckbox.setAttribute("checked", option.hasAttribute("selected"));
  this.treeCellCheckbox.setAttribute("style", "-moz-user-focus: ignore; margin: 1px; ");
  this.treeCellSelected.appendChild(this.treeCellCheckbox);
  this.treeRow.appendChild(this.treeCellText);
  this.treeRow.appendChild(this.treeCellValue);
  this.treeRow.appendChild(this.treeCellSelected);
  this.treeItem.appendChild(this.treeRow);
  parent.treeChildren.appendChild(this.treeItem);
  gDialog.treeItem.setAttribute("open", "true");
}

optionObject.prototype.onSpace = function onSpace()
{
  if (this.element.hasAttribute("selected"))
  {
    selectedOptionCount--;
    this.element.removeAttribute("selected");
    this.treeCellCheckbox.setAttribute("checked", "false");
    gDialog.optionSelected.setAttribute("checked", "false");
    selectedOption = 0;
  }
  else
  {
    if (gDialog.selectMultiple.checked || !globalArray[selectedOption].element.hasAttribute("selected"))
      selectedOptionCount++;
    else
    {
      globalArray[selectedOption].element.removeAttribute("selected");
      globalArray[selectedOption].treeCellCheckbox.setAttribute("checked", "false");
    }
    this.element.setAttribute("selected", "");
    this.treeCellCheckbox.setAttribute("checked", "true");
    gDialog.optionSelected.setAttribute("checked", "true");
    selectedOption = this.globalIndex;
  }
  UpdateSelectMultiple();
};

optionObject.prototype.onFocus = function onFocus()
{
  gDialog.optionText.value = this.element.text;
  hasValue = this.element.hasAttribute("value");
  oldValue = this.element.value;
  gDialog.optionHasValue.checked = hasValue;
  gDialog.optionValue.value = hasValue ? this.element.value : this.element.text;
  gDialog.optionSelected.checked = this.element.hasAttribute("selected");
  gDialog.optionDisabled.checked = this.element.hasAttribute("disabled");
  gDialog.selectDeck.setAttribute("selectedIndex", "2");
};

optionObject.prototype.onBlur = function onBlur()
{
  this.element.text = gDialog.optionText.value;
  if (gDialog.optionHasValue.checked)
    this.element.value = gDialog.optionValue.value;
  else
    this.element.removeAttribute("value");
  if (gDialog.optionSelected.checked)
    this.element.setAttribute("selected", "");
  else
    this.element.removeAttribute("selected");
  if (gDialog.optionDisabled.checked)
    this.element.setAttribute("disabled", "");
  else
    this.element.removeAttribute("disabled");
};

optionObject.prototype.canDestroy = function canDestroy(prompt)
{
  return true;
/*return !prompt ||
    ConfirmWithTitle(GetString("DeleteOption"),
                     GetString("DeleteOptionMsg"),
                     GetString("DeleteOption"));*/
};

optionObject.prototype.destroy = function destroy()
{
  if (this.element.hasAttribute("selected"))
  {
    selectedOptionCount--;
    UpdateSelectMultiple();
  }
  this.parentObject.removeChild(this);
};

optionObject.prototype.canMoveUp = function canMoveUp()
{
  return this.treeItem.previousSibling || this.parentObject != gDialog;
}

optionObject.prototype.moveUp = function moveUp()
{
  if (this.treeItem.previousSibling)
    GetObjectForTreeItem(this.treeItem.previousSibling).insertAfter(this);
  else
    InsertBefore(gDialog, this, this.parentObject);
}

optionObject.prototype.insertAfter = function insertAfter(option)
{
  InsertBefore(this.parentObject, option, this);
}

optionObject.prototype.canMoveDown = function canMoveDown()
{
  return this.treeItem.nextSibling || this.parentObject != gDialog;
}

optionObject.prototype.moveDown = function moveDown()
{
  if (this.treeItem.nextSibling)
    GetObjectForTreeItem(this.treeItem.nextSibling).insertBefore(this);
  else if (this.parentObject.treeItem.nextSibling)
    InsertBefore(gDialog, this, GetObjectForTreeItem(this.parentObject.treeItem.nextSibling));
  else
    AppendChild(gDialog, this);
}

optionObject.prototype.insertBefore = function insertBefore(option)
{
  InsertBefore(this.parentObject, this, option);
}

optionObject.prototype.appendChild = function appendChild(child)
{
  return this.parentObject.appendChild(child);
};

// OPTGROUP element wrapper object

function optgroupObject(parent, optgroup)
{
  this.globalIndex = globalArray.length;
  globalArray[globalArray.length++] = this;
  this.parentObject = parent;
  this.element = optgroup;
  this.treeItem = createXUL("treeitem");
  this.treeRow = createXUL("treerow");
  this.treeCell = createXUL("treecell");
  this.treeChildren = createXUL("treechildren");
  this.treeItem.setAttribute("index", this.globalIndex);
  this.treeItem.setAttribute("container", "true");
  this.treeItem.setAttribute("empty", "true");
  this.treeItem.setAttribute("open", "true");
  this.treeCell.setAttribute("class", "treecell-indent");
  this.treeCell.setAttribute("notwisty", "true");
  this.treeCell.setAttribute("label", optgroup.label);
  this.treeRow.appendChild(this.treeCell);
  this.treeItem.appendChild(this.treeRow);
  this.treeItem.appendChild(this.treeChildren);
  parent.treeChildren.appendChild(this.treeItem);
  gDialog.treeItem.setAttribute("open", "true");
  for (var child = optgroup.firstChild; child; child = child.nextSibling)
    if (child.tagName == "OPTION")
      new optionObject(this, child);
}

optgroupObject.prototype.onSpace = function onSpace()
{
};

optgroupObject.prototype.onFocus = function onFocus()
{
  gDialog.optgroupLabel.value = this.element.label;
  gDialog.optgroupDisabled.checked = this.element.disabled;
  gDialog.selectDeck.setAttribute("selectedIndex", "1");
};

optgroupObject.prototype.onBlur = function onBlur()
{
  this.element.label = gDialog.optgroupLabel.value;
  this.element.disabled = gDialog.optgroupDisabled.checked;
};

optgroupObject.prototype.canDestroy = function canDestroy(prompt)
{
  return !this.element.firstChild;
/*return !this.element.firstChild && (!prompt ||
    ConfirmWithTitle(GetString("DeleteOptGroup"),
                     GetString("DeleteOptGroupMsg"),
                     GetString("DeleteOptGroup")));
*/
};

optgroupObject.prototype.destroy = function destroy()
{
  gDialog.removeChild(this);
};

optgroupObject.prototype.canMoveUp = function canMoveUp()
{
  return this.treeItem.previousSibling;
}

optgroupObject.prototype.moveUp = function moveUp()
{
  InsertBefore(this.parentObject, this, GetObjectForTreeItem(this.treeItem.previousSibling));
}

optgroupObject.prototype.insertBefore = function insertBefore(option)
{
  if (this.treeChildren.firstChild)
    InsertBefore(this, option, GetObjectForTreeItem(this.treeChildren.firstChild));
  else
    AppendChild(this, option);
}

optgroupObject.prototype.canMoveDown = function canMoveDown()
{
  return this.treeItem.nextSibling;
}

optgroupObject.prototype.moveDown = function moveDown()
{
  InsertBefore(this.parentObject, GetObjectForTreeItem(this.treeItem.nextSibling), this);
}

optgroupObject.prototype.insertAfter = function insertAfter(option)
{
  AppendChild(this, option);
}

optgroupObject.prototype.appendChild = function appendChild(child)
{
  this.element.appendChild(child);
  return new optionObject(this, child);
};

optgroupObject.prototype.removeChild = function removeChild(child)
{
  this.element.removeChild(child.element);
  this.treeChildren.removeChild(child.treeItem);
  globalArray[child.globalIndex] = null;
};

// dialog initialization code

function Startup()
{
  if (!InitEditorShell())
    return;

  // Get a single selected select element
  var tagName = "select";
  selectElement = editorShell.GetSelectedElement(tagName);

  if (selectElement)
    // We found an element and don't need to insert one
    insertNew = false;
  else
  {
    insertNew = true;

    // We don't have an element selected,
    //  so create one with default attributes

    selectElement = editorShell.CreateElementWithDefaults(tagName);
    if(!selectElement)
    {
      dump("Failed to get selected element or create a new one!\n");
      window.close();
      return;
    }
  }

  // SELECT element wrapper object
  gDialog = {
    accept:           document.documentElement.getButton("accept"),
    selectDeck:       document.getElementById("SelectDeck"),
    selectName:       document.getElementById("SelectName"),
    selectSize:       document.getElementById("SelectSize"),
    selectMultiple:   document.getElementById("SelectMultiple"),
    selectDisabled:   document.getElementById("SelectDisabled"),
    selectTabIndex:   document.getElementById("SelectTabIndex"),
    optgroupLabel:    document.getElementById("OptGroupLabel"),
    optgroupDisabled: document.getElementById("OptGroupDisabled"),
    optionText:       document.getElementById("OptionText"),
    optionHasValue:   document.getElementById("OptionHasValue"),
    optionValue:      document.getElementById("OptionValue"),
    optionSelected:   document.getElementById("OptionSelected"),
    optionDisabled:   document.getElementById("OptionDisabled"),
    removeButton:     document.getElementById("RemoveButton"),
    previousButton:   document.getElementById("PreviousButton"),
    nextButton:       document.getElementById("NextButton"),
    tree:             document.getElementById("SelectTree"),
    treeCols:         document.getElementById("SelectCols"),
    treeItem:         document.getElementById("SelectItem"),
    treeCell:         document.getElementById("SelectCell"),
    treeChildren:     document.getElementById("SelectChildren"),
    element:          selectElement.cloneNode(false),
    globalIndex:      0,
    onSpace:          function onSpace() {},
    onFocus:          function onFocus()
    {
      gDialog.selectName.value = this.element.getAttribute("name");
      gDialog.selectSize.value = this.element.getAttribute("size");
      gDialog.selectMultiple.checked = this.element.hasAttribute("multiple");
      gDialog.selectDisabled.checked = this.element.hasAttribute("disabled");
      gDialog.selectTabIndex.value = this.element.getAttribute("tabindex");
      this.selectDeck.setAttribute("selectedIndex", "0");
      onNameInput();
    },
    onBlur:           function onBlur()
    {
      this.element.setAttribute("name", gDialog.selectName.value);
      if (gDialog.selectSize.value)
        this.element.setAttribute("size", gDialog.selectSize.value);
      else
        this.element.removeAttribute("size");
      if (gDialog.selectMultiple.checked)
        this.element.setAttribute("multiple", "");
      else
        this.element.removeAttribute("multiple");
      if (gDialog.selectDisabled.checked)
        this.element.setAttribute("disabled", "");
      else
        this.element.removeAttribute("disabled");
      if (gDialog.selectTabIndex.value)
        this.element.setAttribute("tabindex", gDialog.selectTabIndex.value);
      else
        this.element.removeAttribute("tabindex");
    },
    appendChild:      function appendChild(child)
    {
      if (child.tagName == "OPTION")
      {
        this.element.appendChild(child)
        return new optionObject(this, child);
      }
      if (child.tagName == "OPTGROUP")
      {
        this.element.appendChild(child)
        return new optgroupObject(this, child);
      }
      return null;
    },
    removeChild:      function removeChild(child)
    {
      this.treeChildren.removeChild(child.treeItem);
      globalArray[child.globalIndex] = null;
    },
    canDestroy:       function canDestroy(prompt)
    {
      return false;
    },
    canMoveUp:        function canMoveUp()
    {
      return false;
    },
    canMoveDown:      function canMoveDown()
    {
      return false;
    }
  }
  globalArray = [gDialog];

  // Workaround for tree scrollbar bug
  for (var col = gDialog.treeCols.firstChild; col.nextSibling; col = col.nextSibling)
  {
    col.setAttribute("width", col.boxObject.width);
    col.removeAttribute("flex");
  }

  // We modify the actual option and optgroup elements so clone them first
  for (var child = selectElement.firstChild; child; child = child.nextSibling)
    gDialog.appendChild(child.cloneNode(true));

  UpdateSelectMultiple();

  gDialog.tree.selectItem(gDialog.treeItem);
  onNameInput();

  SetTextboxFocus(gDialog.selectName);

  SetWindowLocation();
}

function InitDialog()
{
  currentObject.onFocus();
}

function ValidateData()
{
  currentObject.onBlur();
  return true;
}

function onAccept()
{
  // All values are valid - copy to actual element in doc or
  //   element created to insert
  ValidateData();

  try {
    // Coalesce into one undo transaction
    editorShell.BeginBatchChanges();

    editorShell.CloneAttributes(selectElement, gDialog.element);

    if (insertNew)
      // 'true' means delete the selection before inserting
      editorShell.InsertElementAtSelection(selectElement, true);

    while (selectElement.firstChild)
      editorShell.DeleteElement(selectElement.firstChild);

    var newNodes = gDialog.element.childNodes;
    for (var offset = 0; offset < newNodes.length; offset++)
      editorShell.InsertElement(newNodes[offset], selectElement, offset, true);
  } finally {
    editorShell.EndBatchChanges();
  }

  SaveWindowLocation();

  return true;
}

// Button actions
function AddOption()
{
  var optionElement = editorShell.CreateElementWithDefaults("option");
  var optionObject = currentObject.appendChild(optionElement);
  gDialog.tree.selectItem(optionObject.treeItem);
  gDialog.tree.ensureElementIsVisible(optionObject.treeItem);
  SetTextboxFocus(gDialog.optionText);
}

function AddOptGroup()
{
  var optgroupElement = editorShell.CreateElementWithDefaults("optgroup");
  var optgroupObject = gDialog.appendChild(optgroupElement);
  gDialog.tree.selectItem(optgroupObject.treeItem);
  gDialog.tree.ensureElementIsVisible(optgroupObject.treeItem);
  SetTextboxFocus(gDialog.optgroupLabel);
}

function RemoveElement()
{
  if (currentObject.canDestroy(true))
  {
    var selection = currentObject.treeItem;
    selection = selection.nextSibling || selection.previousSibling || selection.parentNode.parentNode;
    currentObject.destroy();
    gDialog.tree.selectItem(selection);
    gDialog.tree.focus();
  }
}

// Event handlers
function onTreeSelect(event)
{
  if (currentObject)
    currentObject.onBlur();
  var selection = gDialog.tree.selectedItems;
  if (selection.length)
  {
    currentObject = globalArray[parseInt(selection[0].getAttribute("index"))];
    currentObject.onFocus();
  }
  else
    currentObject = gDialog;
  gDialog.removeButton.disabled = !currentObject.canDestroy(false);
  gDialog.previousButton.disabled = !currentObject.canMoveUp();
  gDialog.nextButton.disabled = !currentObject.canMoveDown();
  globalElement = currentObject.element;
}

function onTreeKeyUp(event)
{
  if (event.keyCode == event.DOM_VK_SPACE)
    currentObject.onSpace();
}

function onTreeClicked(event)
{
  if (event.target.localName == "checkbox")
  {
    var selection = event.target.parentNode.parentNode.parentNode;
    gDialog.tree.selectItem(selection);
    currentObject = globalArray[parseInt(selection.getAttribute("index"))];
    currentObject.onSpace();
  }
  gDialog.tree.focus();
}

var timeout = 0;

function copyValue(textbox, treecell, delay)
{
  if (timeout) clearTimeout(timeout);
  if (delay)
    timeout = setTimeout(copyValue, 800, textbox, treecell, false);
  else {
    timeout = 0;
    treecell.setAttribute("label", textbox.value);
  }
}

function onNameInput()
{
  var disabled = !gDialog.selectName.value;
  if (gDialog.accept.disabled != disabled)
    gDialog.accept.disabled = disabled;
  copyValue(gDialog.selectName, gDialog.treeCell, true);
}

function onNameChange()
{
  copyValue(gDialog.selectName, gDialog.treeCell, false);
}

function onLabelInput()
{
  copyValue(gDialog.optgroupLabel, currentObject.treeCell, true);
}

function onLabelChange()
{
  copyValue(gDialog.optgroupLabel, currentObject.treeCell, false);
}

function copyText(delay)
{
  if (timeout) clearTimeout(timeout);
  if (delay)
    timeout = setTimeout(copyText, 800, false);
  else {
    timeout = 0;
    currentObject.treeCellText.setAttribute("label", gDialog.optionText.value);
    if (!hasValue)
    {
      gDialog.optionValue.value = gDialog.optionText.value;
      currentObject.treeCellValue.setAttribute("label", gDialog.optionText.value);
    }
  }
}

function onTextInput()
{
  copyText(true);
}

function onTextChange()
{
  copyText(false);
}

function onValueInput()
{
  gDialog.optionHasValue.checked = hasValue = true;
  oldValue = gDialog.optionValue.value;
  copyValue(gDialog.optionValue, currentObject.treeCellValue, true);
}

function onValueChange()
{
  copyValue(gDialog.optionValue, currentObject.treeCellValue, false);
}

function onHasValueClick()
{
  hasValue = gDialog.optionHasValue.checked;
  if (hasValue)
  {
    gDialog.optionValue.value = oldValue;
  }
  else
  {
    oldValue = gDialog.optionValue.value;
    gDialog.optionValue.value = gDialog.optionText.value;
  }
  currentObject.treeCellValue.setAttribute("label", gDialog.optionValue.value);
}

function onSelectMultipleClick()
{
  if (!gDialog.selectMultiple.checked && selectedOptionCount == 1 && !selectedOption)
    while (!globalArray[selectedOption] || !globalArray[selectedOption].element.hasAttribute("selected"))
      selectedOption++;
}
