/*
 * Copyright (C) 2008, 2009, 2010 Mihai Şucan
 *
 * This file is part of PaintWeb.
 *
 * PaintWeb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PaintWeb 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PaintWeb.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $URL: http://code.google.com/p/paintweb $
 * $Date: 2010-06-26 21:47:30 +0300 $
 */

/**
 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
 * @fileOverview The default PaintWeb interface code.
 */

/**
 * @class The default PaintWeb interface.
 *
 * @param {PaintWeb} app Reference to the main paint application object.
 */
pwlib.gui = function (app) {
  var _self     = this,
      config    = app.config,
      doc       = app.doc,
      lang      = app.lang,
      MathRound = Math.round,
      pwlib     = window.pwlib,
      appEvent  = pwlib.appEvent,
      win       = app.win;

  this.app = app;
  this.idPrefix = 'paintweb' + app.UID + '_',
  this.classPrefix = 'paintweb_';

  /**
   * Holds references to DOM elements.
   * @type Object
   */
  this.elems = {};

  /**
   * Holds references to input elements associated to the PaintWeb configuration 
   * properties.
   * @type Object
   */
  this.inputs = {};

  /**
   * Holds references to DOM elements associated to configuration values.
   * @type Object
   */
  this.inputValues = {};

  /**
   * Holds references to DOM elements associated to color configuration 
   * properties.
   *
   * @type Object
   * @see pwlib.guiColorInput
   */
  this.colorInputs = {};

  /**
   * Holds references to DOM elements associated to each tool registered in the 
   * current PaintWeb application instance.
   *
   * @private
   * @type Object
   */
  this.tools = {};

  /**
   * Holds references to DOM elements associated to PaintWeb commands.
   *
   * @private
   * @type Object
   */
  this.commands = {};

  /**
   * Holds references to floating panels GUI components.
   *
   * @type Object
   * @see pwlib.guiFloatingPanel
   */
  this.floatingPanels = {zIndex_: 0};

  /**
   * Holds references to tab panel GUI components.
   *
   * @type Object
   * @see pwlib.guiTabPanel
   */
  this.tabPanels = {};

  /**
   * Holds an instance of the guiResizer object attached to the Canvas.
   *
   * @private
   * @type pwlib.guiResizer
   */
  this.canvasResizer = null;

  /**
   * Holds an instance of the guiResizer object attached to the viewport 
   * element.
   *
   * @private
   * @type pwlib.guiResizer
   */
  this.viewportResizer = null;

  /**
   * Holds tab configuration information for most drawing tools.
   *
   * @private
   * @type Object
   */
  this.toolTabConfig = {
    bcurve: {
      lineTab: true,
      shapeType: true,
      lineWidth: true,
      lineWidthLabel: lang.inputs.borderWidth,
      lineCap: true
    },
    ellipse: {
      lineTab: true,
      shapeType: true,
      lineWidth: true,
      lineWidthLabel: lang.inputs.borderWidth
    },
    rectangle: {
      lineTab: true,
      shapeType: true,
      lineWidth: true,
      lineWidthLabel: null,
      lineJoin: true
    },
    polygon: {
      lineTab: true,
      shapeType: true,
      lineWidth: true,
      lineWidthLabel: lang.inputs.borderWidth,
      lineJoin: true,
      lineCap: true,
      miterLimit: true
    },
    eraser: {
      lineTab: true,
      lineWidth: true,
      lineWidthLabel: lang.inputs.eraserSize,
      lineJoin: true,
      lineCap: true,
      miterLimit: true
    },
    pencil: {
      lineTab: true,
      lineWidth: true,
      lineWidthLabel: lang.inputs.pencilSize,
      lineJoin: true,
      lineCap: true,
      miterLimit: true
    },
    line: {
      lineTab: true,
      lineWidth: true,
      lineWidthLabel: lang.inputs.line.lineWidth,
      lineJoin: true,
      lineCap: true,
      miterLimit: true
    },
    text: {
      lineTab: false,
      lineTabLabel: lang.tabs.main.textBorder,
      shapeType: true,
      lineWidth: true,
      lineWidthLabel: lang.inputs.borderWidth
    }
  };

  /**
   * Initialize the PaintWeb interface.
   *
   * @param {Document|String} markup The interface markup loaded and parsed as 
   * DOM Document object. Optionally, the value can be a string holding the 
   * interface markup (this is used when PaintWeb is packaged).
   *
   * @returns {Boolean} True if the initialization was successful, or false if 
   * not.
   */
  this.init = function (markup) {
    // Make sure the user nicely waits for PaintWeb to load, without seeing 
    // much.
    var placeholder = config.guiPlaceholder,
        placeholderStyle = placeholder.style;


    placeholderStyle.display = 'none';
    placeholderStyle.height = '1px';
    placeholderStyle.overflow = 'hidden';
    placeholderStyle.position = 'absolute';
    placeholderStyle.visibility = 'hidden';

    placeholder.className += ' ' + this.classPrefix + 'placeholder';
    if (!placeholder.tabIndex || placeholder.tabIndex == -1) {
      placeholder.tabIndex = 1;
    }

    if (!this.initImportDoc(markup)) {
      app.initError(lang.guiMarkupImportFailed);
      return false;
    }
    markup = null;

    if (!this.initParseMarkup()) {
      app.initError(lang.guiMarkupParseFailed);
      return false;
    }

    if (!this.initCanvas() ||
        !this.initImageZoom() ||
        //!this.initSelectionTool() ||
        //!this.initTextTool() ||
        !this.initKeyboardShortcuts()) {
      return false;
    }

    // Setup the main tabbed panel.
    var panel = this.tabPanels.main;
    if (!panel) {
      app.initError(lang.noMainTabPanel);
      return false;
    }

    // Hide the "Shadow" tab if the drawing of shadows is not supported.
    if (!app.shadowSupported && 'shadow' in panel.tabs) {
      panel.tabHide('shadow');
    }

    if (!('viewport' in this.elems)) {
      app.initError(lang.missingViewport);
      return false;
    }

    // Setup the GUI dimensions .
    this.elems.viewport.style.height = config.viewportHeight;
    placeholderStyle.width = config.viewportWidth;

    // Setup the Canvas resizer.
    var resizeHandle = this.elems.canvasResizer;
    if (!resizeHandle) {
      app.initError(lang.missingCanvasResizer);
      return false;
    }
    resizeHandle.title = lang.guiCanvasResizer;
    resizeHandle.replaceChild(doc.createTextNode(resizeHandle.title), 
        resizeHandle.firstChild);
    resizeHandle.addEventListener('mouseover', this.item_mouseover, false);
    resizeHandle.addEventListener('mouseout',  this.item_mouseout,  false);

    this.canvasResizer = new pwlib.guiResizer(this, resizeHandle, 
        this.elems.canvasContainer);

    this.canvasResizer.events.add('guiResizeStart', this.canvasResizeStart);
    this.canvasResizer.events.add('guiResizeEnd',   this.canvasResizeEnd);

    // Setup the viewport resizer.
    var resizeHandle = this.elems.viewportResizer;
    if (!resizeHandle) {
      app.initError(lang.missingViewportResizer);
      return false;
    }
    resizeHandle.title = lang.guiViewportResizer;
    resizeHandle.replaceChild(doc.createTextNode(resizeHandle.title), 
        resizeHandle.firstChild);
    resizeHandle.addEventListener('mouseover', this.item_mouseover, false);
    resizeHandle.addEventListener('mouseout',  this.item_mouseout,  false);

    this.viewportResizer = new pwlib.guiResizer(this, resizeHandle, 
        this.elems.viewport);

    this.viewportResizer.dispatchMouseMove = true;
    this.viewportResizer.events.add('guiResizeMouseMove', 
        this.viewportResizeMouseMove);
    this.viewportResizer.events.add('guiResizeEnd', this.viewportResizeEnd);

    if ('statusMessage' in this.elems) {
      this.elems.statusMessage._prevText = false;
    }

    // Update the version string in Help.
    if ('version' in this.elems) {
      this.elems.version.appendChild(doc.createTextNode(app.toString()));
    }

    // Update the image dimensions in the GUI.
    var imageSize = this.elems.imageSize;
    if (imageSize) {
      imageSize.replaceChild(doc.createTextNode(app.image.width + 'x' 
            + app.image.height), imageSize.firstChild);
    }

    // Add application-wide event listeners.
    app.events.add('canvasSizeChange',  this.canvasSizeChange);
    app.events.add('commandRegister',   this.commandRegister);
    app.events.add('commandUnregister', this.commandUnregister);
    app.events.add('configChange',      this.configChangeHandler);
    app.events.add('imageSizeChange',   this.imageSizeChange);
    app.events.add('imageZoom',         this.imageZoom);
    app.events.add('appInit',           this.appInit);
    app.events.add('shadowAllow',       this.shadowAllow);
    app.events.add('toolActivate',      this.toolActivate);
    app.events.add('toolRegister',      this.toolRegister);
    app.events.add('toolUnregister',    this.toolUnregister);

    // Make sure the historyUndo and historyRedo command elements are 
    // synchronized with the application history state.
    if ('historyUndo' in this.commands && 'historyRedo' in this.commands) {
      app.events.add('historyUpdate', this.historyUpdate);
    }

    app.commandRegister('about', this.commandAbout);

    return true;
  };

  /**
   * Initialize the Canvas elements.
   *
   * @private
   * @returns {Boolean} True if the initialization was successful, or false if 
   * not.
   */
  this.initCanvas = function () {
    var canvasContainer = this.elems.canvasContainer,
        layerCanvas     = app.layer.canvas,
        layerContext    = app.layer.context,
        layerStyle      = layerCanvas.style,
        bufferCanvas    = app.buffer.canvas,
        orginlCanvas    = app.orginl.canvas;

    if (!canvasContainer) {
      app.initError(lang.missingCanvasContainer);
      return false;
    }

    var containerStyle  = canvasContainer.style;

    canvasContainer.className = this.classPrefix + 'canvasContainer';
    orginlCanvas.className     = this.classPrefix + 'orginlCanvas';
    layerCanvas.className     = this.classPrefix + 'layerCanvas';
    bufferCanvas.className    = this.classPrefix + 'bufferCanvas';

    containerStyle.width  = layerStyle.width;
    containerStyle.height = layerStyle.height;
    if (!config.checkersBackground || pwlib.browser.olpcxo) {
      containerStyle.backgroundImage = 'none';
    }

    canvasContainer.appendChild(orginlCanvas);
    canvasContainer.appendChild(layerCanvas);
    canvasContainer.appendChild(bufferCanvas);

    // Make sure the selection transparency input checkbox is disabled if the 
    // putImageData and getImageData methods are unsupported.
    if ('selection_transparent' in this.inputs && (!layerContext.putImageData || 
          !layerContext.getImageData)) {
      this.inputs.selection_transparent.disabled = true;
      this.inputs.selection_transparent.checked = true;
    }

    return true;
  };

  /**
   * Import the DOM nodes from the interface DOM document. All the nodes are 
   * inserted into the {@link PaintWeb.config.guiPlaceholder} element.
   *
   * <p>Elements which have the ID attribute will have the attribute renamed to 
   * <code>data-pwId</code>.
   *
   * <p>Input elements which have the ID attribute will have their attribute 
   * updated to be unique for the current PaintWeb instance.
   *
   * @private
   *
   * @param {Document|String} markup The source DOM document to import the nodes 
   * from. Optionally, this parameter can be a string which holds the interface 
   * markup.
   *
   * @returns {Boolean} True if the initialization was successful, or false if 
   * not.
   */
  this.initImportDoc = function (markup) {
    // I could use some XPath here, but for the sake of compatibility I don't.
    var destElem = config.guiPlaceholder,
        elType = app.ELEMENT_NODE,
        elem, root, nodes, n, tag, isInput;

    if (typeof markup === 'string') {
      elem = doc.createElement('div');
      elem.innerHTML = markup;
      root = elem.firstChild;
    } else {
      root = markup.documentElement;
    }
    markup = null;

    nodes = root.getElementsByTagName('*');
    n = nodes.length;

    // Change all the id attributes to be data-pwId attributes.
    // Input elements have their ID updated to be unique for the current 
    // PaintWeb instance.
    for (var i = 0; i < n; i++) {
      elem = nodes[i];
      if (elem.nodeType !== elType || !elem.tagName) {
        continue;
      }
      tag = elem.tagName.toLowerCase();
      isInput = tag === 'input' || tag === 'select' || tag === 'textarea';

      if (elem.id) {
        elem.setAttribute('data-pwId', elem.id);

        if (isInput) {
          elem.id = this.idPrefix + elem.id;
        } else {
          elem.removeAttribute('id');
        }
      }

      // label elements have their "for" attribute updated as well.
      if (tag === 'label' && elem.htmlFor) {
        elem.htmlFor = this.idPrefix + elem.htmlFor;
      }
    }

    // Import all the nodes.
    n = root.childNodes.length;

    for (var i = 0; i < n; i++) {
      destElem.appendChild(doc.importNode(root.childNodes[i], true));
    }

    return true;
  };

  /**
   * Parse the interface markup. The layout file can have custom 
   * PaintWeb-specific attributes.
   *
   * <p>Elements with the <code>data-pwId</code> attribute are added to the 
   * {@link pwlib.gui#elems} object.
   *
   * <p>Elements having the <code>data-pwCommand</code> attribute are added to 
   * the {@link pwlib.gui#commands} object.
   *
   * <p>Elements having the <code>data-pwTool</code> attribute are added to the 
   * {@link pwlib.gui#tools} object.
   *
   * <p>Elements having the <code>data-pwTabPanel</code> attribute are added to 
   * the {@link pwlib.gui#tabPanels} object. These become interactive GUI 
   * components (see {@link pwlib.guiTabPanel}).
   *
   * <p>Elements having the <code>data-pwFloatingPanel</code> attribute are 
   * added to the {@link pwlib.gui#floatingPanels} object. These become 
   * interactive GUI components (see {@link pwlib.guiFloatingPanel}).
   *
   * <p>Elements having the <code>data-pwConfig</code> attribute are added to 
   * the {@link pwlib.gui#inputs} object. These become interactive GUI 
   * components which allow users to change configuration options.
   *
   * <p>Elements having the <code>data-pwConfigValue</code> attribute are added 
   * to the {@link pwlib.gui#inputValues} object. These can only be child nodes 
   * of elements which have the <code>data-pwConfig</code> attribute. Each such 
   * element is considered an icon. Anchor elements are appended to ensure 
   * keyboard accessibility.
   *
   * <p>Elements having the <code>data-pwConfigToggle</code> attribute are added 
   * to the {@link pwlib.gui#inputs} object. These become interactive GUI 
   * components which toggle the boolean value of the configuration property 
   * they are associated to.
   *
   * <p>Elements having the <code>data-pwColorInput</code> attribute are added 
   * to the {@link pwlib.gui#colorInputs} object. These become color picker 
   * inputs which are associated to the configuration property given as the 
   * attribute value. (see {@link pwlib.guiColorInput})
   *
   * @returns {Boolean} True if the parsing was successful, or false if not.
   */
  this.initParseMarkup = function () {
    var nodes = config.guiPlaceholder.getElementsByTagName('*'),
        elType = app.ELEMENT_NODE,
        elem, tag, isInput, tool, tabPanel, floatingPanel, cmd, id, cfgAttr, 
        colorInput;

    // Store references to important elements and parse PaintWeb-specific 
    // attributes.
    for (var i = 0; i < nodes.length; i++) {
      elem = nodes[i];
      if (elem.nodeType !== elType) {
        continue;
      }
      tag = elem.tagName.toLowerCase();
      isInput = tag === 'input' || tag === 'select' || tag === 'textarea';

      // Store references to commands.
      cmd = elem.getAttribute('data-pwCommand');
      if (cmd && !(cmd in this.commands)) {
        elem.className += ' ' + this.classPrefix + 'command';
        this.commands[cmd] = elem;
      }

      // Store references to tools.
      tool = elem.getAttribute('data-pwTool');
      if (tool && !(tool in this.tools)) {
        elem.className += ' ' + this.classPrefix + 'tool';
        this.tools[tool] = elem;
      }

      // Create tab panels.
      tabPanel = elem.getAttribute('data-pwTabPanel');
      if (tabPanel) {
        this.tabPanels[tabPanel] = new pwlib.guiTabPanel(this, elem);
      }

      // Create floating panels.
      floatingPanel = elem.getAttribute('data-pwFloatingPanel');
      if (floatingPanel) {
        this.floatingPanels[floatingPanel] = new pwlib.guiFloatingPanel(this, 
            elem);
      }

      cfgAttr = elem.getAttribute('data-pwConfig');
      if (cfgAttr) {
        if (isInput) {
          this.initConfigInput(elem, cfgAttr);
        } else {
          this.initConfigIcons(elem, cfgAttr);
        }
      }

      cfgAttr = elem.getAttribute('data-pwConfigToggle');
      if (cfgAttr) {
        this.initConfigToggle(elem, cfgAttr);
      }

      cfgAttr = elem.getAttribute('data-pwConfigButton');
      if (cfgAttr) {
        this.initConfigToggle(elem, cfgAttr, 'button');
      }

      // elem.hasAttribute() fails in webkit (tested with chrome and safari 4)
      if (elem.getAttribute('data-pwColorInput')) {
        colorInput = new pwlib.guiColorInput(this, elem);
        this.colorInputs[colorInput.id] = colorInput;
      }

      id = elem.getAttribute('data-pwId');
      if (id) {
        elem.className += ' ' + this.classPrefix + id;

        // Store a reference to the element.
        if (isInput && !cfgAttr) {
          this.inputs[id] = elem;
        } else if (!isInput) {
          this.elems[id] = elem;
        }
      }
    }

    return true;
  };

  /**
   * Initialize an input element associated to a configuration property.
   *
   * @private
   *
   * @param {Element} elem The DOM element which is associated to the 
   * configuration property.
   *
   * @param {String} cfgAttr The configuration attribute. This tells the 
   * configuration group and property to which the DOM element is attached to.
   */
  this.initConfigInput = function (input, cfgAttr) {
    var cfgNoDots   = cfgAttr.replace('.', '_'),
        cfgArray    = cfgAttr.split('.'),
        cfgProp     = cfgArray.pop(),
        cfgGroup    = cfgArray.join('.'),
        cfgGroupRef = config,
        langGroup   = lang.inputs,
        labelElem   = input.parentNode;

    for (var i = 0, n = cfgArray.length; i < n; i++) {
      cfgGroupRef = cfgGroupRef[cfgArray[i]];
      langGroup = langGroup[cfgArray[i]];
    }

    input._pwConfigProperty = cfgProp;
    input._pwConfigGroup = cfgGroup;
    input._pwConfigGroupRef = cfgGroupRef;
    input.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp];
    input.className += ' ' + this.classPrefix + 'cfg_' + cfgNoDots;

    this.inputs[cfgNoDots] = input;

    if (labelElem.tagName.toLowerCase() !== 'label') {
      labelElem = labelElem.getElementsByTagName('label')[0];
    }

    if (input.type === 'checkbox' || labelElem.htmlFor) {
      labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]), 
          labelElem.lastChild);
    } else {
      labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]), 
          labelElem.firstChild);
    }

    if (input.type === 'checkbox') {
      input.checked = cfgGroupRef[cfgProp];
    } else {
      input.value = cfgGroupRef[cfgProp];
    }

    input.addEventListener('input',  this.configInputChange, false);
    input.addEventListener('change', this.configInputChange, false);
  };

  /**
   * Initialize an HTML element associated to a configuration property, and all 
   * of its own sub-elements associated to configuration values. Each element 
   * that has the <var>data-pwConfigValue</var> attribute is considered an icon.
   *
   * @private
   *
   * @param {Element} elem The DOM element which is associated to the 
   * configuration property.
   *
   * @param {String} cfgAttr The configuration attribute. This tells the 
   * configuration group and property to which the DOM element is attached to.
   */
  this.initConfigIcons = function (input, cfgAttr) {
    var cfgNoDots   = cfgAttr.replace('.', '_'),
        cfgArray    = cfgAttr.split('.'),
        cfgProp     = cfgArray.pop(),
        cfgGroup    = cfgArray.join('.'),
        cfgGroupRef = config,
        langGroup   = lang.inputs;

    for (var i = 0, n = cfgArray.length; i < n; i++) {
      cfgGroupRef = cfgGroupRef[cfgArray[i]];
      langGroup = langGroup[cfgArray[i]];
    }

    input._pwConfigProperty = cfgProp;
    input._pwConfigGroup = cfgGroup;
    input._pwConfigGroupRef = cfgGroupRef;
    input.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp];
    input.className += ' ' + this.classPrefix + 'cfg_' + cfgNoDots;

    this.inputs[cfgNoDots] = input;

    var labelElem = input.getElementsByTagName('p')[0];
    labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]), 
        labelElem.firstChild);

    var elem, anchor, val,
        className = ' ' + this.classPrefix + 'configActive';
        nodes = input.getElementsByTagName('*'),
        elType = app.ELEMENT_NODE;

    for (var i = 0; i < nodes.length; i++) {
      elem = nodes[i];
      if (elem.nodeType !== elType) {
        continue;
      }

      val = elem.getAttribute('data-pwConfigValue');
      if (!val) {
        continue;
      }

      anchor = doc.createElement('a');
      anchor.href = '#';
      anchor.title = langGroup[cfgProp + '_' + val];
      anchor.appendChild(doc.createTextNode(anchor.title));

      elem.className += ' ' + this.classPrefix + cfgProp + '_' + val 
        + ' ' + this.classPrefix + 'icon';
      elem._pwConfigParent = input;

      if (cfgGroupRef[cfgProp] == val) {
        elem.className += className;
      }

      anchor.addEventListener('click',     this.configValueClick, false);
      anchor.addEventListener('mouseover', this.item_mouseover,   false);
      anchor.addEventListener('mouseout',  this.item_mouseout,    false);

      elem.replaceChild(anchor, elem.firstChild);

      this.inputValues[cfgGroup + '_' + cfgProp + '_' + val] = elem;
    }
  };

  /**
   * Initialize an HTML element associated to a boolean configuration property.
   *
   * @private
   *
   * @param {Element} elem The DOM element which is associated to the 
   * configuration property.
   *
   * @param {String} cfgAttr The configuration attribute. This tells the 
   * configuration group and property to which the DOM element is attached to.
   */
  this.initConfigToggle = function (input, cfgAttr, isButton) {
    var cfgNoDots   = cfgAttr.replace('.', '_'),
        cfgArray    = cfgAttr.split('.'),
        cfgProp     = cfgArray.pop(),
        cfgGroup    = cfgArray.join('.'),
        cfgGroupRef = config,
        langGroup   = lang.inputs;

    for (var i = 0, n = cfgArray.length; i < n; i++) {
      cfgGroupRef = cfgGroupRef[cfgArray[i]];
      langGroup = langGroup[cfgArray[i]];
    }

    input._pwConfigProperty = cfgProp;
    input._pwConfigGroup = cfgGroup;
    input._pwConfigGroupRef = cfgGroupRef;
    input.className += ' ' + this.classPrefix + 'cfg_' + cfgNoDots 
      + ' ' + this.classPrefix + 'icon';

    if (cfgGroupRef)
    	if (cfgGroupRef[cfgProp]) {
    		input.className += ' ' + this.classPrefix + 'configActive';
    	}

    var anchor = doc.createElement('a');
    anchor.href = '#';
    anchor.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp];
    anchor.appendChild(doc.createTextNode(langGroup[cfgProp]));

    if (isButton)
         anchor.addEventListener('click', this.configButtonClick, false);
    else anchor.addEventListener('click', this.configToggleClick, false);
    anchor.addEventListener('mouseover', this.item_mouseover,    false);
    anchor.addEventListener('mouseout',  this.item_mouseout,     false);

    input.replaceChild(anchor, input.firstChild);

    this.inputs[cfgNoDots] = input;
  };

  /**
   * Initialize the image zoom input.
   *
   * @private
   * @returns {Boolean} True if the initialization was successful, or false if 
   * not.
   */
  this.initImageZoom = function () {
    var input = this.inputs.imageZoom;
    if (!input) {
      return true; // allow layouts without the zoom input
    }

    //---------------------------------------------------------------------------------------
    // FIXME : image size 조정값 zoom에 set
    //---------------------------------------------------------------------------------------
    console.log('initImageZoom>>>>>', app.image.width, app.image.height, app.image.zoom);
    input.value = app.image.zoom * 100;
    input._old_value = 100;
    //---------------------------------------------------------------------------------------
    //input.value = 100;
    //input._old_value = 100;



    // Override the attributes, based on the settings.
    input.setAttribute('step', config.imageZoomStep * 100);
    input.setAttribute('max',  config.imageZoomMax  * 100);
    input.setAttribute('min',  config.imageZoomMin  * 100);

    var changeFn = function () {
        app.imageZoomTo(parseInt(this.value) / 100);
    };

    input.addEventListener('change', changeFn, false);
    input.addEventListener('input',  changeFn, false);

    // Update some language strings
    var label = input.parentNode;
    if (label.tagName.toLowerCase() === 'label') {
      label.replaceChild(doc.createTextNode(lang.imageZoomLabel), 
          label.firstChild);
    }

    var elem = this.elems.statusZoom;
    if (!elem) {
      return true;
    }

    elem.title = lang.imageZoomTitle;

    return true;
  };

  /**
   * Initialize GUI elements associated to selection tool options and commands.
   *
   * @private
   * @returns {Boolean} True if the initialization was successful, or false if 
   * not.
   */
  this.initSelectionTool = function () {
    var classDisabled = ' ' + this.classPrefix + 'disabled',
        cut   = this.commands.selectionCut,
        copy  = this.commands.selectionCopy,
        paste = this.commands.clipboardPaste;

    if (paste) {
      app.events.add('clipboardUpdate', this.clipboardUpdate);
      paste.className += classDisabled;

    }

    if (cut && copy) {
      app.events.add('selectionChange', this.selectionChange);
      cut.className  += classDisabled;
      copy.className += classDisabled;
    }

    var selTab_cmds = ['selectionCut', 'selectionCopy', 'clipboardPaste'],
        anchor, elem, cmd;

    for (var i = 0, n = selTab_cmds.length; i < n; i++) {
      cmd = selTab_cmds[i];
      elem = this.elems['selTab_' + cmd];
      if (!elem) {
        continue;
      }

      anchor = doc.createElement('a');
      anchor.title = lang.commands[cmd];
      anchor.href = '#';
      anchor.appendChild(doc.createTextNode(anchor.title));
      anchor.addEventListener('click', this.commandClick, false);

      elem.className += classDisabled + ' ' + this.classPrefix + 'command' 
        + ' ' + this.classPrefix + 'cmd_' + cmd;
      elem.setAttribute('data-pwCommand', cmd);
      elem.replaceChild(anchor, elem.firstChild);
    }

    var selCrop   = this.commands.selectionCrop,
        selFill   = this.commands.selectionFill,
		selForNumPlt   = this.commands.selectionForNumberPlate,
        selDelete = this.commands.selectionDelete;

    selCrop.className   += classDisabled;
    selFill.className   += classDisabled;
	selForNumPlt.className   += classDisabled;
    selDelete.className += classDisabled;

    return true;
  };

  /**
   * Initialize GUI elements associated to text tool options.
   *
   * @private
   * @returns {Boolean} True if the initialization was successful, or false if 
   * not.
   */
  this.initTextTool = function () {
    /*if ('textString' in this.inputs) {
      this.inputs.textString.value = lang.inputs.text.textString_value;
    }*/

    if (!('text_fontFamily' in this.inputs) || !('text' in config) || 
        !('fontFamilies' in config.text)) {
      return true;
    }

    var option, input = this.inputs.text_fontFamily;
    for (var i = 0, n = config.text.fontFamilies.length; i < n; i++) {
      option = doc.createElement('option');
      option.value = config.text.fontFamilies[i];
      option.appendChild(doc.createTextNode(option.value));
      input.appendChild(option);

      if (option.value === config.text.fontFamily) {
        input.selectedIndex = i;
        input.value = option.value;
      }
    }

    option = doc.createElement('option');
    option.value = '+';
    option.appendChild(doc.createTextNode(lang.inputs.text.fontFamily_add));
    input.appendChild(option);

    var sizeArr = ["8", "10", "12", "14", "18", "24"]; 
    input = this.inputs.fontSize;
    for (var i = 0; i < sizeArr.length; i++) {
      option = doc.createElement('option');
      option.value = i+1;
      option.appendChild(doc.createTextNode(sizeArr[i]+"pt"));
      input.appendChild(option);
    }
    input.selectedIndex = 3;
    
    return true;
  };

  /**
   * Initialize the keyboard shortcuts. Basically, this updates various strings 
   * to ensure the user interface is informational.
   *
   * @private
   * @returns {Boolean} True if the initialization was successful, or false if 
   * not.
   */
  this.initKeyboardShortcuts = function () {
    var kid = null, kobj = null;

    for (kid in config.keys) {
      kobj = config.keys[kid];

      if ('toolActivate' in kobj && kobj.toolActivate in lang.tools) {
        lang.tools[kobj.toolActivate] += ' [ ' + kid + ' ]';
      }

      if ('command' in kobj && kobj.command in lang.commands) {
        lang.commands[kobj.command] += ' [ ' + kid + ' ]';
      }
    }

    return true;
  };

  /**
   * The <code>appInit</code> event handler. This method is invoked once 
   * PaintWeb completes all the loading.
   *
   * <p>This method dispatches the {@link pwlib.appEvent.guiShow} application 
   * event.
   *
   * @private
   * @param {pwlib.appEvent.appInit} ev The application event object.
   */
  this.appInit = function (ev) {
    // Initialization was not successful ...
    if (ev.state !== PaintWeb.INIT_DONE) {
      return;
    }

    // Make sure the Hand tool is enabled/disabled as needed.
    if ('hand' in _self.tools) {
      app.events.add('canvasSizeChange',   _self.toolHandStateChange);
      app.events.add('viewportSizeChange', _self.toolHandStateChange);
      _self.toolHandStateChange(ev);
    }

    // Make PaintWeb visible.
    var placeholder = config.guiPlaceholder,
        placeholderStyle = placeholder.style;

    // We do not reset the display property. We leave this for the stylesheet.
    placeholderStyle.height = '';
    placeholderStyle.overflow = '';
    placeholderStyle.position = '';
    placeholderStyle.visibility = '';

    var cs = win.getComputedStyle(placeholder, null);

    // Do not allow the static positioning for the PaintWeb placeholder.  
    // Usually, the GUI requires absolute/relative positioning.
    if (cs.position === 'static') {
      placeholderStyle.position = 'relative';
    }

    try {
      placeholder.focus();
    } catch (err) { }


	this.toolActivate("selection"); //영역 선택
	
    app.events.dispatch(new appEvent.guiShow());
  };

  /**
   * The <code>guiResizeStart</code> event handler for the Canvas resize 
   * operation.
   * @private
   */
  this.canvasResizeStart = function () {
    this.resizeHandle.style.visibility = 'hidden';

    // ugly...
    this.timeout_ = setTimeout(function () {
      _self.statusShow('guiCanvasResizerActive', true);
      clearTimeout(_self.canvasResizer.timeout_);
      delete _self.canvasResizer.timeout_;
    }, 400);
  };

  /**
   * The <code>guiResizeEnd</code> event handler for the Canvas resize 
   * operation.
   *
   * @private
   * @param {pwlib.appEvent.guiResizeEnd} ev The application event object.
   */
  this.canvasResizeEnd = function (ev) {
    this.resizeHandle.style.visibility = '';

    app.imageCrop(0, 0, MathRound(ev.width / app.image.canvasScale),
        MathRound(ev.height / app.image.canvasScale));

    if (this.timeout_) {
      clearTimeout(this.timeout_);
      delete this.timeout_;
    } else {
      _self.statusShow(-1);
    }
  };

  /**
   * The <code>guiResizeMouseMove</code> event handler for the viewport resize 
   * operation.
   *
   * @private
   * @param {pwlib.appEvent.guiResizeMouseMove} ev The application event object.
   */
  this.viewportResizeMouseMove = function (ev) {
    config.guiPlaceholder.style.width = ev.width + 'px';
  };

  /**
   * The <code>guiResizeEnd</code> event handler for the viewport resize 
   * operation.
   *
   * @private
   * @param {pwlib.appEvent.guiResizeEnd} ev The application event object.
   */
  this.viewportResizeEnd = function (ev) {
    _self.elems.viewport.style.width = '';
    _self.resizeTo(ev.width + 'px', ev.height + 'px');
    try {
      config.guiPlaceholder.focus();
    } catch (err) { }
  };

  /**
   * The <code>mouseover</code> event handler for all tools, commands and icons.  
   * This simply shows the title / text content of the element in the GUI status 
   * bar.
   *
   * @see pwlib.gui#statusShow The method used for displaying the message in the 
   * GUI status bar.
   */
  this.item_mouseover = function () {
    if (this.title || this.textConent) {
      _self.statusShow(this.title || this.textContent, true);
    }
  };

  /**
   * The <code>mouseout</code> event handler for all tools, commands and icons.  
   * This method simply resets the GUI status bar to the previous message it was 
   * displaying before the user hovered the current element.
   *
   * @see pwlib.gui#statusShow The method used for displaying the message in the 
   * GUI status bar.
   */
  this.item_mouseout = function () {
    _self.statusShow(-1);
  };

  /**
   * Show a message in the status bar.
   *
   * @param {String|Number} msg The message ID you want to display. The ID 
   * should be available in the {@link PaintWeb.lang.status} object. If the 
   * value is -1 then the previous non-temporary message will be displayed. If 
   * the ID is not available in the language file, then the string is shown 
   * as-is.
   *
   * @param {Boolean} [temporary=false] Tells if the message is temporary or 
   * not.
   */
  this.statusShow = function (msg, temporary) {
    var elem = this.elems.statusMessage;
    if (msg === -1 && elem._prevText === false) {
      return false;
    }

    if (msg === -1) {
      msg = elem._prevText;
    }

    if (msg in lang.status) {
      msg = lang.status[msg];
    }

    if (!temporary) {
      elem._prevText = msg;
    }

    if (elem.firstChild) {
      elem.removeChild(elem.firstChild);
    }

    win.status = msg;

    if (msg) {
      elem.appendChild(doc.createTextNode(msg));
    }
  };

  /**
   * The "About" command. This method displays the "About" panel.
   */
  this.commandAbout = function () {
    _self.floatingPanels.about.toggle();
  };

  /**
   * The <code>click</code> event handler for the tool DOM elements.
   *
   * @private
   *
   * @param {Event} ev The DOM Event object.
   *
   * @see PaintWeb#toolActivate to activate a drawing tool.
   */
  this.toolClick = function (ev) {
    app.toolActivate(this.parentNode.getAttribute('data-pwTool'), ev);
    ev.preventDefault();
  };

  /**
   * The <code>toolActivate</code> application event handler. This method 
   * provides visual feedback for the activation of a new drawing tool.
   *
   * @private
   *
   * @param {pwlib.appEvent.toolActivate} ev The application event object.
   *
   * @see PaintWeb#toolActivate the method which allows you to activate 
   * a drawing tool.
   */
  this.toolActivate = function (ev) {
    var tabAnchor,
        tabActive = _self.tools[ev.id],
        tabConfig = _self.toolTabConfig[ev.id] || {},
        tabPanel = _self.tabPanels.main,
        lineTab = tabPanel.tabs.line,
        shapeType = _self.inputs.shapeType,
        lineWidth = _self.inputs.line_lineWidth,
        lineCap = _self.inputs.line_lineCap,
        lineJoin = _self.inputs.line_lineJoin,
        miterLimit = _self.inputs.line_miterLimit,
        lineWidthLabel = null;

    tabActive.className += ' ' + _self.classPrefix + 'toolActive';
    try {
      tabActive.firstChild.focus();
    } catch (err) { }

    if ((ev.id + 'Active') in lang.status) {
      _self.statusShow(ev.id + 'Active');
    }

    // show/hide the shapeType input config.
    if (shapeType) {
      if (tabConfig.shapeType) {
        shapeType.style.display = '';
      } else {
        shapeType.style.display = 'none';
      }
    }

    if (ev.prevId) {
      var prevTab = _self.tools[ev.prevId],
          prevTabConfig = _self.toolTabConfig[ev.prevId] || {};

      prevTab.className = prevTab.className.
        replace(' ' + _self.classPrefix + 'toolActive', '');

      // hide the line tab
      if (prevTabConfig.lineTab && lineTab) {
        tabPanel.tabHide('line');
        lineTab.container.className = lineTab.container.className.
          replace(' ' + _self.classPrefix + 'main_line_' + ev.prevId, 
              ' ' + _self.classPrefix + 'main_line');
      }

      // hide the tab for the current tool.
      if (ev.prevId in tabPanel.tabs) {
        tabPanel.tabHide(ev.prevId);
      }
    }

    // Change the label of the lineWidth input element.
    if (tabConfig.lineWidthLabel) {
      lineWidthLabel = lineWidth.parentNode;
      lineWidthLabel.replaceChild(doc.createTextNode(tabConfig.lineWidthLabel), 
          lineWidthLabel.firstChild);

    }

    if (lineJoin) {
      if (tabConfig.lineJoin) {
        lineJoin.style.display = '';
      } else {
        lineJoin.style.display = 'none';
      }
    }

    if (lineCap) {
      if (tabConfig.lineCap) {
        lineCap.style.display = '';
      } else {
        lineCap.style.display = 'none';
      }
    }

    if (miterLimit) {
      if (tabConfig.miterLimit) {
        miterLimit.parentNode.parentNode.style.display = '';
      } else {
        miterLimit.parentNode.parentNode.style.display = 'none';
      }
    }

    if (lineWidth) {
      if (tabConfig.lineWidth) {
        lineWidth.parentNode.parentNode.style.display = '';
      } else {
        lineWidth.parentNode.parentNode.style.display = 'none';
      }
    }

    // show the line tab, if configured
    if (tabConfig.lineTab && 'line' in tabPanel.tabs) {
      tabAnchor = lineTab.button.firstChild;
      tabAnchor.title = tabConfig.lineTabLabel || lang.tabs.main[ev.id];
      tabAnchor.replaceChild(doc.createTextNode(tabAnchor.title), 
          tabAnchor.firstChild);

      if (ev.id !== 'line') {
        lineTab.container.className = lineTab.container.className.
            replace(' ' + _self.classPrefix + 'main_line', ' ' + _self.classPrefix 
                + 'main_line_' + ev.id);
      }

      tabPanel.tabShow('line');
    }

    // show the tab for the current tool, if there's one.
    if (ev.id in tabPanel.tabs) {
      tabPanel.tabShow(ev.id);
    }
  };

  /**
   * The <code>toolRegister</code> application event handler. This method adds 
   * the new tool into the GUI.
   *
   * @private
   *
   * @param {pwlib.appEvent.toolRegister} ev The application event object.
   *
   * @see PaintWeb#toolRegister the method which allows you to register new 
   * tools.
   */
  this.toolRegister = function (ev) {
    var attr = null, elem = null, anchor = null;

    if (ev.id in _self.tools) {
      elem = _self.tools[ev.id];
      attr = elem.getAttribute('data-pwTool');
      if (attr && attr !== ev.id) {
        attr = null;
        elem = null;
        delete _self.tools[ev.id];
      }
    }

    // Create a new element if there's none already associated to the tool ID.
    if (!elem) {
      elem = doc.createElement('li');
    }

    if (!attr) {
      elem.setAttribute('data-pwTool', ev.id);
    }

    elem.className += ' ' + _self.classPrefix + 'tool_' + ev.id;

    // Append an anchor element which holds the locale string.
    anchor = doc.createElement('a');
    anchor.title = lang.tools[ev.id];
    anchor.href = '#';
    anchor.appendChild(doc.createTextNode(anchor.title));

    if (elem.firstChild) {
      elem.replaceChild(anchor, elem.firstChild);
    } else {
      elem.appendChild(anchor);
    }

    anchor.addEventListener('click',     _self.toolClick,      false);
    anchor.addEventListener('mouseover', _self.item_mouseover, false);
    anchor.addEventListener('mouseout',  _self.item_mouseout,  false);

    if (!(ev.id in _self.tools)) {
      _self.tools[ev.id] = elem;
      _self.elems.tools.appendChild(elem);
    }

    // Disable the text tool icon if the Canvas Text API is not supported.
    if (ev.id === 'text' && !app.layer.context.fillText && 
        !app.layer.context.mozPathText && elem) {
      elem.className += ' ' + _self.classPrefix + 'disabled';
      anchor.title = lang.tools.textUnsupported;

      anchor.removeEventListener('click', _self.toolClick, false);
      anchor.addEventListener('click', function (ev) {
        ev.preventDefault();
      }, false);
    }
  };

  /**
   * The <code>toolUnregister</code> application event handler. This method the 
   * tool element from the GUI.
   *
   * @param {pwlib.appEvent.toolUnregister} ev The application event object.
   *
   * @see PaintWeb#toolUnregister the method which allows you to unregister 
   * tools.
   */
  this.toolUnregister = function (ev) {
    if (ev.id in _self.tools) {
      _self.elems.tools.removeChild(_self.tools[ev.id]);
      delete _self.tools[ev.id];
    } else {
      return;
    }
  };

  /**
   * The <code>click</code> event handler for the command DOM elements.
   *
   * @private
   *
   * @param {Event} ev The DOM Event object.
   *
   * @see PaintWeb#commandRegister to register a new command.
   */
  this.commandClick = function (ev) {
    var cmd = this.parentNode.getAttribute('data-pwCommand');
    if (cmd && cmd in app.commands) {
      app.commands[cmd].call(this, ev);
    }
    ev.preventDefault();

    try {
      this.focus();
    } catch (err) { }
  };

  /**
   * The <code>commandRegister</code> application event handler. GUI elements 
   * associated to commands are updated to ensure proper user interaction.
   *
   * @private
   *
   * @param {pwlib.appEvent.commandRegister} ev The application event object.
   *
   * @see PaintWeb#commandRegister the method which allows you to register new 
   * commands.
   */
  this.commandRegister = function (ev) {
    var elem   = _self.commands[ev.id],
        anchor = null;
    if (!elem) {
      return;
    }

    elem.className += ' ' + _self.classPrefix + 'cmd_' + ev.id;

    if(elem.classList.contains('paintweb_buttonStyle')){
      button = doc.createElement('button');
      button.title = lang.commands[ev.id];
      button.type = "button";
      button.classList.add('btn');
      button.classList.add('blue');
      button.classList.add('p-1');
      button.appendChild(doc.createTextNode(button.title));

      if (elem.firstChild) {
        elem.removeChild(elem.firstChild);
      }
      elem.appendChild(button);

      button.addEventListener('click',     _self.commandClick,   false);

    } else {
      anchor = doc.createElement('a');
      anchor.title = lang.commands[ev.id];
      anchor.href = '#';
      anchor.appendChild(doc.createTextNode(anchor.title));

      // Remove the text content and append the locale string associated to
      // current command inside an anchor element (for better keyboard
      // accessibility).
      if (elem.firstChild) {
        elem.removeChild(elem.firstChild);
      }
      elem.appendChild(anchor);

      anchor.addEventListener('click',     _self.commandClick,   false);
      anchor.addEventListener('mouseover', _self.item_mouseover, false);
      anchor.addEventListener('mouseout',  _self.item_mouseout,  false);
    }


  };

  /**
   * The <code>commandUnregister</code> application event handler. This method 
   * simply removes all the user interactivity from the GUI element associated 
   * to the command being unregistered.
   *
   * @private
   *
   * @param {pwlib.appEvent.commandUnregister} ev The application event object.
   *
   * @see PaintWeb#commandUnregister the method which allows you to unregister 
   * commands.
   */
  this.commandUnregister = function (ev) {
    var elem   = _self.commands[ev.id],
        anchor = null;
    if (!elem) {
      return;
    }

    elem.className = elem.className.replace(' ' + _self.classPrefix + 'cmd_' 
        + ev.id, '');

    anchor = elem.firstChild;
    anchor.removeEventListener('click',     this.commands[ev.id], false);
    anchor.removeEventListener('mouseover', _self.item_mouseover, false);
    anchor.removeEventListener('mouseout',  _self.item_mouseout,  false);

    elem.removeChild(anchor);
  };

  /**
   * The <code>historyUpdate</code> application event handler. GUI elements 
   * associated to the <code>historyUndo</code> and to the 
   * <code>historyRedo</code> commands are updated such that they are either 
   * enabled or disabled, depending on the current history position.
   *
   * @private
   *
   * @param {pwlib.appEvent.historyUpdate} ev The application event object.
   *
   * @see PaintWeb#historyGoto the method which allows you to go to different 
   * history states.
   */
  this.historyUpdate = function (ev) {
    var undoElem  = _self.commands.historyUndo,
        undoState = false,
        redoElem  = _self.commands.historyRedo,
        redoState = false,
        className = ' ' + _self.classPrefix + 'disabled',
        undoElemState = undoElem.className.indexOf(className) === -1,
        redoElemState = redoElem.className.indexOf(className) === -1;

    if (ev.currentPos > 1) {
      undoState = true;
    }
    if (ev.currentPos < ev.states) {
      redoState = true;
    }

    if (undoElemState !== undoState) {
      if (undoState) {
        undoElem.className = undoElem.className.replace(className, '');
      } else {
        undoElem.className += className;
      }
    }

    if (redoElemState !== redoState) {
      if (redoState) {
        redoElem.className = redoElem.className.replace(className, '');
      } else {
        redoElem.className += className;
      }
    }
  };

  /**
   * The <code>imageSizeChange</code> application event handler. The GUI element 
   * which displays the image dimensions is updated to display the new image 
   * size.
   *
   * <p>Image size refers strictly to the dimensions of the image being edited 
   * by the user, that's width and height.
   *
   * @private
   * @param {pwlib.appEvent.imageSizeChange} ev The application event object.
   */
  this.imageSizeChange = function (ev) {
    var imageSize  = _self.elems.imageSize;
    if (imageSize) {
      imageSize.replaceChild(doc.createTextNode(ev.width + 'x' + ev.height), 
          imageSize.firstChild);
    }
  };

  /**
   * The <code>canvasSizeChange</code> application event handler. The Canvas 
   * container element dimensions are updated to the new values, and the image 
   * resize handle is positioned accordingly.
   *
   * <p>Canvas size refers strictly to the dimensions of the Canvas elements in 
   * the browser, changed with CSS style properties, width and height. Scaling 
   * of the Canvas elements is applied when the user zooms the image or when the 
   * browser changes the render DPI / zoom.
   *
   * @private
   * @param {pwlib.appEvent.canvasSizeChange} ev The application event object.
   */
  this.canvasSizeChange = function (ev) {
    var canvasContainer = _self.elems.canvasContainer,
        canvasResizer   = _self.canvasResizer,
        className       = ' ' + _self.classPrefix + 'disabled',
        resizeHandle    = canvasResizer.resizeHandle;

    // Update the Canvas container to be the same size as the Canvas elements.
    canvasContainer.style.width  = ev.width  + 'px';
    canvasContainer.style.height = ev.height + 'px';

    resizeHandle.style.top  = ev.height + 'px';
    resizeHandle.style.left = ev.width  + 'px';
  };

  /**
   * The <code>imageZoom</code> application event handler. The GUI input element 
   * which displays the image zoom level is updated to display the new value.
   *
   * @private
   * @param {pwlib.appEvent.imageZoom} ev The application event object.
   */
  this.imageZoom = function (ev) {
    var elem  = _self.inputs.imageZoom,
        val   = MathRound(ev.zoom * 100);
    if (elem && elem.value != val) {
      elem.value = val;
    }
  };

  /**
   * The <code>configChange</code> application event handler. This method 
   * ensures the GUI input elements stay up-to-date when some PaintWeb 
   * configuration is modified.
   *
   * @private
   * @param {pwlib.appEvent.configChange} ev The application event object.
   */
  this.configChangeHandler = function (ev) {
    var cfg = '', input;
    if (ev.group) {
      cfg = ev.group.replace('.', '_') + '_';
    }
    cfg += ev.config;
    input = _self.inputs[cfg];

    // Handle changes for color inputs.
    if (!input && (input = _self.colorInputs[cfg])) {
      var color = ev.value.replace(/\s+/g, '').
                    replace(/^rgba\(/, '').replace(/\)$/, '');

      color = color.split(',');
      input.updateColor({
        red:   color[0] / 255,
        green: color[1] / 255,
        blue:  color[2] / 255,
        alpha: color[3]
      });

      return;
    }

    if (!input) {
      return;
    }

    var tag = input.tagName.toLowerCase(),
        isInput = tag === 'select' || tag === 'input' || tag === 'textarea';

    if (isInput) {
      if (input.type === 'checkbox' && input.checked !== ev.value) {
        input.checked = ev.value;
      }
      if (input.type !== 'checkbox' && input.value !== ev.value) {
        input.value = ev.value;
      }

      return;
    }

    var classActive = ' ' + _self.className + 'configActive';

    if (input.hasAttribute('data-pwConfigToggle')) {
      var inputActive = input.className.indexOf(classActive) !== -1;

      if (ev.value && !inputActive) {
        input.className += classActive;
      } else if (!ev.value && inputActive) {
        input.className = input.className.replace(classActive, '');
      }
    }

    var classActive = ' ' + _self.className + 'configActive',
        prevValElem = _self.inputValues[cfg + '_' + ev.previousValue],
        valElem = _self.inputValues[cfg + '_' + ev.value];

    if (prevValElem && prevValElem.className.indexOf(classActive) !== -1) {
      prevValElem.className = prevValElem.className.replace(classActive, '');
    }

    if (valElem && valElem.className.indexOf(classActive) === -1) {
      valElem.className += classActive;
    }
  };

  /**
   * The <code>click</code> event handler for DOM elements associated to 
   * PaintWeb configuration values. These elements rely on parent elements which 
   * are associated to configuration properties.
   *
   * <p>This method dispatches the {@link pwlib.appEvent.configChange} event.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  this.configValueClick = function (ev) {
    var pNode = this.parentNode,
        input = pNode._pwConfigParent,
        val = pNode.getAttribute('data-pwConfigValue');

    if (!input || !input._pwConfigProperty) {
      return;
    }

    ev.preventDefault();

    var className = ' ' + _self.classPrefix + 'configActive',
        groupRef = input._pwConfigGroupRef,
        group = input._pwConfigGroup,
        prop = input._pwConfigProperty,
        prevVal = groupRef[prop],
        prevValElem = _self.inputValues[group.replace('.', '_') + '_' + prop 
          + '_' + prevVal];

    if (prevVal == val) {
      return;
    }

    if (prevValElem && prevValElem.className.indexOf(className) !== -1) {
      prevValElem.className = prevValElem.className.replace(className, '');
    }

    groupRef[prop] = val;

    if (pNode.className.indexOf(className) === -1) {
      pNode.className += className;
    }

    app.events.dispatch(new appEvent.configChange(val, prevVal, prop, group, 
          groupRef));
  };

  /**
   * The <code>change</code> event handler for input elements associated to 
   * PaintWeb configuration properties.
   *
   * <p>This method dispatches the {@link pwlib.appEvent.configChange} event.
   *
   * @private
   */
  this.configInputChange = function () {
    if (!this._pwConfigProperty) {
      return;
    }

    var val = this.type === 'checkbox' ? this.checked : this.value,
        groupRef = this._pwConfigGroupRef,
        group = this._pwConfigGroup,
        prop = this._pwConfigProperty,
        prevVal = groupRef[prop];

    if (this.getAttribute('type') === 'number') {
      val = parseInt(val);
      if (val != this.value) {
        this.value = val;
      }
    }

    if (val == prevVal) {
      return;
    }

    groupRef[prop] = val;

    app.events.dispatch(new appEvent.configChange(val, prevVal, prop, group, 
          groupRef));
  };

  /**
   * The <code>click</code> event handler for DOM elements associated to boolean 
   * configuration properties. These elements only toggle the true/false value 
   * of the configuration property.
   *
   * <p>This method dispatches the {@link pwlib.appEvent.configChange} event.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  this.configToggleClick = function (ev) {
    var className = ' ' + _self.classPrefix + 'configActive',
        pNode = this.parentNode,
        groupRef = pNode._pwConfigGroupRef,
        group = pNode._pwConfigGroup,
        prop = pNode._pwConfigProperty,
        elemActive = pNode.className.indexOf(className) !== -1;

    ev.preventDefault();

    groupRef[prop] = !groupRef[prop];

    if (groupRef[prop] && !elemActive) {
      pNode.className += className;
    } else if (!groupRef[prop] && elemActive) {
      pNode.className = pNode.className.replace(className, '');
    }

    app.events.dispatch(new appEvent.configChange(groupRef[prop], 
          !groupRef[prop], prop, group, groupRef));
  };

  /**
   * It's same with configToggleClick().
   * just remove code for Toggle.
   */
  this.configButtonClick = function (ev) {
    var className = ' ' + _self.classPrefix + 'configActive',
        pNode = this.parentNode,
        groupRef = pNode._pwConfigGroupRef,
        group = pNode._pwConfigGroup,
        prop = pNode._pwConfigProperty,
        elemActive = pNode.className.indexOf(className) !== -1;

    ev.preventDefault();

    groupRef[prop] = !groupRef[prop];

    app.events.dispatch(new appEvent.configChange(groupRef[prop], 
          !groupRef[prop], prop, group, groupRef));
  };
  /**
   * The <code>shadowAllow</code> application event handler. This method 
   * shows/hide the shadow tab when shadows are allowed/disallowed.
   *
   * @private
   * @param {pwlib.appEvent.shadowAllow} ev The application event object.
   */
  this.shadowAllow = function (ev) {
    if ('shadow' in _self.tabPanels.main.tabs) {
      if (ev.allowed) {
        _self.tabPanels.main.tabShow('shadow');
      } else {
        _self.tabPanels.main.tabHide('shadow');
      }
    }
  };

  /**
   * The <code>clipboardUpdate</code> application event handler. The GUI element 
   * associated to the <code>clipboardPaste</code> command is updated to be 
   * disabled/enabled depending on the event.
   *
   * @private
   * @param {pwlib.appEvent.clipboardUpdate} ev The application event object.
   */
  this.clipboardUpdate = function (ev) {
    var classDisabled = ' ' + _self.classPrefix + 'disabled',
        elem, elemEnabled,
        elems = [_self.commands.clipboardPaste, 
        _self.elems.selTab_clipboardPaste];

    for (var i = 0, n = elems.length; i < n; i++) {
      elem = elems[i];
      if (!elem) {
        continue;
      }

      elemEnabled = elem.className.indexOf(classDisabled) === -1;

      if (!ev.data && elemEnabled) {
        elem.className += classDisabled;
      } else if (ev.data && !elemEnabled) {
        elem.className = elem.className.replace(classDisabled, '');
      }
    }
  };

  /**
   * The <code>selectionChange</code> application event handler. The GUI 
   * elements associated to the <code>selectionCut</code> and 
   * <code>selectionCopy</code> commands are updated to be disabled/enabled 
   * depending on the event.
   *
   * @private
   * @param {pwlib.appEvent.selectionChange} ev The application event object.
   */
  this.selectionChange = function (ev) {
    var classDisabled  = ' ' + _self.classPrefix + 'disabled',
        elem, elemEnabled,
        elems = [_self.commands.selectionCut, _self.commands.selectionCopy, 
        _self.elems.selTab_selectionCut, _self.elems.selTab_selectionCopy, 
        _self.commands.selectionDelete, _self.commands.selectionFill, 
        _self.commands.selectionCrop];

    for (var i = 0, n = elems.length; i < n; i++) {
      elem = elems[i];
      if (!elem) {
        continue;
      }

      elemEnabled = elem.className.indexOf(classDisabled) === -1;

      if (ev.state === ev.STATE_NONE && elemEnabled) {
        elem.className += classDisabled;
      } else if (ev.state === ev.STATE_SELECTED && !elemEnabled) {
        elem.className = elem.className.replace(classDisabled, '');
      }
    }
  };

  /**
   * Show the graphical user interface.
   *
   * <p>This method dispatches the {@link pwlib.appEvent.guiShow} application 
   * event.
   */
  this.show = function () {
    var placeholder = config.guiPlaceholder,
        className   = this.classPrefix + 'placeholder',
        re          = new RegExp('\\b' + className);

    if (!re.test(placeholder.className)) {
      placeholder.className += ' ' + className;
    }

    try {
      placeholder.focus();
    } catch (err) { }

    app.events.dispatch(new appEvent.guiShow());
  };

  /**
   * Hide the graphical user interface.
   *
   * <p>This method dispatches the {@link pwlib.appEvent.guiHide} application 
   * event.
   */
  this.hide = function () {
    var placeholder = config.guiPlaceholder,
        re = new RegExp('\\b' + this.classPrefix + 'placeholder', 'g');

    placeholder.className = placeholder.className.replace(re, '');

    app.events.dispatch(new appEvent.guiHide());
  };

  /**
   * The application destroy event handler. This method is invoked by the main 
   * PaintWeb application when the instance is destroyed, for the purpose of 
   * cleaning-up the GUI-related things from the document add by the current 
   * instance.
   *
   * @private
   */
  this.destroy = function () {
    var placeholder = config.guiPlaceholder;

    while(placeholder.hasChildNodes()) {
      placeholder.removeChild(placeholder.firstChild);
    }
  };

  /**
   * Resize the PaintWeb graphical user interface.
   *
   * <p>This method dispatches the {@link pwlib.appEvent.configChange} event for 
   * the "viewportWidth" and "viewportHeight" configuration properties. Both 
   * properties are updated to hold the new values you give.
   *
   * <p>Once the GUI is resized, the {@link pwlib.appEvent.viewportSizeChange} 
   * event is also dispatched.
   *
   * @param {String} width The new width you want. Make sure the value is a CSS 
   * length, like "50%", "450px" or "30em".
   *
   * @param {String} height The new height you want.
   */
  this.resizeTo = function (width, height) {
    if (!width || !height) {
      return;
    }

    var width_old  = config.viewportWidth,
        height_old = config.viewportHeight;

    config.viewportWidth  = width;
    config.viewportHeight = height;

    app.events.dispatch(new appEvent.configChange(width, width_old, 
          'viewportWidth', '', config));

    app.events.dispatch(new appEvent.configChange(height, height_old, 
          'viewportHeight', '', config));

    config.guiPlaceholder.style.width = config.viewportWidth;
    this.elems.viewport.style.height  = config.viewportHeight;

    app.events.dispatch(new appEvent.viewportSizeChange(width, height));
  };

  /**
   * The state change event handler for the Hand tool. This function 
   * enables/disables the Hand tool by checking if the current image fits into 
   * the viewport or not.
   *
   * <p>This function is invoked when one of the following application events is  
   * dispatched: <code>viewportSizeChange</code>, <code>canvasSizeChange</code> 
   * or <code>appInit</code.
   *
   * @private
   * @param 
   * {pwlib.appEvent.viewportSizeChange|pwlib.appEvent.canvasSizeChange|pwlib.appEvent.appInit} 
   * [ev] The application event object.
   */
  this.toolHandStateChange = function (ev) {
    var cwidth    = 0,
        cheight   = 0,
        className = ' ' + _self.classPrefix + 'disabled',
        hand      = _self.tools.hand,
        viewport  = _self.elems.viewport;

    if (!hand) {
      return;
    }

    if (ev.type === 'canvasSizeChange') {
      cwidth  = ev.width;
      cheight = ev.height;
    } else {
      var containerStyle = _self.elems.canvasContainer.style;
      cwidth  = parseInt(containerStyle.width);
      cheight = parseInt(containerStyle.height);
    }

    // FIXME: it should be noted that when PaintWeb loads, the entire GUI is 
    // hidden, and win.getComputedStyle() style tells that the viewport 
    // width/height is 0.
    cs = win.getComputedStyle(viewport, null);

    var vwidth     = parseInt(cs.width),
        vheight    = parseInt(cs.height),
        enableHand = false,
        handState  = hand.className.indexOf(className) === -1;

    if (vheight < cheight || vwidth < cwidth) {
      enableHand = true;
    }

    if (enableHand && !handState) {
      hand.className = hand.className.replace(className, '');
    } else if (!enableHand && handState) {
      hand.className += className;
    }

    if (!enableHand && app.tool && app.tool._id === 'hand' && 'prevTool' in 
        app.tool) {
      app.toolActivate(app.tool.prevTool, ev);
    }
  };
};

