/*
  popup.js

  A lightweight general purpose JavaScript DOM element popup class.

  Webpage:
    http;//www.methods.co.nz/popup/popup.html

  Inspired by:
    Lightbox2: http://www.huddletogether.com/projects/lightbox2/
    Lightbox Gone Wild: http://particletree.com/features/lightbox-gone-wild/
    Tooltip: http://blog.innerewut.de/pages/tooltip
    Prototype library: http://www.prototypejs.org/
    Scriptaculous library: http://script.aculo.us/

  Attributions:
    - Uses the getPageSize() function from Lightbox v2.02 by Lokesh Dhakar
      (http://www.huddletogether.com/projects/lightbox2/). [ hstuebner: inzwischen nicht mehr ]
    - Adapted the the modal overlay technique used in Lightbox v2.02 by Lokesh
      Dhakar (http://www.huddletogether.com/projects/lightbox2/).

  Version: 1.0.1

  Author:    Stuart Rackham <srackham@methods.co.nz>
  License:   This source code is released under the MIT license.

  Copyright (c) Stuart Rackham 2007

  Umbau auf Qooxdoo Objektstruktur Heiko Stuebner <heiko.stuebner@nexst4.de> 2008, 2009
*/

/* ************************************************************************
#kesrequire(share/webwidgets/dom/IEHelper.js)
#kesrequire(share/webwidgets/dom/Element.js)
#kesrequire(share/webwidgets/core/PopupManager.js)
************************************************************************ */

/**
 * Abstrakte Klasse zur Erstellung von Popup-Layern in normalen HTML-Seiten.
 * FIXME: Positionierung anhand von Maus-Koordinaten funktioniert noch nicht, da es
 * momentan noch nicht moeglich ist diese per Qooxdoo-Funktionen zu ermitteln.
 */
