/*  Prototype-UI, version trunk

 *

 *  Prototype-UI is freely distributable under the terms of an MIT-style license.

 *  For details, see the PrototypeUI web site: http://www.prototype-ui.com/

 *

 *--------------------------------------------------------------------------*/



if(typeof Prototype == 'undefined' || !Prototype.Version.match("1.6"))

  throw("Prototype-UI library require Prototype library >= 1.6.0");



if (Prototype.Browser.WebKit) {

  Prototype.Browser.WebKitVersion = parseFloat(navigator.userAgent.match(/AppleWebKit\/([\d\.\+]*)/)[1]);

  Prototype.Browser.Safari2 = (Prototype.Browser.WebKitVersion < 420);

}



if (Prototype.Browser.IE) {

  Prototype.Browser.IEVersion = parseFloat(navigator.appVersion.split(';')[1].strip().split(' ')[1]);

  Prototype.Browser.IE6 =  Prototype.Browser.IEVersion == 6;

  Prototype.Browser.IE7 =  Prototype.Browser.IEVersion == 7;

}



Prototype.falseFunction = function() { return false };

Prototype.trueFunction  = function() { return true  };



/*

Namespace: UI



  Introduction:

    Prototype-UI is a library of user interface components based on the Prototype framework.

    Its aim is to easilly improve user experience in web applications.



    It also provides utilities to help developers.



  Guideline:

    - Prototype conventions are followed

    - Everything should be unobstrusive

    - All components are themable with CSS stylesheets, various themes are provided



  Warning:

    Prototype-UI is still under deep development, this release is targeted to developers only.

    All interfaces are subjects to changes, suggestions are welcome.



    DO NOT use it in production for now.



  Authors:

    - Sébastien Gruhier, <http://www.xilinus.com>

    - Samuel Lebeau, <http://gotfresh.info>

*/



var UI = {

  Abstract: { },

  Ajax: { }

};

Object.extend(Class.Methods, {

  extend: Object.extend.methodize(),



  addMethods: Class.Methods.addMethods.wrap(function(proceed, source) {

    // ensure we are not trying to add null or undefined

    if (!source) return this;



    // no callback, vanilla way

    if (!source.hasOwnProperty('methodsAdded'))

      return proceed(source);



    var callback = source.methodsAdded;

    delete source.methodsAdded;

    proceed(source);

    callback.call(source, this);

    source.methodsAdded = callback;



    return this;

  }),



  addMethod: function(name, lambda) {

    var methods = {};

    methods[name] = lambda;

    return this.addMethods(methods);

  },



  method: function(name) {

    return this.prototype[name].valueOf();

  },



  classMethod: function() {

    $A(arguments).flatten().each(function(method) {

      this[method] = (function() {

        return this[method].apply(this, arguments);

      }).bind(this.prototype);

    }, this);

    return this;

  },



  // prevent any call to this method

  undefMethod: function(name) {

    this.prototype[name] = undefined;

    return this;

  },



  // remove the class' own implementation of this method

  removeMethod: function(name) {

    delete this.prototype[name];

    return this;

  },



  aliasMethod: function(newName, name) {

    this.prototype[newName] = this.prototype[name];

    return this;

  },



  aliasMethodChain: function(target, feature) {

    feature = feature.camelcase();



    this.aliasMethod(target+"Without"+feature, target);

    this.aliasMethod(target, target+"With"+feature);



    return this;

  }

});

Object.extend(Number.prototype, {

  // Snap a number to a grid

  snap: function(round) {

    return parseInt(round == 1 ? this : (this / round).floor() * round);

  }

});

/*

Interface: String



*/



Object.extend(String.prototype, {

  camelcase: function() {

    var string = this.dasherize().camelize();

    return string.charAt(0).toUpperCase() + string.slice(1);

  },



  /*

    Method: makeElement

      toElement is unfortunately already taken :/



      Transforms html string into an extended element or null (when failed)



      > '<li><a href="#">some text</a></li>'.makeElement(); // => LI href#

      > '<img src="foo" id="bar" /><img src="bar" id="bar" />'.makeElement(); // => IMG#foo (first one)



    Returns:

      Extended element



  */

  makeElement: function() {

    var wrapper = new Element('div'); wrapper.innerHTML = this;

    return wrapper.down();

  }

});

Object.extend(Array.prototype, {

  empty: function() {

    return !this.length;

  },



  extractOptions: function() {

    return this.last().constructor === Object ? this.pop() : { };

  },



  removeAt: function(index) {

    var object = this[index];

    this.splice(index, 1);

    return object;

  },



  remove: function(object) {

    var index;

    while ((index = this.indexOf(object)) != -1)

      this.removeAt(index);

    return object;

  },



  insert: function(index) {

    var args = $A(arguments);

    args.shift();

    this.splice.apply(this, [ index, 0 ].concat(args));

    return this;

  }

});

Element.addMethods({

  getScrollDimensions: function(element) {

    return {

      width:  element.scrollWidth,

      height: element.scrollHeight

    }

  },



  getScrollOffset: function(element) {

    return Element._returnOffset(element.scrollLeft, element.scrollTop);

  },



  setScrollOffset: function(element, offset) {

    element = $(element);

    if (arguments.length == 3)

      offset = { left: offset, top: arguments[2] };

    element.scrollLeft = offset.left;

    element.scrollTop  = offset.top;

    return element;

  },



  // returns "clean" numerical style (without "px") or null if style can not be resolved

  // or is not numeric

  getNumStyle: function(element, style) {

    var value = parseFloat($(element).getStyle(style));

    return isNaN(value) ? null : value;

  },



  // by Tobie Langel (http://tobielangel.com/2007/5/22/prototype-quick-tip)

  appendText: function(element, text) {

    element = $(element);

    text = String.interpret(text);

    element.appendChild(document.createTextNode(text));

    return element;

  }

});



document.whenReady = function(callback) {

  if (document.loaded)

    callback.call(document);

  else

    document.observe('dom:loaded', callback);

};



Object.extend(document.viewport, {

  // Alias this method for consistency

  getScrollOffset: document.viewport.getScrollOffsets,



  setScrollOffset: function(offset) {

    Element.setScrollOffset(Prototype.Browser.WebKit ? document.body : document.documentElement, offset);

  },



  getScrollDimensions: function() {

    return Element.getScrollDimensions(Prototype.Browser.WebKit ? document.body : document.documentElement);

  }

});

/*

Interface: UI.Options

  Mixin to handle *options* argument in initializer pattern.



  TODO: find a better example than Circle that use an imaginary Point function,

        this example should be used in tests too.



  It assumes class defines a property called *options*, containing

  default options values.



  Instances hold their own *options* property after a first call to <setOptions>.



  Example:

    > var Circle = Class.create(UI.Options, {

    >

    >   // default options

    >   options: {

    >     radius: 1,

    >     origin: Point(0, 0)

    >   },

    >

    >   // common usage is to call setOptions in initializer

    >   initialize: function(options) {

    >     this.setOptions(options);

    >   }

    > });

    >

    > var circle = new Circle({ origin: Point(1, 4) });

    >

    > circle.options

    > // => { radius: 1, origin: Point(1,4) }



  Accessors:

    There are builtin methods to automatically write options accessors. All those

    methods can take either an array of option names nor option names as arguments.

    Notice that those methods won't override an accessor method if already present.



     * <optionsGetter> creates getters

     * <optionsSetter> creates setters

     * <optionsAccessor> creates both getters and setters



    Common usage is to invoke them on a class to create accessors for all instances

    of this class.

    Invoking those methods on a class has the same effect as invoking them on the class prototype.

    See <classMethod> for more details.



    Example:

    > // Creates getter and setter for the "radius" options of circles

    > Circle.optionsAccessor('radius');

    >

    > circle.setRadius(4);

    > // 4

    >

    > circle.getRadius();

    > // => 4 (circle.options.radius)



  Inheritance support:

    Subclasses can refine default *options* values, after a first instance call on setOptions,

    *options* attribute will hold all default options values coming from the inheritance hierarchy.

*/



(function() {

  UI.Options = {

    methodsAdded: function(klass) {

      klass.classMethod($w(' setOptions allOptions optionsGetter optionsSetter optionsAccessor '));

    },



    // Group: Methods



    /*

      Method: setOptions

        Extends object's *options* property with the given object

    */

    setOptions: function(options) {

      if (!this.hasOwnProperty('options'))

        this.options = this.allOptions();



      this.options = Object.extend(this.options, options || {});

    },



    /*

      Method: allOptions

        Computes the complete default options hash made by reverse extending all superclasses

        default options.



        > Widget.prototype.allOptions();

    */

    allOptions: function() {

      var superclass = this.constructor.superclass, ancestor = superclass && superclass.prototype;

      return (ancestor && ancestor.allOptions) ?

          Object.extend(ancestor.allOptions(), this.options) :

          Object.clone(this.options);

    },



    /*

      Method: optionsGetter

        Creates default getters for option names given as arguments.

        With no argument, creates getters for all option names.

    */

    optionsGetter: function() {

      addOptionsAccessors(this, arguments, false);

    },



    /*

      Method: optionsSetter

        Creates default setters for option names given as arguments.

        With no argument, creates setters for all option names.

    */

    optionsSetter: function() {

      addOptionsAccessors(this, arguments, true);

    },



    /*

      Method: optionsAccessor

        Creates default getters/setters for option names given as arguments.

        With no argument, creates accessors for all option names.

    */

    optionsAccessor: function() {

      this.optionsGetter.apply(this, arguments);

      this.optionsSetter.apply(this, arguments);

    }

  };



  // Internal

  function addOptionsAccessors(receiver, names, areSetters) {

    names = $A(names).flatten();



    if (names.empty())

      names = Object.keys(receiver.allOptions());



    names.each(function(name) {

      var accessorName = (areSetters ? 'set' : 'get') + name.camelcase();



      receiver[accessorName] = receiver[accessorName] || (areSetters ?

        // Setter

        function(value) { return this.options[name] = value } :

        // Getter

        function()      { return this.options[name]         });

    });

  }

})();