/**
 * @class A floating panel GUI element.
 *
 * @private
 *
 * @param {pwlib.gui} gui Reference to the PaintWeb GUI object.
 *
 * @param {Element} container Reference to the DOM element you want to transform 
 * into a floating panel.
 */
pwlib.guiFloatingPanel = function (gui, container) {
  var _self          = this,
      appEvent       = pwlib.appEvent,
      cStyle         = container.style,
      doc            = gui.app.doc,
      guiPlaceholder = gui.app.config.guiPlaceholder,
      lang           = gui.app.lang,
      panels         = gui.floatingPanels,
      win            = gui.app.win,
      zIndex_step    = 200;

  // These hold the mouse starting location during the drag operation.
  var mx, my;

  // These hold the panel starting location during the drag operation.
  var ptop, pleft;

  /**
   * Panel state: hidden.
   * @constant
   */
  this.STATE_HIDDEN    = 0;

  /**
   * Panel state: visible.
   * @constant
   */
  this.STATE_VISIBLE   = 1;

  /**
   * Panel state: minimized.
   * @constant
   */
  this.STATE_MINIMIZED = 3;

  /**
   * Panel state: the user is dragging the floating panel.
   * @constant
   */
  this.STATE_DRAGGING  = 4;

  /**
   * Tells the state of the floating panel: hidden/minimized/visible or if it's 
   * being dragged.
   * @type Number
   */
  this.state = -1;

  /**
   * Floating panel ID. This is the ID used in the 
   * <var>data-pwFloatingPanel</var> element attribute.
   * @type String
   */
  this.id = null;

  /**
   * Reference to the floating panel element.
   * @type Element
   */
  this.container = container;

  /**
   * The viewport element. This element is the first parent element which has 
   * the style.overflow set to "auto" or "scroll".
   * @type Element
   */
  this.viewport = null;

  /**
   * Custom application events interface.
   * @type pwlib.appEvents
   */
  this.events = null;

  /**
   * The panel content element.
   * @type Element
   */
  this.content = null;

  // The initial viewport scroll position.
  var vScrollLeft = 0, vScrollTop = 0,
      btn_close = null, btn_minimize = null;

  /**
   * Initialize the floating panel.
   * @private
   */
  function init () {
    _self.events = new pwlib.appEvents(_self);

    _self.id = _self.container.getAttribute('data-pwFloatingPanel');

    var ttl = _self.container.getElementsByTagName('h1')[0],
        content = _self.container.getElementsByTagName('div')[0],
        cs = win.getComputedStyle(_self.container, null),
        zIndex = parseInt(cs.zIndex);

    cStyle.zIndex = cs.zIndex;

    if (zIndex > panels.zIndex_) {
      panels.zIndex_ = zIndex;
    }

    _self.container.className += ' ' + gui.classPrefix + 'floatingPanel ' +
      gui.classPrefix + 'floatingPanel_' + _self.id;

    // the content
    content.className += ' ' + gui.classPrefix + 'floatingPanel_content';
    _self.content = content;

    // setup the title element
    ttl.className += ' ' + gui.classPrefix + 'floatingPanel_title';
    ttl.replaceChild(doc.createTextNode(lang.floatingPanels[_self.id]), 
        ttl.firstChild);

    ttl.addEventListener('mousedown', ev_mousedown, false);

    // allow auto-hide for the panel
    if (_self.container.getAttribute('data-pwPanelHide') === 'true') {
      _self.hide();
    } else {
      _self.state = _self.STATE_VISIBLE;
    }

    // Find the viewport parent element.
    var pNode = _self.container.parentNode,
        found = null;

    while (!found && pNode) {
      if (pNode.nodeName.toLowerCase() === 'html') {
        found = pNode;
        break;
      }

      cs = win.getComputedStyle(pNode, null);
      if (cs && (cs.overflow === 'scroll' || cs.overflow === 'auto')) {
        found = pNode;
      } else {
        pNode = pNode.parentNode;
      }
    }

    _self.viewport = found;

    // add the panel minimize button.
    btn_minimize = doc.createElement('a');
    btn_minimize.href = '#';
    btn_minimize.title = lang.floatingPanelMinimize;
    btn_minimize.className = gui.classPrefix + 'floatingPanel_minimize';
    btn_minimize.addEventListener('click', ev_minimize, false);
    btn_minimize.appendChild(doc.createTextNode(btn_minimize.title));

    _self.container.insertBefore(btn_minimize, content);

    // add the panel close button.
    btn_close = doc.createElement('a');
    btn_close.href = '#';
    btn_close.title = lang.floatingPanelClose;
    btn_close.className = gui.classPrefix + 'floatingPanel_close';
    btn_close.addEventListener('click', ev_close, false);
    btn_close.appendChild(doc.createTextNode(btn_close.title));

    _self.container.insertBefore(btn_close, content);

    // setup the panel resize handle.
    if (_self.container.getAttribute('data-pwPanelResizable') === 'true') {
      var resizeHandle = doc.createElement('div');
      resizeHandle.className = gui.classPrefix + 'floatingPanel_resizer';
      _self.container.appendChild(resizeHandle);
      _self.resizer = new pwlib.guiResizer(gui, resizeHandle, _self.container);
    }
  };

  /**
   * The <code>click</code> event handler for the panel Minimize button element.
   *
   * <p>This method dispatches the {@link 
   * pwlib.appEvent.guiFloatingPanelStateChange} application event.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_minimize (ev) {
    ev.preventDefault();
    try {
      this.focus();
    } catch (err) { }

    var classMinimized = ' ' + gui.classPrefix + 'floatingPanel_minimized';

    if (_self.state === _self.STATE_MINIMIZED) {
      _self.state = _self.STATE_VISIBLE;

      this.title = lang.floatingPanelMinimize;
      this.className = gui.classPrefix + 'floatingPanel_minimize';
      this.replaceChild(doc.createTextNode(this.title), this.firstChild);

      if (_self.container.className.indexOf(classMinimized) !== -1) {
        _self.container.className 
          = _self.container.className.replace(classMinimized, '');
      }

    } else if (_self.state === _self.STATE_VISIBLE) {
      _self.state = _self.STATE_MINIMIZED;

      this.title = lang.floatingPanelRestore;
      this.className = gui.classPrefix + 'floatingPanel_restore';
      this.replaceChild(doc.createTextNode(this.title), this.firstChild);

      if (_self.container.className.indexOf(classMinimized) === -1) {
        _self.container.className += classMinimized;
      }
    }

    _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));

    _self.bringOnTop();
  };

  /**
   * The <code>click</code> event handler for the panel Close button element.  
   * This hides the floating panel.
   *
   * <p>This method dispatches the {@link 
   * pwlib.appEvent.guiFloatingPanelStateChange} application event.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_close (ev) {
    ev.preventDefault();
    _self.hide();
    try {
      guiPlaceholder.focus();
    } catch (err) { }
  };

  /**
   * The <code>mousedown</code> event handler. This is invoked when you start 
   * dragging the floating panel.
   *
   * <p>This method dispatches the {@link 
   * pwlib.appEvent.guiFloatingPanelStateChange} application event.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_mousedown (ev) {
    _self.state = _self.STATE_DRAGGING;

    mx = ev.clientX;
    my = ev.clientY;

    var cs = win.getComputedStyle(_self.container, null);

    ptop  = parseInt(cs.top);
    pleft = parseInt(cs.left);

    if (_self.viewport) {
      vScrollLeft = _self.viewport.scrollLeft;
      vScrollTop  = _self.viewport.scrollTop;
    }

    _self.bringOnTop();

    doc.addEventListener('mousemove', ev_mousemove, false);
    doc.addEventListener('mouseup',   ev_mouseup,   false);

    _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));

    if (ev.preventDefault) {
      ev.preventDefault();
    }
  };

  /**
   * The <code>mousemove</code> event handler. This performs the actual move of 
   * the floating panel.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_mousemove (ev) {
    var x = pleft + ev.clientX - mx,
        y = ptop  + ev.clientY - my;

    if (_self.viewport) {
      if (_self.viewport.scrollLeft !== vScrollLeft) {
        x += _self.viewport.scrollLeft - vScrollLeft;
      }
      if (_self.viewport.scrollTop !== vScrollTop) {
        y += _self.viewport.scrollTop - vScrollTop;
      }
    }

    cStyle.left = x + 'px';
    cStyle.top  = y + 'px';
  };

  /**
   * The <code>mouseup</code> event handler. This ends the panel drag operation.
   *
   * <p>This method dispatches the {@link 
   * pwlib.appEvent.guiFloatingPanelStateChange} application event.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_mouseup (ev) {
    if (_self.container.className.indexOf(' ' + gui.classPrefix 
          + 'floatingPanel_minimized') !== -1) {
      _self.state = _self.STATE_MINIMIZED;
    } else {
      _self.state = _self.STATE_VISIBLE;
    }

    doc.removeEventListener('mousemove', ev_mousemove, false);
    doc.removeEventListener('mouseup',   ev_mouseup,   false);

    try {
      guiPlaceholder.focus();
    } catch (err) { }

    _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));
  };

  /**
   * Bring the panel to the top. This method makes sure the current floating 
   * panel is visible.
   */
  this.bringOnTop = function () {
    panels.zIndex_ += zIndex_step;
    cStyle.zIndex = panels.zIndex_;
  };

  /**
   * Hide the panel.
   *
   * <p>This method dispatches the {@link 
   * pwlib.appEvent.guiFloatingPanelStateChange} application event.
   */
  this.hide = function () {
    cStyle.display = 'none';
    _self.state = _self.STATE_HIDDEN;
    _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));
  };

  /**
   * Show the panel.
   *
   * <p>This method dispatches the {@link 
   * pwlib.appEvent.guiFloatingPanelStateChange} application event.
   */
  this.show = function () {
    if (_self.state === _self.STATE_VISIBLE) {
      return;
    }

    cStyle.display = 'block';
    _self.state = _self.STATE_VISIBLE;

    var classMinimized = ' ' + gui.classPrefix + 'floatingPanel_minimized';

    if (_self.container.className.indexOf(classMinimized) !== -1) {
      _self.container.className 
        = _self.container.className.replace(classMinimized, '');

      btn_minimize.className = gui.classPrefix + 'floatingPanel_minimize';
      btn_minimize.title = lang.floatingPanelMinimize;
      btn_minimize.replaceChild(doc.createTextNode(btn_minimize.title), 
          btn_minimize.firstChild);
    }

    _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state));

    _self.bringOnTop();
  };

  /**
   * Toggle the panel visibility.
   *
   * <p>This method dispatches the {@link 
   * pwlib.appEvent.guiFloatingPanelStateChange} application event.
   */
  this.toggle = function () {
    if (_self.state === _self.STATE_VISIBLE || _self.state === 
        _self.STATE_MINIMIZED) {
      _self.hide();
    } else {
      _self.show();
    }
  };

  init();
};

