/*  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:        330,



    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();



});