/*

Namespace: CSS



  Utility functions for CSS/StyleSheet files access



  Authors:

    - Sébastien Gruhier, <http://www.xilinus.com>

    - Samuel Lebeau, <http://gotfresh.info>

*/



var CSS = (function() {

  // Code based on:

  //   - IE5.5+ PNG Alpha Fix v1.0RC4 (c) 2004-2005 Angus Turnbull http://www.twinhelix.com

  //   - Whatever:hover - V2.02.060206 - hover, active & focus (c) 2005 - Peter Nederlof * Peterned - http://www.xs4all.nl/~peterned/

  function fixPNG() {

   parseStylesheet.apply(this, $A(arguments).concat(fixRule));

  };



  function parseStylesheet() {

    var patterns = $A(arguments);

    var method = patterns.pop();



    // To avoid flicking background

    //document.execCommand("BackgroundImageCache", false, true);

    // Parse all document stylesheets

    var styleSheets = $A(document.styleSheets);

    if (patterns.length > 1) {

      styleSheets = styleSheets.select(function(css) {

        return patterns.any(function(pattern) {

          return css.href && css.href.match(pattern)

          });

      });

    }

    styleSheets.each(function(styleSheet) {fixStylesheet.call(this, styleSheet, method)});

  };



  // Fixes a stylesheet

  function fixStylesheet(stylesheet, method) {

    // Parse import files

    if (stylesheet.imports)

      $A(stylesheet.imports).each(fixStylesheet);



    var href = stylesheet.href || document.location.href;

    var docPath = href.substr(0, href.lastIndexOf('/'));

	  // Parse all CSS Rules

    $A(stylesheet.rules || stylesheet.cssRules).each(function(rule) { method.call(this, rule, docPath) });

  };



  var filterPattern = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="#{src}",sizingMethod="#{method}")';



  // Fixes a rule if it has a PNG background

  function fixRule(rule, docPath) {

    var bgImg = rule.style.backgroundImage;

    // Rule with PNG background image

    if (bgImg && bgImg != 'none' && bgImg.match(/^url[("']+(.*\.png)[)"']+$/i)) {

      var src = RegExp.$1;

      var bgRepeat = rule.style.backgroundRepeat;

      // Relative path

      if (src[0] != '/')

        src = docPath + "/" + src;

      // Apply filter

      rule.style.filter = filterPattern.interpolate({

        src:    src,

        method: bgRepeat == "no-repeat" ? "crop" : "scale" });

      rule.style.backgroundImage = "none";

    }

  };



  var preloadedImages = new Hash();



  function preloadRule(rule, docPath) {

    var bgImg = rule.style.backgroundImage;

    if (bgImg && bgImg != 'none'  && bgImg != 'initial' ) {

      if (!preloadedImages.get(bgImg)) {

        bgImg.match(/^url[("']+(.*)[)"']+$/i);

        var src = RegExp.$1;

        // Relative path

        if (!(src[0] == '/' || src.match(/^file:/) || src.match(/^https?:/)))

          src = docPath + "/" + src;

        preloadedImages.set(bgImg, true);

        var image = new Image();

        image.src = src;

      }

    }

  }



  return {

    /*

       Method: fixPNG

         Fix transparency of PNG background of document stylesheets.

         (only on IE version<7, otherwise does nothing)



         Warning: All png background will not work as IE filter use for handling transparency in PNG

         is not compatible with all background. It does not support top/left position (so no CSS sprite)



         I recommend to create a special CSS file with png that needs to be fixed and call CSS.fixPNG on this CSS



         Examples:

          > CSS.fixPNG() // To fix all css

          >

          > CSS.fixPNG("mac_shadow.css") // to fix all css files with mac_shadow.css so mainly only on file

          >

          > CSS.fixPNG("shadow", "vista"); // To fix all css files with shadow or vista in their names



       Parameters

         patterns: (optional) list of pattern to filter css files

    */

    fixPNG: (Prototype.Browser.IE && Prototype.Browser.IEVersion < 7) ? fixPNG : Prototype.emptyFunction,



    // By Tobie Langel (http://tobielangel.com)

    //   inspired by http://yuiblog.com/blog/2007/06/07/style/

    addRule: function(css, backwardCompatibility) {

      if (backwardCompatibility) css = css + '{' + backwardCompatibility + '}';

      var style = new Element('style', { type: 'text/css', media: 'screen' });

      $(document.getElementsByTagName('head')[0]).insert(style);

      if (style.styleSheet) style.styleSheet.cssText = css;

      else style.appendText(css);

      return style;

    },



    preloadImages: function() {

      parseStylesheet.apply(this, $A(arguments).concat(preloadRule));

    }

  };

})();

UI.Benchmark = {

  benchmark: function(lambda, iterations) {

    var date = new Date();

    (iterations || 1).times(lambda);

    return (new Date() - date) / 1000;

  }

};

/*

  Group: Drag

    UI provides Element#enableDrag method that allow elements to fire drag-related events.



    Events fired:

      - drag:started : fired when a drag is started (mousedown then mousemove)

      - drag:updated : fired when a drag is updated (mousemove)

      - drag:ended   : fired when a drag is ended (mouseup)



    Notice it doesn't actually move anything, drag behavior has to be implemented

    by attaching handlers to drag events.



    Drag-related informations:

      event.memo contains useful information about the drag occuring:

        - dx         : difference between pointer x position when drag started

                       and actual x position

        - dy         : difference between pointer y position when drag started

                       and actual y position

        - mouseEvent : the original mouse event, useful to know pointer absolute position,

                       or if key were pressed.



    Example, with event handling for a specific element:



    > // Now "resizable" will fire drag-related events

    > $('resizable').enableDrag();

    >

    > // Let's observe them

    > $('resizable').observe('drag:started', function(event) {

    >   this._dimensions = this.getDimensions();

    > }).observe('drag:updated', function(event) {

    >   var drag = event.memo;

    >

    >   this.setStyle({

    >     width:  this._dimensions.width  + drag.dx + 'px',

    >     height: this._dimensions.height + drag.dy + 'px'

    >   });

    > });



    Example, with event delegating on the whole document:



    > // All elements in the having the "draggable" class name will fire drag events.

    > $$('.draggable').invoke('enableDrag');

    >

    > document.observe('drag:started', function(event) {

    >   UI.logger.info('trying to drag ' + event.element().id);

    > }):

*/

(function() {

  var initPointer, currentDraggable, dragging;



  document.observe('mousedown', onMousedown);



  function onMousedown(event) {

    var draggable = event.findElement('[ui:draggable="true"]');



    if (draggable) {

      // prevent default browser action

      event.stop();

      currentDraggable = draggable;

      initPointer = event.pointer();



      document.observe("mousemove", onMousemove)

              .observe("mouseup",   onMouseup);

    }

  };



  function onMousemove(event) {

    event.stop();



    if (dragging)

      fire('drag:updated', event);

    else {

      dragging = true;

      fire('drag:started', event);

    }

  };



  function onMouseup(event) {

    document.stopObserving('mousemove', onMousemove)

            .stopObserving('mouseup',   onMouseup);



    if (dragging) {

      dragging = false;

      fire('drag:ended', event);

    }

  };



  function fire(eventName, mouseEvent) {

    var pointer = mouseEvent.pointer();



    currentDraggable.fire(eventName, {

      dx: pointer.x - initPointer.x,

      dy: pointer.y - initPointer.y,

      mouseEvent: mouseEvent

    })

  };



  Element.addMethods({

    enableDrag: function(element) {

      element = $(element);

      element.writeAttribute('ui:draggable', 'true');

      return element;

    },



    disableDrag: function(element){

      element = $(element);

      element.writeAttribute('ui:draggable', null);

      return element;

    },



    isDraggable: function(element) {

      return $(element).readAttribute('ui:draggable') == 'true';

    }

  });

})();

/*

  Class: UI.IframeShim

    Handles IE6 bug when <select> elements overlap other elements with higher z-index



  Example:

    > // creates iframe and positions it under "contextMenu" element

    > this.iefix = new UI.IframeShim().positionUnder('contextMenu');

    > ...

    > document.observe('click', function(e) {

    >   if (e.isLeftClick()) {

    >     this.contextMenu.hide();

    >

    >     // hides iframe when left click is fired on a document

    >     this.iefix.hide();

    >   }

    > }.bind(this))

    > ...

*/



// TODO:

//

// Maybe it makes sense to bind iframe to an element

// so that it automatically calls positionUnder method

// when the element it's binded to is moved or resized

// Not sure how this might affect overall perfomance...



UI.IframeShim = Class.create(UI.Options, {



  /*

    Method: initialize

    Constructor



      Creates iframe shim and appends it to the body.

      Note that this method does not perform proper positioning and resizing of an iframe.

      To do that use positionUnder method



    Returns:

      this

  */

  initialize: function() {

    this.element = new Element('iframe', {

      style: 'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);display:none',

      src: 'javascript:false;',

      frameborder: 0

    });

    $(document.body).insert(this.element);

  },



  /*

    Method: hide

      Hides iframe shim leaving its position and dimensions intact



    Returns:

      this

  */

  hide: function() {

    this.element.hide();

    return this;

  },



  /*

    Method: show

      Show iframe shim leaving its position and dimensions intact



    Returns:

      this

  */

  show: function() {

    this.element.show();

    return this;

  },



  /*

    Method: positionUnder

      Positions iframe shim under the specified element

      Sets proper dimensions, offset, zIndex and shows it

      Note that the element should have explicitly specified zIndex



    Returns:

      this

  */

  positionUnder: function(element) {

    var element = $(element),

        offset = element.cumulativeOffset(),

        dimensions = element.getDimensions(),

        style = {

          left: offset[0] + 'px',

          top: offset[1] + 'px',

          width: dimensions.width + 'px',

          height: dimensions.height + 'px',

          zIndex: element.getStyle('zIndex') - 1

        };

    this.element.setStyle(style).show();



    return this;

  },



  /*

    Method: setBounds

      Sets element's width, height, top and left css properties using 'px' as units



    Returns:

      this

  */

  setBounds: function(bounds) {

    for (prop in bounds) {

      bounds[prop] += 'px';

    }

    this.element.setStyle(bounds);

    return this;

  },



  /*

    Method: destroy

      Completely removes the iframe shim from the document



    Returns:

      this

  */

  destroy: function() {

    if (this.element)

      this.element.remove();



    return this;

  }

});

/*

Class: UI.Logger

*/



/*

  Group: Logging Facilities

    Prototype UI provides a facility to log message with levels.

    Levels are in order "debug", "info", "warn" and "error".



    As soon as the DOM is loaded, a default logger is present in UI.logger.



    This logger is :

    * an <ElementLogger> if $('log') is present

    * a <ConsoleLogger> if window.console is defined

    * a <MemLogger> otherwise



    See <AbstractLogger> to learn how to use it.



    Example:



    > UI.logger.warn('something bad happenned !');

*/



// Class: AbstractLogger



UI.Abstract.Logger = Class.create({

  /*

    Property: level

      The log level, default value is debug  <br/>

  */

  level: 'debug'

});



(function() {

  /*

    Method: debug

      Logs with "debug" level



    Method: info

      Logs with "info" level



    Method: warn

      Logs with "warn" level



    Method: error

      Logs with "error" level

  */

  var levels = $w(" debug info warn error ");



  levels.each(function(level, index) {

    UI.Abstract.Logger.addMethod(level, function(message) {

      // filter lower level messages

      if (index >= levels.indexOf(this.level))

        this._log({ level: level, message: message, date: new Date() });

    });

  });

})();



/*

  Class: NullLogger

    Does nothing

*/

UI.NullLogger = Class.create(UI.Abstract.Logger, {

  _log: Prototype.emptyFunction

});



/*

  Class: MemLogger

    Logs in memory



    Property: logs

      An array of logs, objects with "date", "level", and "message" properties

*/

UI.MemLogger = Class.create(UI.Abstract.Logger, {

  initialize: function() {

    this.logs = [ ];

  },



  _log: function(log) {

    this.logs.push(log);

  }

});



/*

  Class: ConsoleLogger

    Logs using window.console

*/

UI.ConsoleLogger = Class.create(UI.Abstract.Logger, {

  _log: function(log) {

    console[log.level || 'log'](log.message);

  }

});



/*

  Class: ElementLogger

    Logs in a DOM element

*/

UI.ElementLogger = Class.create(UI.Abstract.Logger, {

  /*

    Method: initialize

      Constructor, takes a DOM element to log into as argument

  */

  initialize: function(element) {

    this.element = $(element);

  },



  /*

    Property: format

      A format string, will be interpolated with "date", "level" and "message"



      Example:

        > "<p>(#{date}) #{level}: #{message}</p>"

  */

  format: '<p>(<span class="date">#{date}</span>) ' +

              '<span class="level">#{level}</span> : ' +

              '<span class="message">#{message}</span></p>',



  _log: function(log) {

    var entry = this.format.interpolate({

      level:   log.level.toUpperCase(),

      message: log.message.escapeHTML(),

      date:    log.date.toLocaleTimeString()

    });

    this.element.insert({ top: entry });

  }

});



document.observe('dom:loaded', function() {

  if ($('log'))             UI.logger = new UI.ElementLogger('log');

  else if (window.console)  UI.logger = new UI.ConsoleLogger();

  else                      UI.logger = new UI.MemLogger();

});

/*

Class: UI.Shadow

  Add shadow around a DOM element. The element MUST BE in ABSOLUTE position.



  Shadow can be skinned by CSS (see mac_shadow.css or drop_shadow.css).

  CSS must be included to see shadow.



  A shadow can have two states: focused and blur.

  Shadow shifts are set in CSS file as margin and padding of shadow_container to add visual information.



  Example:

    > new UI.Shadow("element_id");

*/

UI.Shadow = Class.create(UI.Options, {

  options: {

    theme: "alphacube",

    focus: false,

    zIndex: 100

  },



  /*

    Method: initialize

      Constructor, adds shadow elements to the DOM if element is in the DOM.

      Element MUST BE in ABSOLUTE position.



    Parameters:

      element - DOM element

      options - Hashmap of options

        - theme (default: mac_shadow)

        - focus (default: true)

        - zIndex (default: 100)



    Returns:

      this

  */

  initialize: function(element, options) {

    this.setOptions(options);



    this.element = $(element);

    this.create();

    if (Object.isElement(this.element.parentNode))

      this.render();

  },



  /*

    Method: destroy

      Destructor, removes elements from the DOM

  */

  destroy: function() {

    if (this.shadow.parentNode)

      this.remove();

  },



  // Group: Size and Position

  /*

    Method: setPosition

      Sets top/left shadow position in pixels



    Parameters:

      top -  top position in pixel

      left - left position in pixel



    Returns:

      this

  */

  setPosition: function(top, left) {

    if (this.shadowSize) {

      var shadowStyle = this.shadow.style;



      shadowStyle.top  = parseInt(top)  - this.shadowSize.top  + this.shadowShift.top + 'px';

      shadowStyle.left = parseInt(left) - this.shadowSize.left + this.shadowShift.left+ 'px';

    }

    return this;

  },



  /*

    Method: setSize

      Sets width/height shadow in pixels



    Parameters:

      width  - width in pixel

      height - height in pixel



    Returns:

      this

  */

  setSize: function(width, height) {

    if (this.shadowSize) {

      var w = parseInt(width) + this.shadowSize.width - this.shadowShift.width + "px";

      this.shadow.style.width = w;

      var h =  parseInt(height) - this.shadowShift.height + "px";



      // this.shadowContents[1].style.height = h;

      this.shadowContents[1].childElements().each(function(e) {e.style.height = h});

      this.shadowContents.each(function(item){ item.style.width = w});

    }

    return this;

  },



  /*

    Method: setBounds

      Sets shadow bounds in pixels



    Parameters:

      bounds - an Hash {top:, left:, width:, height:}



    Returns:

      this

  */

  setBounds: function(bounds) {

    return this.setPosition(bounds.top, bounds.left).setSize(bounds.width, bounds.height);

  },



  /*

    Method: setZIndex

      Sets shadow z-index



    Parameters:

      zIndex - zIndex value



    Returns:

      this

  */

  setZIndex: function(zIndex) {

    this.shadow.style.zIndex = zIndex;

    return this;

  },



   // Group: Render

  /*

    Method: show

      Displays shadow



    Returns:

      this

  */

  show: function() {

   this.shadow.show();

   return this;

  },



  /*

    Method: hide

      Hides shadow



    Returns:

      this

  */

  hide: function() {

    this.shadow.hide();

    return this;

  },



  /*

    Method: remove

      Removes shadow from the DOM



    Returns:

      this

  */

  remove: function() {

    this.shadow.remove();

    return this;

  },



  // Group: Status

  /*

    Method: focus

      Focus shadow.



      Change shadow shift. Shift values are set in CSS file as margin and padding of shadow_container

      to add visual information of shadow status.



    Returns:

      this

  */

  focus: function() {

    this.options.focus = true;

    this.updateShadow();

    return this;

  },



  /*

    Method: blur

      Blurs shadow.



      Change shadow shift. Shift values are set in CSS file as margin and padding of shadow_container

      to add visual information of shadow status.



    Returns:

      this

  */

  blur: function() {

    this.options.focus = false;

    this.updateShadow();

    return this;

  },



  // Private Functions

  // Adds shadow elements to DOM, computes shadow size and displays it

  render: function() {

    if (this.element.parentNode && !Object.isElement(this.shadow.parentNode)) {

      this.element.parentNode.appendChild(this.shadow);

      this.computeSize();

      this.setBounds(Object.extend(this.element.getDimensions(), this.getElementPosition()));

      this.shadow.show();

    }

    return this;

  },



  // Creates HTML elements without inserting them into the DOM

  create: function() {

    var zIndex = this.element.getStyle('zIndex');

    if (!zIndex)

      this.element.setStyle({zIndex: this.options.zIndex});

    zIndex = (zIndex || this.options.zIndex) - 1;



    this.shadowContents = new Array(3);

    this.shadowContents[0] = new Element("div")

      .insert(new Element("div", {className: "shadow_center_wrapper"}).insert(new Element("div", {className: "n_shadow"})))

      .insert(new Element("div", {className: "shadow_right ne_shadow"}))

      .insert(new Element("div", {className: "shadow_left nw_shadow"}));



    this.shadowContents[1] = new Element("div")

      .insert(new Element("div", {className: "shadow_center_wrapper c_shadow"}))

      .insert(new Element("div", {className: "shadow_right e_shadow"}))

      .insert(new Element("div", {className: "shadow_left w_shadow"}));

    this.centerElements = this.shadowContents[1].childElements();



    this.shadowContents[2] = new Element("div")

      .insert(new Element("div", {className: "shadow_center_wrapper"}).insert(new Element("div", {className: "s_shadow"})))

      .insert(new Element("div", {className: "shadow_right se_shadow"}))

      .insert(new Element("div", {className: "shadow_left sw_shadow"}));



    this.shadow = new Element("div", {className: "shadow_container " + this.options.theme,

                                      style: "position:absolute; top:-10000px; left:-10000px; display:none; z-index:" + zIndex })

      .insert(this.shadowContents[0])

      .insert(this.shadowContents[1])

      .insert(this.shadowContents[2]);

  },



  // Compute shadow size

  computeSize: function() {

    if (this.focusedShadowShift)

      return;

    this.shadow.show();



    // Trick to get shadow shift designed in CSS as padding

    var content = this.shadowContents[1].select("div.c_shadow").first();

    this.unfocusedShadowShift = {};

    this.focusedShadowShift = {};



    $w("top left bottom right").each(function(pos) {this.unfocusedShadowShift[pos] = content.getNumStyle("padding-" + pos) || 0}.bind(this));

    this.unfocusedShadowShift.width  = this.unfocusedShadowShift.left + this.unfocusedShadowShift.right;

    this.unfocusedShadowShift.height = this.unfocusedShadowShift.top + this.unfocusedShadowShift.bottom;



    $w("top left bottom right").each(function(pos) {this.focusedShadowShift[pos] = content.getNumStyle("margin-" + pos) || 0}.bind(this));

    this.focusedShadowShift.width  = this.focusedShadowShift.left + this.focusedShadowShift.right;

    this.focusedShadowShift.height = this.focusedShadowShift.top + this.focusedShadowShift.bottom;



    this.shadowShift = this.options.focus ? this.focusedShadowShift : this.unfocusedShadowShift;



    // Get shadow size

    this.shadowSize  = {top:    this.shadowContents[0].childElements()[1].getNumStyle("height"),

                        left:   this.shadowContents[0].childElements()[1].getNumStyle("width"),

                        bottom: this.shadowContents[2].childElements()[1].getNumStyle("height"),

                        right:  this.shadowContents[0].childElements()[2].getNumStyle("width")};



    this.shadowSize.width  = this.shadowSize.left + this.shadowSize.right;

    this.shadowSize.height = this.shadowSize.top + this.shadowSize.bottom;



    // Remove padding

    content.setStyle("padding:0; margin:0");

    this.shadow.hide();

  },



  // Update shadow size (called when it changes from focused to blur and vice-versa)

  updateShadow: function() {

    this.shadowShift = this.options.focus ? this.focusedShadowShift : this.unfocusedShadowShift;

    var shadowStyle = this.shadow.style, pos  = this.getElementPosition(), size = this.element.getDimensions();



    shadowStyle.top  =  pos.top    - this.shadowSize.top   + this.shadowShift.top   + 'px';

    shadowStyle.left  = pos.left   - this.shadowSize.left  + this.shadowShift.left  + 'px';

    shadowStyle.width = size.width + this.shadowSize.width - this.shadowShift.width + "px";

    var h = size.height - this.shadowShift.height + "px";

    this.centerElements.each(function(e) {e.style.height = h});



    var w = size.width + this.shadowSize.width - this.shadowShift.width+ "px";

    this.shadowContents.each(function(item) { item.style.width = w });

  },



  // Get element position in integer values

  getElementPosition: function() {

    return {top: this.element.getNumStyle("top"), left: this.element.getNumStyle("left")}

  }

});



// Set theme and focus as read/write accessor

document.whenReady(function() { CSS.fixPNG("shadow") });

/*

Class: UI.Window

  Main class to handle windows inside a web page.



  Example:

    > new UI.Window({ theme: 'bluglighting' }).show()

*/





/*

<div class="STitle">Options</div>

*/



UI.Window = Class.create(UI.Options, {

  // Group: Options

  options: {



    // Property: theme

    //   window theme, uses the window manager theme as default

    theme:         null,



    // Property: shadowTheme

    //   window shadow theme, uses the window manager one as default

    //   Only useful if <shadow> options is true, see <UI.Shadow> for details

    shadowTheme:   null,



    // Property: id

    //   id ot the window, generated by default

    id:            null,



    // Property: windowManager

    //   window manager that manages this window,

    //   uses UI.defaultWM as default

    windowManager: null,



    top:           200,

    left:          800,

    width:         368,

    height:        470,

    minHeight:     null,

    minWidth:      null,

    maxHeight:     null,

    maxWidth:      null,

    altitude:      "front",



    // Property: resizable

    //   true by default

    resizable:     false,



    // Property: draggable

    //   true by default

    draggable:     true,



    // Property: wired

    //   draw wires around window when dragged, false by default

    wired:         false,



    // Property: show

    //   Function used to show the window, default is Element.show

    show: Element.show,



    // Property: hide

    //   Function used to hide the window, default is Element.hide.

    hide: Element.hide,



    // Property: superflousEffects

    //   uses superflous effects when resizing or moving window.

    //   it's true if Scriptaculous' Effect is defined, false otherwise

    superflousEffects: !Object.isUndefined(window.Effect),



    // Property: shadow

    //   draw shadow around the window, default is false

    shadow:            false,



    // Property: activeOnClick

    //   When set to true, a click on an blurred window content activates it,

    //   default is true

    activeOnClick:     true,



    // Grid

    gridX:  1,

    gridY:  1,



    // Buttons and actions (false to disable)



    // Property: close

    //   Window method name as string, or false to disable close button

    //   Default is 'destroy'

    close:    'destroy',



    // Property: minimize

    //   Window method name as string, or false to disable minimize button

    //   Default is 'toggleFold'

    minimize: 'toggleFold',



    // Property: maximize

    //   Window method name as string, or false to disable maximize button

    //   Default is 'toggleMaximize'

    maximize: 'toggleMaximize'

  },



  // Group: Attributes



  /*

    Property: id

      DOM id of the window's element



    Property: element

      DOM element containing the window



    Property: windowManager

      Window manager that manages the window



    Property: content

      Window content element



    Property: header

      Window header element



    Property: footer

      Window footer element



    Property: visible

      true if window is visible



    Property: focused

      true if window is focused



    Property: folded

      true if window is folded



    Property: maximized

      true if window is maximized

  */



  /*

    Group: Events

    List of events fired by a window

  */



  /*

    Property: created

      Fired after creating the window



    Property: destroyed

      Fired after destroying the window



    Property: showing

      Fired when showing a window



    Property: shown

      Fired after showing effect



    Property: hiding

      Fired when hiding a window



    Property: hidden

      Fired after hiding effect



    Property: focused

      Fired after focusing the window



    Property: blurred

      Fired after bluring the window



    Property: maximized

      Fired after maximizing the window



    Property: restored

      Fired after restoring the window from its maximized state



    Property: fold

      Fired after unfolding the window



    Property: unfold

      Fired after folding the window



    Property: altitude:changed

      Fired when window altitude has changed (z-index)



    Property: size:changed

      Fired when window size has changed



    Property: position:changed

      Fired when window position has changed



    Property: move:started

      Fired when user has started a moving a window, position:changed are then fired continously



    Property: move:ended

      Fired when user has finished moving a window



    Property: resize:started

      Fired when user has started resizing window, size:changed are then fired continuously



    Property: resize:ended

      Fired when user has finished resizing window



  */



  // Group: Contructor



  /*

    Method: initialize

      Constructor, should not be called directly, it's called by new operator (new Window())

      The window is not open and nothing has been added to the DOM yet



    Parameters:

      options - (Hash) list of optional parameters



    Returns:

      this

  */

  initialize: function(options) {

    this.setOptions(options);

    this.windowManager = this.options.windowManager || UI.defaultWM;

    this.create();

    this.id = this.element.id;

    this.windowManager.register(this);

    this.render();

    if (this.options.activeOnClick)

      this.overlay.setStyle({ zIndex: this.lastZIndex + 1 }).show();

  },



  /*

    Method: destroy

      Destructor, closes window, cleans up DOM and memory

  */

  destroy: function($super) {

    this.hide();

    if (this.centerOptions)

      Event.stopObserving(this.windowManager.scrollContainer, "scroll", this.centerOptions.handler);

    this.windowManager.unregister(this);

    this.fire('destroyed');

  },



  // Group: Event handling



  /*

    Method: fire

      Fires a window custom event automatically namespaced in "window:" (see Prototype custom events).

      The memo object contains a "window" property referring to the window.



    Example:

      > UI.Window.addMethods({

      >   iconify: function() {

      >     // ... your iconifying code here ...

      >     this.fire('iconified');

      >     // chain friendly

      >     return this;

      >   }

      > });

      >

      > document.observe('window:iconified', function(event) {

      >   alert("Window with id " + event.memo.window.id + " has just been iconified");

      > });



    Parameters:

      eventName - an event name

      memo - a memo object



    Returns:

      fired event

  */

  fire: function(eventName, memo) {

    memo = memo || { };

    memo.window = this;

    return this.element.fire('window:' + eventName, memo);

  },



   /*

     Method: observe

       Observe a window event with a handler function automatically bound to the window



     Parameters:

       eventName - an event name

       handler - a handler function



     Returns:

       this

  */

  observe: function(eventName, handler) {

    this.element.observe('window:' + eventName, handler.bind(this));

    return this;

  },





  // Group: Actions



  /*

    Method: show

      Opens the window (appends it to the DOM)



    Parameters:

      modal - open the window in a modal mode (default false)



    Returns:

      this

 */

  show: function(modal) {

    if (this.visible) return this;



    this.fire('showing');

    this.effect('show');



    if (modal) {

      this.windowManager.startModalSession(this);

      this.modalSession = true;

    }



    this.addElements();

    this.visible = true;



    new PeriodicalExecuter(function(executer) {

      if (!this.element.visible()) return;

      this.fire('shown');

      executer.stop();

    }.bind(this), 0.1);



    return this;

  },



  /*

    Method: hide

       Hides the window, (removes it from the DOM)



     Returns:

       this

  */

  hide: function() {

    if (!this.visible) return this;



    this.fire('hiding');

    this.effect('hide');



    if (this.modalSession) {

      this.windowManager.endModalSession(this);

      this.modalSession = false;

    }



    this.windowManager.hide(this);



    new PeriodicalExecuter(function(executer) {

      if (this.element.visible()) return;

      this.visible = false;

      this.element.remove();

      this.fire('hidden');

      executer.stop();

    }.bind(this), 0.1);



    return this;

  },



  close: function() {

    return this.action('close');

  },



  /*

    Method: activate

      Brings window to the front and sets focus on it



     Returns:

       this

  */

  activate: function() {

    return this.bringToFront().focus();

  },



  /*

    Method: bringToFront

      Brings window to the front (but does not set focus on it)



     Returns:

       this

  */

  bringToFront: function() {

    return this.setAltitude('front');

  },



  /*

    Method: sendToBack

      Sends window to the back (without changing its focus)



     Returns:

       this

  */

  sendToBack: function() {

    return this.setAltitude('back');

  },



  /*

    Method: focus

      Focuses the window (without bringing window to the front)



     Returns:

       this

  */

  focus: function() {

    if (this.focused) return this;



    this.windowManager.focus(this);

    // Hide the overlay that catch events

    this.overlay.hide();

    // Add focused class name

    this.element.addClassName(this.options.theme + '_focused');



    this.focused = true;

    this.fire('focused');

    return this;

  },



  /*

    Method: blur

      Blurs the window (without changing windows order)



     Returns:

       this

  */

  blur: function() {

    if (!this.focused) return this;



    this.windowManager.blur(this);

    this.element.removeClassName(this.options.theme + '_focused');



    // Show the overlay to catch events

    if (this.options.activeOnClick)

      this.overlay.setStyle({ zIndex: this.lastZIndex + 1 }).show();



    this.focused = false;

    this.fire('blurred');

    return this;

  },



  /*

    Method: maximize

      Maximizes window inside its viewport (managed by WindowManager)

      Makes window take full size of its viewport



     Returns:

       this

  */

  maximize: function() {

    if (this.maximized) return this;



    // Get bounds has to be before  this.windowManager.maximize for IE!! this.windowManager.maximize remove overflow

    // and it breaks this.getBounds()

    var bounds = this.getBounds();

    if (this.windowManager.maximize(this)) {

      this.disableButton('minimize').setResizable(false).setDraggable(false);



      this.activate();

      this.maximized = true;

      this.savedArea = bounds;

      var newBounds = Object.extend(this.windowManager.viewport.getDimensions(), { top: 0, left: 0 });

      this[this.options.superflousEffects && !Prototype.Browser.IE ? "morph" : "setBounds"](newBounds);

      this.fire('maximized');

      return this;

    }

  },



  /*

    Function: restore

      Restores a maximized window to its initial size



     Returns:

       this

  */

  restore: function() {

    if (!this.maximized) return this;



    if (this.windowManager.restore(this)) {

      this[this.options.superflousEffects  && !Prototype.Browser.IE ? "morph" : "setBounds"](this.savedArea);

      this.enableButton("minimize").setResizable(true).setDraggable(true);



      this.maximized = false;

      this.fire('restored');

      return this;

    }

  },



  /*

    Function: toggleMaximize

      Maximizes/Restores window inside it's viewport (managed by WindowManager)



     Returns:

       this

  */

  toggleMaximize: function() {

    return this.maximized ? this.restore() : this.maximize();

  },



  /*

    Function: adapt

      Adapts window size to fit its content



     Returns:

       this

  */

  adapt: function() {

    var dimensions = this.content.getScrollDimensions();

    if (this.options.superflousEffects)

      this.morph(dimensions, true);

    else

      this.setSize(dimensions.width, dimensions.height, true);

    return this;

  },



  /*

    Method: fold

      Folds window content



     Returns:

       this

  */

  fold: function() {

    if (!this.folded) {

      var size = this.getSize(true);

      this.folded = true;

      this.savedInnerHeight = size.height;



      if (this.options.superflousEffects)

        this.morph({ width: size.width, height: 0 }, true);

      else

        this.setSize(size.width, 0, true);



      this.setResizable(false);

      this.fire("fold");

    }

    return this;

  },



  /*

    Method: unfold

      Unfolds window content



     Returns:

       this

  */

  unfold: function() {

    if (this.folded) {

      var size = this.getSize(true);

      this.folded = false;



      if (this.options.superflousEffects)

        this.morph({ width: size.width, height: this.savedInnerHeight }, true);

      else

        this.setSize(size.width, this.savedInnerHeight, true);



      this.setResizable(true);

      this.fire("unfold");

    }

    return this;

  },



  /*

    Method: toggleFold

      Folds/Unfolds window content



     Returns:

       this

  */

  toggleFold: function() {

    return this.folded ? this.unfold() : this.fold();

  },



  /*

    Method: setHeader

      Sets window header, equivalent to this.header.update(...) but allows chaining



     Returns:

       this

  */

  setHeader: function(header) {

    this.header.update(header);

    return this;

  },



  /*

    Method: setContent

      Sets window content, equivalent to this.content.update(...) but allows chaining



     Returns:

       this

  */

  setContent: function(content) {

    this.content.update(content);

    return this;

  },



  /*

    Method: setFooter

      Sets window footer, equivalent to this.footer.update(...) but allows chaining



     Returns:

       this

  */

  setFooter: function(footer) {

    this.footer.update(footer);

    return this;

  },



  /*

    Method: setAjaxContent

      Sets window content using Ajax request



     Parameters:

        url - Ajax URL

        options - Ajax Updater options (see http://prototypejs.org/api/ajax/options and

          http://prototypejs.org/api/ajax/updater)



     Returns:

       this

  */

  setAjaxContent: function(url, options) {

    // bind all callbacks to the window

    Object.keys(options || { }).each(function(name) {

      if (Object.isFunction(options[name]))

        options[name] = options[name].bind(this);

    }, this);



    new Ajax.Updater(this.content, url, options);

    return this;

  },



  // Group: Size and Position



  /*

    Method: getPosition

      Returns top/left position of a window (in pixels)



     Returns:

       an Hash {top:, left:}

  */

  getPosition: function() {

    return { left: this.options.left, top: this.options.top };

  },



  /*

    Method: setPosition

      Sets top/left position of a window (in pixels)



    Parameters

      top:  top position in pixel

      left: left position in pixel



    Returns:

      this

  */

  setPosition: function(top, left) {

    var pos = this.computePosition(top, left);

    this.options.top  = pos.top;

    this.options.left = pos.left;



    var elementStyle  = this.element.style;

    elementStyle.top  = pos.top + 'px';

    elementStyle.left = pos.left + 'px';



    this.fire('position:changed');

    return this;

  },



  /*

    Method: center

      Centers the window within its viewport



    Returns:

      this

  */

  center: function(options) {

    var size          = this.getSize(),

        windowManager = this.windowManager,

        viewport      = windowManager.viewport;

        viewportArea  = viewport.getDimensions(),

        offset        = viewport.getScrollOffset();



    if (options && options.auto) {

      this.centerOptions = Object.extend({ handler: this.recenter.bind(this) }, options);

      Event.observe(this.windowManager.scrollContainer,"scroll", this.centerOptions.handler);

      Event.observe(window,"resize", this.centerOptions.handler);

    }



    options = Object.extend({

      top:  (viewportArea.height - size.height) / 2,

      left: (viewportArea.width  - size.width)  / 2

    }, options || {});



    return this.setPosition(options.top + offset.top, options.left + offset.left);

  },



  /*

    Method: getSize

      Returns window width/height dimensions (in pixels)



    Parameters

      innerSize: returns content size if true, window size if false (defaults to false)



    Returns:

      Hash {width:, height:}

  */

  getSize: function(innerSize) {

    if (innerSize)

      return { width:  this.options.width  - this.borderSize.width,

               height: this.options.height - this.borderSize.height };

    else

      return { width: this.options.width, height: this.options.height };

  },



  /*

    Method: setSize

      Sets window width/height dimensions (in pixels), fires size:changed



    Parameters

      width:  width (in pixels)

      height: height (in pixels)

      innerSize: if true change set content size, else set window size (defaults to false)



    Returns:

      this

  */

  setSize: function(width, height, innerSize) {

    var size = this.computeSize(width, height, innerSize);

    var elementStyle = this.element.style, contentStyle = this.content.style;



    this.options.width  = size.outerWidth;

    this.options.height = size.outerHeight;



    elementStyle.width = size.outerWidth + "px", elementStyle.height = size.outerHeight + "px";

    contentStyle.width = size.innerWidth + "px", contentStyle.height = size.innerHeight + "px";

    this.overlay.style.height = size.innerHeight + "px";



    this.fire('size:changed');

 	  return this;

  },



  /*

    Method: getBounds

      Returns window bounds (in pixels)



    Parameters

      innerSize: returns content size if true, window size otherwise



    Returns:

      an Hash {top:, left:, width:, height:}

  */

  getBounds: function(innerSize) {

    return Object.extend(this.getPosition(), this.getSize(innerSize));

  },



  /*

    Method: setBounds

      Sets window bounds (in pixels), fires position:changed and size:changed



    Parameters

      bounds: Hash {top:, left:, width:, height:} where all values are optional

      innerSize: sets content size if true, window size otherwise



    Returns:

      Hash {top:, left:, width:, height:}

  */

  setBounds: function(bounds, innerSize) {

    return this.setPosition(bounds.top, bounds.left)

               .setSize(bounds.width, bounds.height, innerSize);

  },



  morph: function(bounds, innerSize) {

    bounds = Object.extend(this.getBounds(innerSize), bounds || {});



    if (this.centerOptions && this.centerOptions.auto)

       bounds = Object.extend(bounds, this.computeRecenter(bounds));



    if (innerSize) {

      bounds.width  += this.borderSize.width;

      bounds.height += this.borderSize.height;

    }



    this.animating = true;



    new UI.Window.Effects.Morph(this, bounds, {

      duration: 0.5,

      afterFinish: function() { this.animating = false }.bind(this)

    });



    Object.extend(this.options, bounds);



    return this;

  },



  /*

    Method: getAltitude

      Returns window altitude, an integer between 0 and the number of windows,

      the higher the altitude number - the higher the window position.

  */

  getAltitude: function() {

    return this.windowManager.getAltitude(this);

  },



  /*

    Method: setAltitude

      Sets window altitude, fires 'altitude:changed' if altitude was changed

  */

  setAltitude: function(altitude) {

    if (this.windowManager.setAltitude(this, altitude))

      this.fire('altitude:changed');

    return this;

  },



  /*

    Method: setResizable

      TODO

  */

  setResizable: function(resizable) {

    this.options.resizable = resizable;



    var toggleClassName = (resizable ? 'add' : 'remove') + 'ClassName';



    this.element[toggleClassName]('resizable')

      .select('div:[class*=_sizer]').invoke(resizable ? 'show' : 'hide');

    if (resizable)

      this.createResizeHandles();



    this.element.select('div.se').first()[toggleClassName]('se_resize_handle');



    return this;

  },



  /*

    Method: setDraggable

      TODO

  */

  setDraggable: function(draggable) {

    this.options.draggable = draggable;

    this.element[(draggable ? 'add' : 'remove') + 'ClassName']('draggable');

    return this;

  },



  // Group: Theme

  /*

    Method: getTheme

      Returns window theme name

  */

  getTheme: function() {

    return this.options.theme || this.windowManager.getTheme();

  },



  /*

    Method: setTheme

      Sets window theme

  */

  setTheme: function(theme, windowManagerTheme) {

    this.element.removeClassName(this.getTheme()).addClassName(theme);

    // window has it's own theme

    if (!windowManagerTheme)

      this.options.theme = theme;



    return this;

  },



  /*

    Method: getShadowTheme

      Returns shadow theme name

  */

  getShadowTheme: function() {

    return this.options.shadowTheme || this.windowManager.getShadowTheme();

  }

});



UI.Window.addMethods(UI.Window.Buttons);

UI.Window.addMethods(UI.Window.Shadow);

UI.Window.optionsAccessor($w(" minWidth minHeight maxWidth maxHeight gridX gridY altitude "));

// Private functions for window.js

UI.Window.addMethods({

  style: "position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;",



  action: function(name) {

    var action = this.options[name];

    if (action)

      Object.isString(action) ? this[action]() : action.call(this, this);

  },



  create: function() {

    function createDiv(className, options) {

      return new Element('div', Object.extend({ className: className }, options));

    };



    // Main div

    this.element = createDiv("ui-window " + this.getTheme(), {

      id: this.options.id,

      style: "top:-10000px; left:-10000px"

    });



    // Create HTML window code

    this.header  = createDiv('n move_handle').enableDrag();

    this.content = createDiv('content').appendText(' ');

    this.footer  = createDiv('s move_handle').enableDrag();



    var header   = createDiv('nw').insert(createDiv('ne').insert(this.header));

    var content  = createDiv('w').insert(createDiv('e', {style: "position:relative"}).insert(this.content));

    var footer   = createDiv('sw').insert(createDiv('se' + (this.options.resizable ?  " se_resize_handle" : "")).insert(this.footer));



    this.element.insert(header).insert(content).insert(footer).identify('ui-window');

    this.header.observe('mousedown', this.activate.bind(this));



    this.setDraggable(this.options.draggable);

    this.setResizable(this.options.resizable);



    this.overlay = new Element('div', { style: this.style + "display: none" })

        .observe('mousedown', this.activate.bind(this));



    if (this.options.activeOnClick)

      this.content.insert({ before: this.overlay });

  },



  createWiredElement: function() {

    this.wiredElement = this.wiredElement || new Element("div", {

      className: this.getTheme() + "_wired",

      style:    "display: none; position: absolute; top: 0; left: 0"

    });

  },



  createResizeHandles: function() {

    $w(" n  w  e  s  nw  ne  sw  se ").each(function(id) {

      this.insert(new Element("div", {

        className:   id + "_sizer resize_handle",

        drag_prefix: id }).enableDrag());

    }, this.element);

    this.createResizeHandles = Prototype.emptyFunction;

  },



  // First rendering, pre-compute window border size

  render: function() {

    this.addElements();



    this.computeBorderSize();

    this.updateButtonsOrder();

    this.element.hide().remove();



    // this.options contains top, left, width and height keys

    return this.setBounds(this.options);

  },



  // Adds window elements to the DOM

  addElements: function() {

    this.windowManager.container.appendChild(this.element);

  },



  // Set z-index to all window elements

  setZIndex: function(zIndex) {

    if (this.zIndex != zIndex) {

      this.zIndex = zIndex;

      [ this.element ].concat(this.element.childElements()).each(function(element) {

        element.style.zIndex = zIndex++;

      });

      this.lastZIndex = zIndex;

    }

    return this;

  },



  effect: function(name, element, options) {

    var effect = this.options[name] || Prototype.emptyFunction;

    effect(element || this.element, options || {});

  },



  // re-compute window border size

  computeBorderSize: function() {

    if (this.element) {

      if (Prototype.Browser.IEVersion >= 7)

        this.content.style.width = "100%";

      var dim = this.element.getDimensions(), pos = this.content.positionedOffset();

      this.borderSize = {  top:    pos[1],

                           bottom: dim.height - pos[1] - this.content.getHeight(),

                           left:   pos[0],

                           right:  dim.width - pos[0] - this.content.getWidth() };

      this.borderSize.width  = this.borderSize.left + this.borderSize.right;

      this.borderSize.height = this.borderSize.top  + this.borderSize.bottom;

      if (Prototype.Browser.IEVersion >= 7)

        this.content.style.width = "auto";

    }

  },



  computeSize: function(width, height, innerSize) {

    var innerWidth, innerHeight, outerWidth, outerHeight;

	  if (innerSize) {

	    outerWidth  =  width  + this.borderSize.width;

	    outerHeight =  height + this.borderSize.height;

    } else {

	    outerWidth  =  width;

	    outerHeight =  height;

    }

    // Check grid value

    if (!this.animating) {

      outerWidth = outerWidth.snap(this.options.gridX);

      outerHeight = outerHeight.snap(this.options.gridY);



      // Check min size

      if (!this.folded) {

        if (outerWidth < this.options.minWidth)

          outerWidth = this.options.minWidth;



        if (outerHeight < this.options.minHeight)

          outerHeight = this.options.minHeight;

      }



      // Check max size

      if (this.options.maxWidth && outerWidth > this.options.maxWidth)

        outerWidth = this.options.maxWidth;



      if (this.options.maxHeight && outerHeight > this.options.maxHeight)

        outerHeight = this.options.maxHeight;

    }



    if (this.centerOptions && this.centerOptions.auto)

      this.recenter();



    innerWidth  = outerWidth - this.borderSize.width;

    innerHeight = outerHeight - this.borderSize.height;

    return {

      innerWidth: innerWidth, innerHeight: innerHeight,

      outerWidth: outerWidth, outerHeight: outerHeight

    };

  },



  computePosition: function(top, left) {

    if (this.centerOptions && this.centerOptions.auto)

      return this.computeRecenter(this.getSize());                                                                                                            ;



    return {

      top:  this.animating ? top  : top.snap(this.options.gridY),

      left: this.animating ? left : left.snap(this.options.gridX)

    };

  },



  computeRecenter: function(size) {

    var viewport   = this.windowManager.viewport,

        area       = viewport.getDimensions(),

        offset     = viewport.getScrollOffset(),

        center     = {

          top:  Object.isUndefined(this.centerOptions.top)  ? (area.height - size.height) / 2 : this.centerOptions.top,

          left: Object.isUndefined(this.centerOptions.left) ? (area.width  - size.width)  / 2 : this.centerOptions.left

        };



    return {

      top:  parseInt(center.top + offset.top),

      left: parseInt(center.left + offset.left)

    };

  },



  recenter: function(event) {

    var pos = this.computeRecenter(this.getSize());

    this.setPosition(pos.top, pos.left);

  }

});

UI.URLWindow = Class.create(UI.Window, {

  options: {

    url: 'about:blank'

  },



  afterClassCreate: function() {

    this.undefMethod('setAjaxContent');

  },



  initialize: function($super, options) {

    $super(options);

    this.setUrl(this.options.url);

  },



  destroy: function($super){

    this.iframe.src = null;

    $super();

  },



  getUrl: function() {

    return this.iframe.src;

  },



  setUrl: function(url, options) {

    this.iframe.src = url;

    return this;

  },



  create: function($super) {

    $super();



    this.iframe = new Element('iframe', {

      style: this.style,

      frameborder: 0,

      src: this.options.url,

      name: this.element.id + "_frame",

      id:  this.element.id + "_frame"

    });



    this.content.insert(this.iframe);

  }

});

if (!Object.isUndefined(window.Effect)) {

  UI.Window.Effects = UI.Window.Effects || {};

  UI.Window.Effects.Morph = Class.create(Effect.Base, {

    initialize: function(window, bounds) {

      this.window = window;

      var options = Object.extend({

        fromBounds: this.window.getBounds(),

        toBounds:   bounds,

        from:       0,

        to:         1

      }, arguments[2] || { });

      this.start(options);

    },



    update: function(position) {

      var t = this.options.fromBounds.top + (this.options.toBounds.top   - this.options.fromBounds.top) * position;

      var l = this.options.fromBounds.left + (this.options.toBounds.left - this.options.fromBounds.left) * position;



      var ow = this.options.fromBounds.width + (this.options.toBounds.width - this.options.fromBounds.width) * position;

      var oh = this.options.fromBounds.height + (this.options.toBounds.height - this.options.fromBounds.height) * position;



      this.window.setBounds({top: t,  left: l, width: ow, height: oh})

    }

  });

}

UI.Window.addMethods({

  startDrag: function(handle) {

    this.initBounds = this.getBounds();

    this.activate();



    if (this.options.wired) {

      this.createWiredElement();

      this.wiredElement.style.cssText = this.element.style.cssText;

      this.element.hide();

      this.saveElement = this.element;

      this.windowManager.container.appendChild(this.wiredElement);

      this.element = this.wiredElement;

    }



    handle.hasClassName('resize_handle') ? this.startResize(handle) : this.startMove();

  },



  endDrag: function() {

    this.element.hasClassName('resized') ? this.endResize() : this.endMove();



    if (this.options.wired) {

      this.saveElement.style.cssText = this.wiredElement.style.cssText;

      this.wiredElement.remove();

      this.element = this.saveElement;

    }

  },



  startMove: function() {

    // method used to drag

    this.drag = this.moveDrag;

    this.element.addClassName('moved');

    this.fire('move:started');

  },



  endMove: function() {

    this.element.removeClassName('moved');

    this.fire('move:ended');

  },



  startResize: function(handle) {

    this.drag = this[handle.readAttribute('drag_prefix')+'Drag'];

    this.element.addClassName('resized');

    this.fire('resize:started');

  },



  endResize: function() {

    this.element.removeClassName('resized');

    this.fire('resize:ended');

  },



  moveDrag: function(dx, dy) {

    this.setPosition(this.initBounds.top + dy, this.initBounds.left + dx);

  },



  swDrag: function(dx, dy) {

    var initBounds = this.initBounds;

    this.setSize(initBounds.width - dx, initBounds.height + dy)

        .setPosition(initBounds.top,

                     initBounds.left + (initBounds.width - this.getSize().width));

  },



  seDrag: function(dx, dy) {

    this.setSize(this.initBounds.width + dx, this.initBounds.height + dy);

  },



  nwDrag: function(dx, dy) {

    var initBounds = this.initBounds;

    this.setSize(initBounds.width - dx, initBounds.height - dy)

        .setPosition(initBounds.top + (initBounds.height - this.getSize().height),

                     initBounds.left + (initBounds.width - this.getSize().width));

  },



  neDrag: function(dx, dy) {

    var initBounds = this.initBounds;

    this.setSize(initBounds.width + dx, initBounds.height - dy)

        .setPosition(initBounds.top + (initBounds.height - this.getSize().height),

                     initBounds.left);

  },



  wDrag: function(dx, dy) {

    var initBounds = this.initBounds;

    this.setSize(initBounds.width - dx, initBounds.height)

        .setPosition(initBounds.top,

                     initBounds.left + (initBounds.width - this.getSize().width));

  },



  eDrag: function(dx, dy) {

    this.setSize(this.initBounds.width + dx, this.initBounds.height);

  },



  nDrag: function(dx, dy) {

    var initBounds = this.initBounds;

    this.setSize(initBounds.width, initBounds.height - dy)

        .setPosition(initBounds.top + (initBounds.height - this.getSize().height),

                     initBounds.left);

  },



  sDrag: function(dx, dy) {

    this.setSize(this.initBounds.width, this.initBounds.height + dy);

  }

});

UI.Window.addMethods({

  methodsAdded: function(base) {

    base.aliasMethodChain('create',  'buttons');

    base.aliasMethodChain('destroy', 'buttons');

  },



  createWithButtons: function() {

    this.createWithoutButtons();



    if (!this.options.resizable) {

      this.options.minimize = false;

      this.options.maximize = false;

    }



    this.buttons = new Element("div", { className: "buttons" })

      .observe('click',     this.onButtonsClick.bind(this))

      .observe('mouseover', this.onButtonsHover.bind(this))

      .observe('mouseout',  this.onButtonsOut.bind(this));



    this.element.insert(this.buttons);



    this.defaultButtons.each(function(button) {

      if (this.options[button] !== false)

        this.addButton(button);

    }, this);

  },



  destroyWithButtons: function() {

    this.buttons.stopObserving();

    this.destroyWithoutButtons();

  },



  defaultButtons: $w(' minimize maximize close '),



  getButtonElement: function(buttonName) {

    return this.buttons.down("." + buttonName);

  },



  // Controls close, minimize, maximize, etc.

  // action can be either a string or a function

  // if action is a string, it is the method name that will be called

  // else the function will take the window as first parameter.

  // if not given action will be taken in window's options

  addButton: function(buttonName, action) {

    this.buttons.insert(new Element("a", { className: buttonName, href: "#"}));



    if (action)

      this.options[buttonName] = action;



    return this;

  },



  removeButton: function(buttonName) {

    this.getButtonElement(buttonName).remove();

    return this;

  },



  disableButton: function(buttonName) {

    this.getButtonElement(buttonName).addClassName("disabled");

    return this;

  },



  enableButton: function(buttonName) {

    this.getButtonElement(buttonName).removeClassName("disabled");

    return this;

  },



  onButtonsClick: function(event) {

    var element = event.findElement('a:not(.disabled)');



    if (element) this.action(element.className);

    event.stop();

  },



  onButtonsHover: function(event) {

    this.buttons.addClassName("over");

  },



  onButtonsOut: function(event) {

    this.buttons.removeClassName("over");

  },



  updateButtonsOrder: function() {

    var buttons = this.buttons.childElements();



    buttons.inject(new Array(buttons.length), function(array, button) {

      array[parseInt(button.getStyle("padding-top"))] = button.setStyle("padding: 0");

      return array;

    }).each(function(button) { this.buttons.appendChild(button) }, this);

  }

});

UI.Window.addMethods({

  methodsAdded: function(base) {

    (function(methods) {

      $w(methods).each(function(m) { base.aliasMethodChain(m, 'shadow') });

    })(' create addElements setZIndex setPosition setSize setBounds ');

  },



  showShadow: function() {

    if (this.shadow) {

      this.shadow.hide();

      this.effect('show', this.shadow.shadow);

    }

  },



  hideShadow: function() {

    if (this.shadow)

      this.effect('hide', this.shadow.shadow);

  },



  removeShadow: function() {

    if (this.shadow)

      this.shadow.remove();

  },



  focusShadow: function() {

    if (this.shadow)

      this.shadow.focus();

  },



  blurShadow: function() {

    if (this.shadow)

      this.shadow.blur();

  },



  // Private Functions

  createWithShadow: function() {

    this.createWithoutShadow();



    this.observe('showing', this.showShadow)

        .observe('hiding',  this.hideShadow)

        .observe('hidden',  this.removeShadow)

        .observe('focused', this.focusShadow)

        .observe('blurred', this.blurShadow);



    if (this.options.shadow)

      this.shadow = new UI.Shadow(this.element, {theme: this.getShadowTheme()});

  },



  addElementsWithShadow: function() {

    this.addElementsWithoutShadow();

    if (this.shadow) {

      this.shadow.setBounds(this.options).render();

    }

  },



  setZIndexWithShadow: function(zIndex) {

    if (this.zIndex != zIndex) {

      if (this.shadow)

        this.shadow.setZIndex(zIndex - 1);

      this.setZIndexWithoutShadow(zIndex);

      this.zIndex = zIndex;

    }

    return this;

  },



  setPositionWithShadow: function(top, left) {

    this.setPositionWithoutShadow(top, left);

    if (this.shadow) {

      var pos = this.getPosition();

      this.shadow.setPosition(pos.top, pos.left);

    }

    return this;

  },



  setSizeWithShadow: function(width, height, innerSize) {

    this.setSizeWithoutShadow(width, height, innerSize);

    if (this.shadow) {

      var size = this.getSize();

      this.shadow.setSize(size.width, size.height);

    }

    return this;

  },



  setBoundsWithShadow: function(bounds, innerSize) {

    this.setBoundsWithoutShadow(bounds, innerSize);

    if (this.shadow)

      this.shadow.setBounds(this.getBounds());

  }

});



/*

Class: UI.WindowManager

  Window Manager.

  A default instance of this class is created in UI.defaultWM.



  Example:

    > new UI.WindowManger({

    >   container: 'desktop',

    >   theme: 'mac_os_x'

    > });

*/



UI.WindowManager = Class.create(UI.Options, {

  options: {

    container:   null, // will default to document.body

    zIndex:      0,

    theme:       "alphacube",

    shadowTheme: "mac_shadow",

    showOverlay: Element.show,

    hideOverlay: Element.hide,

    positionningStrategy: function(win, area) {

      UI.WindowManager.DumbPositionningStrategy(win, area);

    }

  },



  initialize: function(options) {

    this.setOptions(options);



    this.container = $(this.options.container || document.body);



    if (this.container === $(document.body)) {

      this.viewport = document.viewport;

      this.scrollContainer = window;

    } else {

      this.viewport = this.scrollContainer = this.container;

    }



    this.container.observe('drag:started', this.onStartDrag.bind(this))

                  .observe('drag:updated', this.onDrag.bind(this))

                  .observe('drag:ended',   this.onEndDrag.bind(this));



    this.stack = new UI.WindowManager.Stack();

    this.modalSessions = 0;



    this.createOverlays();

    this.resizeEvent = this.resize.bind(this);



    Event.observe(window, "resize", this.resizeEvent);

  },



  destroy: function() {

    this.windows().invoke('destroy');

    this.stack.destroy();

    Event.stopObserving(window, "resize", this.resizeEvent);

  },



  /*

    Method: setTheme

      Changes window manager's theme, all windows that don't have a own theme

      will have this new theme.



    Parameters:

      theme - theme name



    Example:

      > UI.defaultWM.setTheme('bluelighting');

  */

  setTheme: function(theme) {

    this.stack.windows.select(function(w) {

      return !w.options.theme;

    }).invoke('setTheme', theme, true);

    this.options.theme = theme;

    return this;

  },



  register: function(win) {

    if (this.getWindow(win.id)) return;



    this.handlePosition(win);

    this.stack.add(win);

    this.restartZIndexes();

  },



  unregister: function(win) {

    this.stack.remove(win);



    if (win == this.focusedWindow)

      this.focusedWindow = null;

  },



  /*

    Method: getWindow

      Find the window containing a given element.



    Example:

      > $$('.ui-window a.close').invoke('observe', 'click', function() {

      >   UI.defaultWM.getWindow(this).close();

      > });



    Parameters:

      element - element or element identifier



    Returns:

      containing window or null

  */

  getWindow: function(element) {

    element = $(element);



    if (!element) return;



    if (!element.hasClassName('ui-window'))

      element = element.up('.ui-window');



    var id = element.id;

    return this.stack.windows.find(function(win) { return win.id == id });

  },



  /*

    Method: windows

      Returns an array of all windows handled by this window manager.

      First one is the back window, last one is the front window.



    Example:

      > UI.defaultWM.windows().invoke('destroy');

  */

  windows: function() {

    return this.stack.windows.clone();

  },



  /*

    Method: getFocusedWindow

      Returns the focused window

  */

  getFocusedWindow: function() {

    return this.focusedWindow;

  },



  // INTERNAL



  // Modal mode

  startModalSession: function(win) {

    if (!this.modalSessions) {

      this.removeOverflow();

      this.modalOverlay.className = win.getTheme() + "_overlay";

      this.container.appendChild(this.modalOverlay);



      if (!this.modalOverlay.opacity)

        this.modalOverlay.opacity = this.modalOverlay.getOpacity();

      this.modalOverlay.setStyle("height: " + this.viewport.getHeight() + "px");



      this.options.showOverlay(this.modalOverlay, {from: 0, to: this.modalOverlay.opacity});

    }

    this.modalOverlay.setStyle({ zIndex: win.zIndex - 1 });

    this.modalSessions++;

  },



  endModalSession: function(win) {

    this.modalSessions--;

    if (this.modalSessions) {

      this.modalOverlay.setStyle({ zIndex: this.stack.getPreviousWindow(win).zIndex - 1 });

    } else {

      this.resetOverflow();

      this.options.hideOverlay(this.modalOverlay, { from: this.modalOverlay.opacity, to: 0 });

    }

  },



  moveHandleSelector:   '.ui-window.draggable .move_handle',

  resizeHandleSelector: '.ui-window.resizable .resize_handle',



  onStartDrag: function(event) {

    var handle = event.element(),

        isMoveHandle   = handle.match(this.moveHandleSelector),

        isResizeHandle = handle.match(this.resizeHandleSelector);



    // ensure dragged element is a window handle !

    if (isResizeHandle || isMoveHandle) {

      event.stop();



      // find the corresponding window

      var win = this.getWindow(event.findElement('.ui-window'));



      // render drag overlay

      this.container.insert(this.dragOverlay.setStyle({ zIndex: this.getLastZIndex() }));



      win.startDrag(handle);

      this.draggedWindow = win;

    }

  },



  onDrag: function(event) {

    if (this.draggedWindow) {

      event.stop();

      this.draggedWindow.drag(event.memo.dx, event.memo.dy);

    }

  },



  onEndDrag: function(event) {

    if (this.draggedWindow) {

      event.stop();

      this.dragOverlay.remove();

      this.draggedWindow.endDrag();

      this.draggedWindow = null;

    }

  },



  maximize: function(win) {

    this.removeOverflow();

    this.maximizedWindow = win;

    return true;

  },



  restore: function(win) {

    if (this.maximizedWindow) {

      this.resetOverflow();

      this.maximizedWindow = false;

    }

    return true;

  },



  removeOverflow: function() {

    var container = this.container;

    // Remove overflow, save overflow and scrolloffset values to restore them when restore window

    container.savedOverflow = container.style.overflow || "auto";

    container.savedOffset = this.viewport.getScrollOffset();

    container.style.overflow = "hidden";



    this.viewport.setScrollOffset({ top:0, left:0 });



    if (this.container == document.body && Prototype.Browser.IE)

      this.cssRule = CSS.addRule("html { overflow: hidden }");

  },



  resetOverflow: function() {

    var container = this.container;

    // Restore overflow ans scrolloffset

    if (container.savedOverflow) {

      if (this.container == document.body && Prototype.Browser.IE)

        this.cssRule.remove();



      container.style.overflow = container.savedOverflow;

      this.viewport.setScrollOffset(container.savedOffset);



      container.savedOffset = container.savedOverflow = null;

    }

  },



  hide: function(win) {

    var previous = this.stack.getPreviousWindow(win);

    if (previous) previous.focus();

  },



  restartZIndexes: function(){

    // Reset zIndex

    var zIndex = this.getZIndex() + 1; // keep a zIndex free for overlay divs

    this.stack.windows.each(function(w) {

      w.setZIndex(zIndex);

      zIndex = w.lastZIndex + 1;

    });

  },



  getLastZIndex: function() {

    return this.stack.getFrontWindow().lastZIndex + 1;

  },



  overlayStyle: "position: absolute; top: 0; left: 0; display: none; width: 100%;",



  createOverlays: function() {

    this.modalOverlay = new Element("div", { style: this.overlayStyle });

    this.dragOverlay  = new Element("div", { style: this.overlayStyle+"height: 100%" });

  },



  focus: function(win) {

    // Blur the previous focused window

    if (this.focusedWindow)

      this.focusedWindow.blur();

    this.focusedWindow = win;

  },



  blur: function(win) {

    if (win == this.focusedWindow)

      this.focusedWindow = null;

  },



  setAltitude: function(win, altitude) {

    var stack = this.stack;



    if (altitude === "front") {

      if (stack.getFrontWindow() === win) return;

      stack.bringToFront(win);

    } else if (altitude === "back") {

      if (stack.getBackWindow() === win) return;

      stack.sendToBack(win);

    } else {

      if (stack.getPosition(win) == altitude) return;

      stack.setPosition(win, altitude);

    }



    this.restartZIndexes();

    return true;

  },



  getAltitude: function(win) {

    return this.stack.getPosition(win);

  },



  resize: function(event) {

    var area = this.viewport.getDimensions();



    if (this.maximizedWindow)

      this.maximizedWindow.setSize(area.width, area.height);



    if (this.modalOverlay.visible())

      this.modalOverlay.setStyle("height:" + area.height + "px");

  },



  handlePosition: function(win) {

    // window has its own position, nothing needs to be done

    if (Object.isNumber(win.options.top) && Object.isNumber(win.options.left))

      return;



    var strategy = this.options.positionningStrategy,

        area     = this.viewport.getDimensions();



    Object.isFunction(strategy) ? strategy(win, area) : strategy.position(win, area);

  }

});



UI.WindowManager.DumbPositionningStrategy = function(win, area) {

  size = win.getSize();



  var top  = area.height - size.height,

      left = area.width  - size.width;



  top  = top  < 0 ? 0 : Math.random() * top;

  left = left < 0 ? 0 : Math.random() * left;



  win.setPosition(top, left);

};



UI.WindowManager.optionsAccessor('zIndex', 'theme', 'shadowTheme');



UI.WindowManager.Stack = Class.create(Enumerable, {

  initialize: function() {

    this.windows = [ ];

  },



  each: function(iterator) {

    this.windows.each(iterator);

  },



  add: function(win, position) {

    this.windows.splice(position || this.windows.length, 0, win);

  },



  remove: function(win) {

    this.windows = this.windows.without(win);

  },



  sendToBack: function(win) {

    this.remove(win);

    this.windows.unshift(win);

  },



  bringToFront: function(win) {

    this.remove(win);

    this.windows.push(win);

  },



  getPosition: function(win) {

    return this.windows.indexOf(win);

  },



  setPosition: function(win, position) {

    this.remove(win);

    this.windows.splice(position, 0, win);

  },



  getFrontWindow: function() {

    return this.windows.last();

  },



  getBackWindow: function() {

    return this.windows.first();

  },



  getPreviousWindow: function(win) {

    return (win == this.windows.first()) ? null : this.windows[this.windows.indexOf(win) - 1];

  }

});



document.whenReady(function() {

  UI.defaultWM = new UI.WindowManager();

});