/**
 * @class The state change event for the floating panel. This event is fired 
 * when the floating panel changes its state. This event is not cancelable.
 *
 * @augments pwlib.appEvent
 *
 * @param {Number} state The floating panel state.
 */
pwlib.appEvent.guiFloatingPanelStateChange = function (state) {
  /**
   * Panel state: hidden.
   * @constant
   */
  this.STATE_HIDDEN    = 0;

  /**
   * Panel state: visible.
   * @constant
   */
  this.STATE_VISIBLE   = 1;

  /**
   * Panel state: minimized.
   * @constant
   */
  this.STATE_MINIMIZED = 3;

  /**
   * Panel state: the user is dragging the floating panel.
   * @constant
   */
  this.STATE_DRAGGING  = 4;

  /**
   * The current floating panel state.
   * @type Number
   */
  this.state = state;

  pwlib.appEvent.call(this, 'guiFloatingPanelStateChange');
};

/**
 * @class Resize handler.
 *
 * @private
 *
 * @param {pwlib.gui} gui Reference to the PaintWeb GUI object.
 *
 * @param {Element} resizeHandle Reference to the resize handle DOM element.  
 * This is the element users will be able to drag to achieve the resize effect 
 * on the <var>container</var> element.
 *
 * @param {Element} container Reference to the container DOM element. This is 
 * the element users will be able to resize using the <var>resizeHandle</var> 
 * element.
 */