qx.Class.define("nx4.webwidgets.controller.popup.Popup",
{
  extend:qx.core.Object,

  /**
   * Einrichten des Popups, Optionen verarbeiten und Events anhaengen.
   * @param popup das Popup-Div
   * @param link das Linkelement zum Oeffnen des Popups
   */
  construct : function(popup, link)
  {
    this.base(arguments);

    if(arguments[2] && qx.lang.Type.isObject(arguments[2]) && qx.lang.Object.hasMinLength(arguments[2], 1))
      this.set(arguments[2]);
    this._applyTrigger(this.getTrigger()); //triggerFunction initialisieren

    //DOM-Element des Popup-divs holen
    this.setId(popup);
    this.popup = document.getElementById(popup);
    qx.bom.element.Style.set(this.popup, "zIndex", 10000);

    //Link-DOM-Element holen
    if(link)
      this.setLink(document.getElementById(link));

    //wenn popup versteckt starten soll (defaultwert), das verstecken erzwingen
    if (this.getHidden())
      this.csshide();

    //Closeboxen holen
    if(this.getClosebox())
    {
      //FIXME: aufraeumen
      this.closeboxes = qx.bom.Selector.query("."+this.getClosebox(), this.popup);
      if (qx.bom.element.Class.has(this.popup, this.getClosebox()))
        this.closeboxes[this.closeboxes.length] = this.popup;
    }
    else
      this.closeboxes = [];

    //die Events an das Popup anhaengen
    this.register_events();

    //IE-Iframe erstellen
    if(qx.core.Variant.isSet("qx.client", "mshtml"))
    {
      var dim = nx4.webwidgets.dom.Element.dimensions(this.popup);
      this.ieiframe = this.createIEIFrame(this.popup, popup, 0, 0, dim.width, dim.height, qx.bom.element.Style.get(this.popup, "zIndex")-1);
      this.ieiframe.style.display = 'none';
    }

    //dem Manager bescheid geben, dass wir jetzt da sind
    nx4.webwidgets.core.PopupManager.getInstance().add(this);
  },

  statics :
  {
    zIndex : 1000, /**< zIndex-Verwaltung fuer alle Elemente */
    overlay : null, /**< hier liegt dann evtl. das Overlay-Dom-Element fuer Modale Popups drinnen */
    overlay_levels : null /**< Hilfsvariable zur Verwaltung von meheren Popups und dem Overlay */
  },

  properties :
  {
    id : { init : null },

    link : { init : null, apply : "_applyLink" },

    modal           : { init : false, apply : "_applyModal" },
    hidden          : { init : true },
    closebox        : { init : 'popup_closebox' },       // CSS class name of click-to-close elements.
    trigger         : { init : 'click', apply : "_applyTrigger" },
    triggerFunction : { init : null },
    outevents       : { init : true },
    position        : { init : 'center' },
    duration        : { init : 0.5 },
    show_duration   : { init : 0.5 },
    hide_duration   : { init : 0.5 },
    blockerColor    : { init : "#a0a0a0", apply : "_applyBlockerColor" },
    opacity         : { init : 0.5, apply : "_applyOpacity" },
    show_delay      : { init : 500 },
    hide_delay      : { init : 2000 },
    cursor_margin   : { init : 5 }
  },

  members :
  {
    closeboxes : [], /**< Array der Closebox-Elemente */
    ieiframe   : null, /**< Objekt das den IFRame fuer IE-Browser enthaelt. */

    _transformLink : function(val)
    {
      if(typeof(val) == "string")
        return document.getElementById(val);

      return val;
    },

    _applyLink : function(newLink, oldLink)
    {
      if(oldLink)
      {
        qx.event.Registration.removeListener(oldLink, this.getTrigger(), this.getTriggerFunction(), this);
        if(this.getTrigger() == 'mouseover')
          qx.event.Registration.removeListener(oldLink, 'mouseout', this.stop_show_timer, this);
      }

      qx.event.Registration.addListener(newLink, this.getTrigger(), this.getTriggerFunction(), this);
      if(this.getTrigger() == 'mouseover')
        qx.event.Registration.addListener(newLink, 'mouseout', this.stop_show_timer, this);
    },

    _applyModal : function(n, o)
    {
      if(n && !this._blocker)
      {
        this._blocker = new qx.bom.Blocker();
        this._blocker.setBlockerColor(this.getBlockerColor());
        this._blocker.setBlockerOpacity(this.getOpacity());
      }
    },

    _applyTrigger : function(trigger)
    {
      this.setTriggerFunction(trigger == 'mouseover' ? this.start_show_timer : this.show);
    },

    _applyBlockerColor : function(n, o)
    {
      if(this._blocker)
        this._blocker.setBlockerColor(n);
    },

    _applyOpacity : function(n, o)
    {
      if(this._blocker)
        this._blocker.setBlockerOpacity(n);
    },

/////////////////////////////////////////// grundlegende Routinen des Popups //////////////////////////////////////

    /**
     * Im IE einen IFrame erstellen, der unter dem popup liegt und sich mit einblendet
     * Dadurch kann das Popup-Div auch Input-Window-Elemente ueberdecken. Idee von
     * http://dotnetjunkies.com/WebLog/jking/archive/2003/07/21/488.aspx
     */
    createIEIFrame : function(parent, IFramePrefix, top, left, width, height, zIndex)
    {
      parent.insertAdjacentHTML("beforeBegin", "<iframe style=\"position:absolute;\" src=\"javascript:'<html></html>'\" frameborder=0 scrolling=no id=\""+IFramePrefix+"iframe\"></iframe>");
      ieiframe = document.getElementById(IFramePrefix+"iframe");
      qx.bom.element.Style.setStyles(ieiframe, {left:left+'px', top:top+'px', width:width+'px', height:height+'px', zIndex:zIndex, filter:'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'});
      return ieiframe;
    },


    /**
     * Pruefen ob sich das Popup schon beim Mouseover oeffnen soll.
     * Z.B. fuer Tooltips
     * @return true oder false
     */
    is_auto_open: function()
    {
      return this.getTrigger() == 'mouseover';
    },

    /**
     * EventHandler an das Popup-Element und den Oeffnungslink knuepfen.
     * Diese sind fuer das Oeffnen und Schliessen des Popups verantwortlich
     */
    register_events: function()
    {
      //Events an alle Schliesselemente haengen
      if(this.closeboxes.length > 0)
      {
        for(var i = 0; i < this.closeboxes.length; i++)
          qx.event.Registration.addListener(this.closeboxes[i], 'click', this.hide, this);
      }

      //wenn keine Close-Boxen vorhanden sind, oder die outevents immer mit angelegt werden sollen
      if(this.closeboxes.length == 0 || this.getOutevents())
      {
        //fuer Link-Event wieder pruefen ob Link da ist, und ggf. abhaengig vom Element-Typ ein Out-Event anhaengen
        if(this.getLink())
          switch(this.getLink().tagName)
          {
            //Selects nutzen onblur
/*            case "SELECT":
              qx.bom.Event.addNativeListener(this.getLink(), 'blur', qx.lang.Function.bind(this.start_hide_timer, this));
              break;*/
            //alle anderen onmouseout
            default:
              qx.event.Registration.addListener(this.getLink(), 'mouseout', this.start_hide_timer, this);
              break;
          }

        qx.event.Registration.addListener(this.popup, 'mouseover', this.stop_hide_timer, this);
        qx.event.Registration.addListener(this.popup, 'mouseout', this.start_hide_timer, this);
      }
    },

////////////////////////////////////////// Timer-Funktionen zum Aus- und Einblenden /////////////////////////////////////

    /**
     * Einblendetimer starten.
     */
    start_show_timer: function(event)
    {
      // NOTE: event is bound to this.show but it's state changes between being
      // bound here and arriving at this.show -- specifically, the mouse
      // coordinates are reset to zero). I've no idea why. Anyway, this is the
      // reason for passing the event mouse coordinates as properties of this.
      this.stop_show_timer(event);
//FIXME: Mouse-Koordinaten fehlen noch
//      this.mouse_x = Event.pointerX(event);
//      this.mouse_y = Event.pointerY(event);
      this.show_timer = setTimeout(qx.lang.Function.bind(this.show, this, event), this.getShow_delay());
    },

    /**
     * Einblendetimer stoppen.
     */
    stop_show_timer: function(event)
    {
      if (this.show_timer)
      {
        clearTimeout(this.show_timer);
        this.show_timer = null;
      }
    },

    /**
     * Ausblendetimer starten.
     */
    start_hide_timer: function(event)
    {
      this.stop_hide_timer(event);
      this.hide_timer = setTimeout(qx.lang.Function.bind(this.hide, this, event), this.getHide_delay());
    },

    /**
     * Ausblendetimer stoppen.
     */
    stop_hide_timer: function(event)
    {
      if (this.hide_timer)
      {
        clearTimeout(this.hide_timer);
        this.hide_timer = null;
      }
    },

/////////////////////////////////////////////// Aus- und Einblenden des Popups /////////////////////////////////////////

    /**
     * Popup per CSS verstecken.
     * Wird intern verwendet.
     */
    csshide : function()
    {
      this.popup.style.display = "none";
      if(qx.core.Variant.isSet("qx.client", "mshtml") && this.ieiframe)
        this.ieiframe.style.display = "none";
    },

    /**
     * Popup per CSS einblenden
     * Wird intern verwendet.
     */
    cssshow : function()
    {
      this.popup.style.display = "";
      if(qx.core.Variant.isSet("qx.client", "mshtml") && this.ieiframe)
        this.ieiframe.style.display = "";
    },

    /**
     * Popup anzeigen.
     */
    show: function(event)
    {
      this.stop_show_timer(event);
      this.stop_hide_timer(event);
      if (this.is_open)
        return;

      if (this.getModal())
        this.show_overlay();

      var pos = {};
      if (!event)
        // We only arrive here if this.show has been called externally.
        pos = this.get_popup_position();
      else if (this.is_auto_open())
        // Because auto-open popups calls this.show indirectly via start_show_timer.
        pos = this.get_popup_position(this.mouse_x, this.mouse_y);
      else
        pos = this.get_popup_position(0, 0);
//        pos = this.get_popup_position(Event.pointerX(event), Event.pointerY(event)); FIXME: Maus-Koordinaten momentan fuer uns nicht verfuegbar

      qx.bom.element.Style.setStyles(this.popup, {left:pos.x, top:pos.y});

      //im IE, das Hilfs-IFrame mit positionieren
      if(qx.core.Variant.isSet("qx.client", "mshtml") && this.ieiframe)
        qx.bom.element.Style.setStyles(this.ieiframe, {left:pos.x, top:pos.y});

      this.is_open = true;
      this.cssshow();
    },

    /**
     * Popup ausblenden
     */
    hide: function(event)
    {
      this.is_open = false;
      this.csshide();

      if (this.getModal())
        this.hide_overlay();
    },

///////////////////////////////// Positionierung des PopUp-Fensters ////////////////////////////////////////

    /**
     * Return the top and left CSS position strings as an {x,y} object that the popup should be shown at.
     * mouse_x and mouse_y are the mouse x,y coordinates numbers when the popup was triggered.
     */
    get_popup_position: function(mouse_x, mouse_y)
    {
      var pos;
      switch (this.getPosition())
      {
/*        case 'auto':
          pos = this.get_auto_position(mouse_x, mouse_y); //FIXME: benoetigt Maus-Koordinaten
          break;*/
        case 'center':
          pos = nx4.webwidgets.dom.Element.centerCoords(this.popup);
          break;
        case 'below':
          pos = this.get_below_position();
          break;
        default:
          //spezielles Format: x-Koord,below behandeln
          //als unterhalb des Links aber x festgelegt
          //spezielles Format: x,below+y dabei wird dem ermittelten below-Wert die Zahl y hinzuaddiert
          if(mo = this.getPosition().match(/^\s*([^\s,]+)\s*,\s*(below)\+*([^\s,]*)\s*$/))
          {
            pos = this.get_below_position();
            pos.x = Number(mo[1]) || mo[1];
            if(Number(mo[3]) > 0)
              pos.y+= Number(mo[3]);
          }
          //spezielles Format: x-Koord,center behandeln (Zentriert B)
          else if(mo = this.getPosition().match(/^\s*([^\s,]+)\s*,\s*(center)\+*([^\s,]*)\s*$/))
          {
            pos = nx4.webwidgets.dom.Element.centerCoords(this.popup);
            pos.x = Number(mo[1]) || mo[1];
            if(Number(mo[3]) > 0)
              pos.y+= Number(mo[3]);
          }
          // Check for x,y postion format (x and y can be any valid CSS left or
          // top property value).
          else if(mo = this.getPosition().match(/^\s*([^\s,]+)\s*,\s*([^\s,]+)\s*$/))
          {
            pos = {x: mo[1], y: mo[2]};
            // If possible convert to numbers.
            pos.x = Number(pos.x) || pos.x;
            pos.y = Number(pos.y) || pos.y;
          }
          else
            pos = {x: 0, y: 0};
          break;
      }

      if (typeof pos.x == 'number')
        pos.x += 'px';

      if (typeof pos.y == 'number')
        pos.y += 'px';

      return pos;
    },

    /**
     * Koordinaten unterhalb des Link-Elementes holen.
     */
    get_below_position: function()
    {
      var pos = qx.bom.element.Location.get(this.getLink());
      return {x: pos.left, y: pos.bottom};
    },

    /**
     * Center-Positionen auf dem Bildschirm holen (Popupgroesse eingerechnet)
     */
    get_center_position: function()
    {
      qx.log.Logger.warn("get_center_position is deprecated");
      return nx4.webwidgets.dom.Element.centerCoords(this.popup);
    },

/* FIXME: Maus-Koordinaten sind noch nicht verfuegbar
  get_auto_position: function(mouse_x, mouse_y) {
    dim = nx4.webwidgets.dom.Element.dimensions(this.popup);
    var popup_width = dim.width;
    var popup_height = dim.height;
    var viewport = { w:qx.bom.Viewport.getWidth(), h:qx.bom.Viewport.getHeight()};
    var viewport_width = viewport.w;
    var viewport_height = viewport.h;

    var available_right = viewport_width - (mouse_x + this.getCursor_margin());
    var available_left = mouse_x - this.options.cursor_margin;
    var available_top = mouse_y - this.options.cursor_margin;
    var available_bottom = viewport_height - (mouse_x + this.getCursor_margin());
    var offset = this.getCursor_margin();
    var x = mouse_x;
    var y = mouse_y;

    if (popup_width >= viewport_width)
      x = 0;
    else if (popup_width <= available_right)
      x += offset;
    else if (popup_width <= available_left)
      x -= popup_width + offset;
    else if (available_right >= available_left)
      x = viewport_width - popup_width;
    else
      x = 0;

    if (popup_height >= viewport_height)
      y = 0;
    else if (popup_height <= available_bottom)
      y += offset;
    else if (popup_height <= available_top)
      y -= popup_height + offset;
    else if (available_bottom >= available_top)
      y = viewport_height - popup_height;
    else
      y = 0;

    return {x: x, y: y};
  },
*/

//////////////////////////////////// Overlay fuer Modal-Popups ////////////////////////////////////////////

    /**
     * Bei modalen Popups das Overlay anzeigen, dass den Restinhalt ausblendet.
     * Wenn es nicht vorhanden ist, wird das Overlayelement angelegt.
     */
    show_overlay: function()
    {
      this._blocker.setBlockerZIndex(qx.bom.element.Style.get(this.popup, "zIndex")-2);
      this._blocker.block();
    },

    /**
     * Bei modalen Popups das Overlay ausblenden.
     */
    hide_overlay: function()
    {
      this._blocker.unblock();
    }

  },

  /*
  *****************************************************************************
     DESTRUCTOR
  *****************************************************************************
  */

  destruct : function()
  {
    //dem Manager bescheid geben, dass wir jetzt weg sind
    nx4.webwidgets.core.PopupManager.getInstance().remove(this);
//    this._disposeMap("__objects");
  }
});