pwlib.guiResizer = function (gui, resizeHandle, container) {
  var _self              = this,
      cStyle             = container.style,
      doc                = gui.app.doc,
      guiResizeEnd       = pwlib.appEvent.guiResizeEnd,
      guiResizeMouseMove = pwlib.appEvent.guiResizeMouseMove,
      guiResizeStart     = pwlib.appEvent.guiResizeStart,
      win                = gui.app.win;

  /**
   * Custom application events interface.
   * @type pwlib.appEvents
   */
  this.events = null;

  /**
   * The resize handle DOM element.
   * @type Element
   */
  this.resizeHandle = resizeHandle;

  /**
   * The container DOM element. This is the element that's resized by the user 
   * when he/she drags the resize handle.
   * @type Element
   */
  this.container = container;

  /**
   * The viewport element. This element is the first parent element which has 
   * the style.overflow set to "auto" or "scroll".
   * @type Element
   */
  this.viewport = null;

  /**
   * Tells if the GUI resizer should dispatch the {@link 
   * pwlib.appEvent.guiResizeMouseMove} application event when the user moves 
   * the mouse during the resize operation.
   *
   * @type Boolean
   * @default false
   */
  this.dispatchMouseMove = false;

  /**
   * Tells if the user resizing the container now.
   *
   * @type Boolean
   * @default false
   */
  this.resizing = false;

  // The initial position of the mouse.
  var mx = 0, my = 0;

  // The initial container dimensions.
  var cWidth = 0, cHeight = 0;

  // The initial viewport scroll position.
  var vScrollLeft = 0, vScrollTop = 0;

  /**
   * Initialize the resize functionality.
   * @private
   */
  function init () {
    _self.events = new pwlib.appEvents(_self);
    resizeHandle.addEventListener('mousedown', ev_mousedown, false);

    // Find the viewport parent element.
    var cs, pNode = _self.container.parentNode,
        found = null;
    while (!found && pNode) {
      if (pNode.nodeName.toLowerCase() === 'html') {
        found = pNode;
        break;
      }

      cs = win.getComputedStyle(pNode, null);
      if (cs && (cs.overflow === 'scroll' || cs.overflow === 'auto')) {
        found = pNode;
      } else {
        pNode = pNode.parentNode;
      }
    }

    _self.viewport = found;
  };

  /**
   * The <code>mousedown</code> event handler. This starts the resize operation.
   *
   * <p>This function dispatches the {@link pwlib.appEvent.guiResizeStart} 
   * event.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_mousedown (ev) {
    mx = ev.clientX;
    my = ev.clientY;

    var cs = win.getComputedStyle(_self.container, null);
    cWidth  = parseInt(cs.width);
    cHeight = parseInt(cs.height);

    var cancel = _self.events.dispatch(new guiResizeStart(mx, my, cWidth, 
          cHeight));

    if (cancel) {
      return;
    }

    if (_self.viewport) {
      vScrollLeft = _self.viewport.scrollLeft;
      vScrollTop  = _self.viewport.scrollTop;
    }

    _self.resizing = true;
    doc.addEventListener('mousemove', ev_mousemove, false);
    doc.addEventListener('mouseup',   ev_mouseup,   false);

    if (ev.preventDefault) {
      ev.preventDefault();
    }

    if (ev.stopPropagation) {
      ev.stopPropagation();
    }
  };

  /**
   * The <code>mousemove</code> event handler. This performs the actual resizing 
   * of the <var>container</var> element.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_mousemove (ev) {
    var w = cWidth  + ev.clientX - mx,
        h = cHeight + ev.clientY - my;

    if (_self.viewport) {
      if (_self.viewport.scrollLeft !== vScrollLeft) {
        w += _self.viewport.scrollLeft - vScrollLeft;
      }
      if (_self.viewport.scrollTop !== vScrollTop) {
        h += _self.viewport.scrollTop - vScrollTop;
      }
    }

    cStyle.width  = w + 'px';
    cStyle.height = h + 'px';

    if (_self.dispatchMouseMove) {
      _self.events.dispatch(new guiResizeMouseMove(ev.clientX, ev.clientY, w, 
            h));
    }
  };

  /**
   * The <code>mouseup</code> event handler. This ends the resize operation.
   *
   * <p>This function dispatches the {@link pwlib.appEvent.guiResizeEnd} event.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_mouseup (ev) {
    var cancel = _self.events.dispatch(new guiResizeEnd(ev.clientX, ev.clientY, 
          parseInt(cStyle.width), parseInt(cStyle.height)));

    if (cancel) {
      return;
    }

    _self.resizing = false;
    doc.removeEventListener('mousemove', ev_mousemove, false);
    doc.removeEventListener('mouseup',   ev_mouseup,   false);
  };

  init();
};

/**
 * @class The GUI element resize start event. This event is cancelable.
 *
 * @augments pwlib.appEvent
 *
 * @param {Number} x The mouse location on the x-axis.
 * @param {Number} y The mouse location on the y-axis.
 * @param {Number} width The element width.
 * @param {Number} height The element height.
 */
pwlib.appEvent.guiResizeStart = function (x, y, width, height) {
  /**
   * The mouse location on the x-axis.
   * @type Number
   */
  this.x = x;

  /**
   * The mouse location on the y-axis.
   * @type Number
   */
  this.y = y;

  /**
   * The element width.
   * @type Number
   */
  this.width = width;

  /**
   * The element height.
   * @type Number
   */
  this.height = height;

  pwlib.appEvent.call(this, 'guiResizeStart', true);
};

/**
 * @class The GUI element resize end event. This event is cancelable.
 *
 * @augments pwlib.appEvent
 *
 * @param {Number} x The mouse location on the x-axis.
 * @param {Number} y The mouse location on the y-axis.
 * @param {Number} width The element width.
 * @param {Number} height The element height.
 */
pwlib.appEvent.guiResizeEnd = function (x, y, width, height) {
  /**
   * The mouse location on the x-axis.
   * @type Number
   */
  this.x = x;

  /**
   * The mouse location on the y-axis.
   * @type Number
   */
  this.y = y;

  /**
   * The element width.
   * @type Number
   */
  this.width = width;

  /**
   * The element height.
   * @type Number
   */
  this.height = height;

  pwlib.appEvent.call(this, 'guiResizeEnd', true);
};

/**
 * @class The GUI element resize mouse move event. This event is not cancelable.
 *
 * @augments pwlib.appEvent
 *
 * @param {Number} x The mouse location on the x-axis.
 * @param {Number} y The mouse location on the y-axis.
 * @param {Number} width The element width.
 * @param {Number} height The element height.
 */
pwlib.appEvent.guiResizeMouseMove = function (x, y, width, height) {
  /**
   * The mouse location on the x-axis.
   * @type Number
   */
  this.x = x;

  /**
   * The mouse location on the y-axis.
   * @type Number
   */
  this.y = y;

  /**
   * The element width.
   * @type Number
   */
  this.width = width;

  /**
   * The element height.
   * @type Number
   */
  this.height = height;

  pwlib.appEvent.call(this, 'guiResizeMouseMove');
};

/**
 * @class The tabbed panel GUI component.
 *
 * @private
 *
 * @param {pwlib.gui} gui Reference to the PaintWeb GUI object.
 *
 * @param {Element} panel Reference to the panel DOM element.
 */
pwlib.guiTabPanel = function (gui, panel) {
  var _self    = this,
      appEvent = pwlib.appEvent,
      doc      = gui.app.doc,
      lang     = gui.app.lang;

  /**
   * Custom application events interface.
   * @type pwlib.appEvents
   */
  this.events = null;

  /**
   * Panel ID. The ID is the same as the data-pwTabPanel attribute value of the 
   * panel DOM element .
   *
   * @type String.
   */
  this.id = null;

  /**
   * Holds references to the DOM element of each tab and tab button.
   * @type Object
   */
  this.tabs = {};

  /**
   * Reference to the tab buttons DOM element.
   * @type Element
   */
  this.tabButtons = null;

  /**
   * The panel container DOM element.
   * @type Element
   */
  this.container = panel;

  /**
   * Holds the ID of the currently active tab.
   * @type String
   */
  this.tabId = null;

  /**
   * Holds the ID of the previously active tab.
   *
   * @private
   * @type String
   */
  var prevTabId_ = null;

  /**
   * Initialize the toolbar functionality.
   * @private
   */
  function init () {
    _self.events = new pwlib.appEvents(_self);
    _self.id = _self.container.getAttribute('data-pwTabPanel');

    // Add two class names, the generic .paintweb_tabPanel and another class 
    // name specific to the current tab panel: .paintweb_tabPanel_id. 
    _self.container.className += ' ' + gui.classPrefix + 'tabPanel' 
      + ' ' + gui.classPrefix + 'tabPanel_' + _self.id;

    var tabButtons = doc.createElement('ul'),
        tabButton = null,
        tabDefault = _self.container.getAttribute('data-pwTabDefault') || null,
        childNodes = _self.container.childNodes,
        type = gui.app.ELEMENT_NODE,
        elem = null,
        tabId = null,
        anchor = null;

    tabButtons.className = gui.classPrefix + 'tabsList';

    // Find all the tabs in the current panel container element.
    for (var i = 0; elem = childNodes[i]; i++) {
      if (elem.nodeType !== type) {
        continue;
      }

      // A tab is any element with a given data-pwTab attribute.
      tabId = elem.getAttribute('data-pwTab');
      if (!tabId) {
        continue;
      }

      // two class names, the generic .paintweb_tab and the tab-specific class 
      // name .paintweb_tabPanelId_tabId.
      elem.className += ' ' + gui.classPrefix + 'tab ' + gui.classPrefix 
        + _self.id + '_' + tabId;

      tabButton = doc.createElement('li');
      tabButton._pwTab = tabId;

      anchor = doc.createElement('a');
      anchor.href = '#';
      anchor.addEventListener('click', ev_tabClick, false);

      if (_self.id in lang.tabs) {
        anchor.title = lang.tabs[_self.id][tabId + 'Title'] || 
          lang.tabs[_self.id][tabId];
        anchor.appendChild(doc.createTextNode(lang.tabs[_self.id][tabId]));
      }

      if ((tabDefault && tabId === tabDefault) ||
          (!tabDefault && !_self.tabId)) {
        _self.tabId = tabId;
        tabButton.className = gui.classPrefix + 'tabActive';
      } else {
        prevTabId_ = tabId;
        elem.style.display = 'none';
      }

      // automatically hide the tab
      if (elem.getAttribute('data-pwTabHide') === 'true') {
        tabButton.style.display = 'none';
      }

      _self.tabs[tabId] = {container: elem, button: tabButton};

      tabButton.appendChild(anchor);
      tabButtons.appendChild(tabButton);
    }

    _self.tabButtons = tabButtons;
    _self.container.appendChild(tabButtons);
  };

  /**
   * The <code>click</code> event handler for tab buttons. This function simply 
   * activates the tab the user clicked.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_tabClick (ev) {
    ev.preventDefault();
    _self.tabActivate(this.parentNode._pwTab);
  };

  /**
   * Activate a tab by ID.
   *
   * <p>This method dispatches the {@link pwlib.appEvent.guiTabActivate} event.
   *
   * @param {String} tabId The ID of the tab you want to activate.
   * @returns {Boolean} True if the tab has been activated successfully, or 
   * false if not.
   */
  this.tabActivate = function (tabId) {
    if (!tabId || !(tabId in this.tabs)) {
      return false;
    } else if (tabId === this.tabId) {
      return true;
    }

    var ev = new appEvent.guiTabActivate(tabId, this.tabId),
        cancel = this.events.dispatch(ev),
        elem = null,
        tabButton = null;

    if (cancel) {
      return false;
    }

    // Deactivate the currently active tab.
    if (this.tabId in this.tabs) {
      elem = this.tabs[this.tabId].container;
      elem.style.display = 'none';
      tabButton = this.tabs[this.tabId].button;
      tabButton.className = '';
      prevTabId_ = this.tabId;
    }

    // Activate the new tab.
    elem = this.tabs[tabId].container;
    elem.style.display = '';
    tabButton = this.tabs[tabId].button;
    tabButton.className = gui.classPrefix + 'tabActive';
    tabButton.style.display = ''; // make sure the tab is not hidden
    this.tabId = tabId;

    try {
      tabButton.firstChild.focus();
    } catch (err) { }

    return true;
  };

  /**
   * Hide a tab by ID.
   *
   * @param {String} tabId The ID of the tab you want to hide.
   * @returns {Boolean} True if the tab has been hidden successfully, or false 
   * if not.
   */
  this.tabHide = function (tabId) {
    if (!(tabId in this.tabs)) {
      return false;
    }

    if (this.tabId === tabId) {
      this.tabActivate(prevTabId_);
    }

    this.tabs[tabId].button.style.display = 'none';

    return true;
  };

  /**
   * Show a tab by ID.
   *
   * @param {String} tabId The ID of the tab you want to show.
   * @returns {Boolean} True if the tab has been displayed successfully, or 
   * false if not.
   */
  this.tabShow = function (tabId) {
    if (!(tabId in this.tabs)) {
      return false;
    }

    this.tabs[tabId].button.style.display = '';

    return true;
  };

  init();
};

/**
 * @class The GUI tab activation event. This event is cancelable.
 *
 * @augments pwlib.appEvent
 *
 * @param {String} tabId The ID of the tab being activated.
 * @param {String} prevTabId The ID of the previously active tab.
 */
pwlib.appEvent.guiTabActivate = function (tabId, prevTabId) {
  /**
   * The ID of the tab being activated.
   * @type String
   */
  this.tabId = tabId;

  /**
   * The ID of the previously active tab.
   * @type String
   */
  this.prevTabId = prevTabId;

  pwlib.appEvent.call(this, 'guiTabActivate', true);
};

/**
 * @class The color input GUI component.
 *
 * @private
 *
 * @param {pwlib.gui} gui Reference to the PaintWeb GUI object.
 *
 * @param {Element} input Reference to the DOM input element. This can be 
 * a span, a div, or any other tag.
 */
pwlib.guiColorInput = function (gui, input) {
  var _self      = this,
      colormixer = null,
      config     = gui.app.config,
      doc        = gui.app.doc,
      MathRound  = Math.round,
      lang       = gui.app.lang;

  /**
   * Color input ID. The ID is the same as the data-pwColorInput attribute value 
   * of the DOM input element .
   *
   * @type String.
   */
  this.id = null;

  /**
   * The color input element DOM reference.
   *
   * @type Element
   */
  this.input = input;

  /**
   * The configuration property to which this color input is attached to.
   * @type String
   */
  this.configProperty = null;

  /**
   * The configuration group to which this color input is attached to.
   * @type String
   */
  this.configGroup = null;

  /**
   * Reference to the configuration object which holds the color input value.
   * @type String
   */
  this.configGroupRef = null;

  /**
   * Holds the current color displayed by the input.
   *
   * @type Object
   */
  this.color = {red: 0, green: 0, blue: 0, alpha: 0};

  /**
   * Initialize the color input functionality.
   * @private
   */
  function init () {
    var cfgAttr     = _self.input.getAttribute('data-pwColorInput'),
        cfgNoDots   = cfgAttr.replace('.', '_'),
        cfgArray    = cfgAttr.split('.'),
        cfgProp     = cfgArray.pop(),
        cfgGroup    = cfgArray.join('.'),
        cfgGroupRef = config,
        langGroup   = lang.inputs,
        labelElem   = _self.input.parentNode,
        anchor      = doc.createElement('a'),
        color;

    for (var i = 0, n = cfgArray.length; i < n; i++) {
      cfgGroupRef = cfgGroupRef[cfgArray[i]];
      langGroup = langGroup[cfgArray[i]];
    }

    _self.configProperty = cfgProp;
    _self.configGroup = cfgGroup;
    _self.configGroupRef = cfgGroupRef;

    _self.id = cfgNoDots;

    _self.input.className += ' ' + gui.classPrefix + 'colorInput' 
      + ' ' + gui.classPrefix + _self.id;

    labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]), 
        labelElem.firstChild);

    color = _self.configGroupRef[_self.configProperty];
    color = color.replace(/\s+/g, '').replace(/^rgba\(/, '').replace(/\)$/, '');
    color = color.split(',');
    _self.color.red   = color[0] / 255;
    _self.color.green = color[1] / 255;
    _self.color.blue  = color[2] / 255;
    _self.color.alpha = color[3];

    anchor.style.backgroundColor = 'rgb(' + color[0] + ',' + color[1] + ',' 
        + color[2] + ')';
    anchor.style.opacity = color[3];

    anchor.href = '#';
    anchor.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp];
    anchor.appendChild(doc.createTextNode(lang.inputs.colorInputAnchorContent));
    anchor.addEventListener('click', ev_input_click, false);

    _self.input.replaceChild(anchor, _self.input.firstChild);
  };

  /**
   * The <code>click</code> event handler for the color input element. This 
   * function shows/hides the Color Mixer panel.
   *
   * @private
   * @param {Event} ev The DOM Event object.
   */
  function ev_input_click (ev) {
    ev.preventDefault();

    if (!colormixer) {
      colormixer = gui.app.extensions.colormixer;
    }

    if (!colormixer.targetInput || colormixer.targetInput.id !== _self.id) {
      colormixer.show({
          id: _self.id,
          configProperty: _self.configProperty,
          configGroup: _self.configGroup,
          configGroupRef: _self.configGroupRef,
          show: colormixer_show,
          hide: colormixer_hide
        }, _self.color);

    } else {
      colormixer.hide();
    }
  };

  /**
   * The color mixer <code>show</code> event handler. This function is invoked 
   * when the color mixer is shown.
   * @private
   */
  function colormixer_show () {
    var classActive = ' ' + gui.classPrefix + 'colorInputActive',
        elemActive = _self.input.className.indexOf(classActive) !== -1;

    if (!elemActive) {
      _self.input.className += classActive;
    }
  };

  /**
   * The color mixer <code>hide</code> event handler. This function is invoked 
   * when the color mixer is hidden.
   * @private
   */
  function colormixer_hide () {
    var classActive = ' ' + gui.classPrefix + 'colorInputActive',
        elemActive = _self.input.className.indexOf(classActive) !== -1;

    if (elemActive) {
      _self.input.className = _self.input.className.replace(classActive, '');
    }
  };

  /**
   * Update color. This method allows the change of the color values associated 
   * to the current color input.
   *
   * <p>This method is used by the color picker tool and by the global GUI 
   * <code>configChange</code> application event handler.
   *
   * @param {Object} color The new color values. The object must have four 
   * properties: <var>red</var>, <var>green</var>, <var>blue</var> and 
   * <var>alpha</var>. All values must be between 0 and 1.
   */
  this.updateColor = function (color) {
    var anchor = _self.input.firstChild.style;

    anchor.opacity         = color.alpha;
    anchor.backgroundColor = 'rgb(' + MathRound(color.red   * 255) + ',' +
                                      MathRound(color.green * 255) + ',' +
                                      MathRound(color.blue  * 255) + ')';
    _self.color.red   = color.red;
    _self.color.green = color.green;
    _self.color.blue  = color.blue;
    _self.color.alpha = color.alpha;
  };

  init();
};

// vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:

