"use strict";
/**
 * <div class="ngstk.anuncio"><b>Autor:</b> Daniel Mart&iacute;n Fern&aacute;ndez<br />
 * <b>Web:</b> <a href="http://www.naguissa.com">http://www.naguissa.com</a><br />
 * <b>Nombre:</b> Naguissa Toolkit (ngstk)<br />
 * <b>Descripci&oacute;n:</b> Toolkit JavaScript y PHP para creaci&oacute;n, manipulaci&oacute;n y transformaci&oacute;n de documentos web.<br />
 * <b>Secci&oacute;n:</b> Funciones y definiciones base. No son vitales para el funcionamiento, de los componentes, pero al haber definiciones y funciones de check los scripts fallaran si no se carga.<br />
 * <b>Licencia: GPL</b></div>
 * <b>Version: 1.3.01</b></div>
 *
 *
 * Changelog:
 *      v.1.5.02:
 *          - Added urlParameter function to retrieve parameters from URLs.
 *      v.1.5.01:
 *          - Added the ability to accept base64 encoded text in init function of the RTE editor. This way any code can be loaded.
 *      v.1.5.00:
 *          - Fixed base64_encode and base64_decode.
 *          - Revisited event functions.
 *          - Redone copyCSS and applyCSS functions.
 *          - JSLint almost 100%: jslint white: true, browser: true, devel: true, undef: true, nomen: true, maxerr: 5000
 *          - Tuned mouse demo movement (planned at 25fps and max 100frames (if more, fps are lowered).
 *          - Tuned all functions, objects and constructors.
 *          - Substituted ngstk.getElementById by its DOM default. Add this standard function when no available.
 *          - As code is JSLint almost-verified, now packer with all options is used to compress .js file.
 *      v.1.3.01:
 *          - New base64_encode and base64_decode functions.
 *      v.1.3.00:
 *          - setCookie, getCookie and delCookie new functions.
 */




/**
 *  Compatibility with document.getElementById
 *
 * @return DOMelement
 */
if (typeof document.getElementById == 'undefined') {
    document.getElementById = function (id) { 
        return document.all[id];
    };
}


/**
 *  Base object
 *
 * @property singleton
 */
// Protect variables.
var ngstk = new (function Ngstk() {
    // Protect 'this' object.
    var self = this;


    /**
     * Public configuration array.
     *
     * It has a base config and is public-accessible
     * to change configuration aspect externally.
     * Provides reutilization facilities.
     */
    this.config = [];
//    this.config.BASE_URL = "..";
    this.config.BASE_URL = "http://www.dwebresources.com";
    this.config.IMAGE_URL = this.config.BASE_URL + "/ngstk/image/";
    this.config.BROWSER_URL = this.config.BASE_URL + "/en/usuarios.php?espacio";
    this.config.SATAY_URL = this.config.BASE_URL + "/ngstk/satay.swf";
    this.config.SYNTAX = false;       // Para el editor RTE. Si se activa se han de cargar los archivo de Syntax Highlighter JS en el resultado. Estan en la carpeta del toolkit.
    this.config.SYNTAX_URL = this.config.BASE_URL + "/ngstk/syntax/";


    /**
     * Private counter.
     *
     * It's used to make unique IDs.
     */
    var uniqueid = 1;



    /**
     *  Private events array.
     *
     * It's used to manage events and triggers.
     */
    var events = [];


    /**
     *  Private image objects array.
     *
     * It's used to prefetch images.
     */
    var imgs = [];



    /**
     * Returns numeric unique IDs.
     *
     * It uses uniqueid private variable to return a numeric
     * ID and incrase the count.
     *
     * @return      integer     ID (count).
     *
     * @see uniqueid
     */
    this.getUniqueID = function () {
        return uniqueid++;
    };


    /**
     * Preload images.
     *
     * Preloads all images from an image array or structure.
     *
     * @param   ima     array/structure    Set of images to preload.
     *
     * @see imgs
     */
    this.preloadimg = function (imga) {
        for (var im in imga) {
            if (imga.hasOwnProperty(im)) {
                imgs[imgs.length] = new Image();
                imgs[imgs.length - 1].src = imga[im];
            }
        }
    };


    /**
     * Gets a parameter from a url.
     *
     * @param   n     string    Name of parameter to seek
     * @param   u     string    Optional, url to look in. Default location.href
     * @param   a     string    Optional, if true, uses # as parameter delimitator too
     *
     * @return  string/bool     Returns the string value of parameter or false if error or not found.
     */
    this.urlParameter = function (n, u, a) {
        if (!n || typeof n != "string") {
            return false;
        }
        if (!u || typeof u != "string") {
            u = location.href;
        }
        n = n.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
        var regex = (a === true ? new RegExp("[\\?&#]"+n+"=([^&#]*)") : new RegExp("[\\?&]"+n+"=([^&#]*)"));
        var results=regex.exec(location.href);
        return (results == null ? false : results[1]);
    };


    /**
     *  Private base64 dictionary string
     *
     * It's used to encode/decode base64 strings
     */
    var base64dic = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";


    /**
     * Encode to base64
     *
     * Encodes and input string to base64 (equal as PHP function)
     *
     * @param   i       string      String to encode
     *
     * @return      string          Encoded string.
     *
     * @see base64dic
     * @see base64_decode
     */
    this.base64_encode = function (i) {
        var o = [], c1, c2, c3, e1, e2, e3, e4, n = 0;
        do {
            c1 = i.charCodeAt(n++);
            c2 = i.charCodeAt(n++);
            c3 = i.charCodeAt(n++);
            e1 = c1 >> 2;
            e2 = ((c1 & 3) << 4) | (c2 >> 4);
            e3 = ((c2 & 15) << 2) | (c3 >> 6);
            e4 = c3 & 63;
            if (isNaN(c2)) {
                e3 = e4 = 64;
            } else {
                if (isNaN(c3)) {
                    e4 = 64;
                }
            }
            o[o.length] = base64dic.charAt(e1) + base64dic.charAt(e2) + base64dic.charAt(e3) + base64dic.charAt(e4);
        } while (n < i.length);
        return o.join('');
    };


    /**
     * Decode from base64
     *
     * Decodes and input string from base64 (equal as PHP function)
     *
     * @param   i       string      String to decode
     *
     * @return      string          Decoded string.
     *
     * @see base64dic
     * @see base64_encode
     */
    this.base64_decode = function (data) {
        var o = [], c1, c2, c3, e1, e2, e3, e4;
        for (var i = 0; i < data.length;) {
            e1 = base64dic.indexOf(data.charAt(i++));
            e2 = base64dic.indexOf(data.charAt(i++));
            e3 = base64dic.indexOf(data.charAt(i++));
            e4 = base64dic.indexOf(data.charAt(i++));
            c1 = (e1 << 2) + (e2 >> 4);
            c2 = ((e2 & 15) << 4) + (e3 >> 2);
            c3 = ((e3 & 3) << 6) + e4;
            o[o.length] = String.fromCharCode(c1);
            if (e3 != 64) {
                o[o.length] = String.fromCharCode(c2);
            }
            if (e4 != 64) {
                o[o.length] = String.fromCharCode(c3);
            }
        }
        return o.join('');
    };

    /**
     * Returns url encoded string.
     *
     * It's a url conversion method equivalent to PHP's
     * urlencode one.
     *
     * @param   str     string      String to encode.
     *
     * @return      string          Encoded string.
     *
     * @see urldecode
     */
    this.urlencode = function (str) {
        return escape(str).replace('+', '%2B').replace('%20', '+').replace('*', '%2A').replace('/', '%2F').replace('@', '%40');
    };


    /**
     * Returns decoded string.
     *
     * It's a url conversion method equivalent to PHP's
     * urldecode one.
     *
     * @param   str     string      String to encode.
     *
     * @return      string          Decoded string.
     *
     * @see urlencode
     */
    this.urldecode = function (str) {
        return unescape(str.replace('+', ' '));
    };


    /**
     * Returns escaped string.
     *
     * Replaces <, > and & for their hexadecimal entities HTML and XML valid.
     *
     * @param       str     String      String to encape.
     * @param       euro    Boolean     Escapes Euro symbol too.
     */
    this.escapeXML = function (str, euro) {
        if (euro) {
            return str.split("&").join("&#38;").split(String.fromCharCode(8364)).join("&#128;").split("<").join("&#60;").split(">").join("&#62;").split("&").join("&#38;");
        }
        return str.split("&").join("&#38;").split("<").join("&#60;").split(">").join("&#62;").split("&").join("&#38;");
    };



    /**
     * Sets display property of an object
     *
     * It's a multimode function to handle display
     * css property.
     *
     * @param   e   string/DOM fragment     Object of DOM to handle or his ID.
     * @param   s   string/boolean          Optional. "block"/"none" or true/false. Case insensitive. If defined and none of this four cases returns false.
     *
     * @return      string/boolean          returns (new?) status of display property of false if error happens.
     */
    this.display = function (e, s) {
        // Normalizamos el argumento
        if (typeof s !== 'undefined') {
            if (s === true) {
                s = 'block';
            } else if (s === false) {
                s = 'none';
            } else {
                s = s.toLowerCase(s + "");
            }
        }

        // Normalizamos el objeto
        if (typeof e === 'string') {
            e = document.getElementById(e);
        }
        if (!e) {
            return false;
        }
        if (typeof s !== 'undefined') {
            e.style.display = s;
        }
        return e.style.display;
    };



    /**
     * Switches display property of an object.
     *
     * It's a multimode function to switch display
     * css property of an object.
     *
     * @param   e   string/DOM fragment     Object of DOM to handle or his ID.
     *
     * @return      string/boolean          returns new status of display property of false if error happens.
     */
    this.showhide = function (e) {
        var d = self.display(e);
        if (d === false) {
            return false;
        }
        return self.display(e, d == 'none');
    };


    /**
     * Gets a DOM element by it's ID.
     *
     * @param   e   string                  ID of te object to get.
     *
     * @return      DOM fragment/boolean    Returns the element of the DOM or false if fails.
     */
    this.getElementById = function (e) {
        if (typeof e === 'string') {
            return document.getElementById(e);
        }
        return false;
    };



    /**
     * Sets a cookie
     *
     * @param   n   String          Cookie variable's name
     * @param   v   Mixed           Cookie's value
     * @param   d   int             [optional] days to expire
     */
    this.setCookie = function (n, v, d) {
        var e = "";
        if (typeof d == "number") {
            var dt = new Date();
            dt.setTime(dt.getTime() + (d * 86400000));
            e = "; expires=" + dt.toGMTString();
        }
        document.cookie = self.trim(n) + "=" + v + e + "; path=/";
    };


    /**
     * Gets a cookie's value
     *
     * @param   n   String          Cookie variable's name
     */
    this.getCookie = function (n) {
        var nE = self.trim(n) + "=";
        var ca = document.cookie.split(';');
        for (var i = 0; i < ca.length; i++) {
            var c = self.trim(ca[i]);
            if (c.indexOf(nE) === 0) {
                return c.substring(nE.length, c.length);
            }
        }
        return null;
    };


    /**
     * Sets a cookie to blank ("")
     *
     * @param   n   String          Cookie variable's name
     *
     * @see setCookie
     */
    this.delCookie = function (n) {
        self.setCookie(n, "", -1);
    };



    /**
     * Attach a function to a event in an object.
     *
     * It handles differences between browsers and is
     * multi-mode input. Accepts multiple attachments
     * to the same object and event, and executes them
     * at reverse order until a false return is reached.
     *
     * @param       obj         string/DOM fragment     Object (or id) where attach the function event.
     * @param       etype       string                  Event name (without the "on" prefix).
     * @param       fn          string/DOM fragment     Function to attach.
     * @param       ctx         Optional. object        Context. 'window' by default.
     *
     * @return      object/boolean      event object, to use with removeEvent. False if fails.
     *
     * @see removeEvent
     * @see stopEvent
     */
    this.addEvent = function (obj, etype, fn, ctx) {
        // normalizamos el objeto
        if (typeof obj == 'string') {
            obj = document.getElementById(obj);
        }
        // ctx is window if not defined
        if (!ctx || typeof ctx != 'object') {
            ctx = window;
        }

        // If something undefined, return false.
        if (!obj || typeof obj != 'object' || !etype || typeof etype != 'string' || !fn || typeof fn != 'function' || !ctx || typeof ctx != 'object') {
            return false;
        }

        // Store an event object.
        var ev = {
            obj: obj,
            etype: etype,
            fn: fn,
            ctx: ctx
        };

        if (typeof events[ev.etype] == 'undefined') {
            events[ev.etype] = [ev];
        } else {
            events[ev.etype][events[ev.etype].length] = ev;
        }

        // If it's a DOM event and there's no more events, attach the trigger function:
        if (obj !== self) {
            var k = events[ev.etype].length - 1;
            while (k--) {
                // If one object found, return "ret" value.
                if (events[ev.etype][k].obj === ev.obj) {
                    return ev;
                }
            }
            // Add event
            if (typeof obj.addEventListener != 'undefined') {
                obj.addEventListener(ev.etype, function (e) {
                    self.triggerEvent(ev, e || window.event);
                }, false);
            } else if (typeof obj.attachEvent != 'undefined') {
                obj.attachEvent("on" + ev.etype, function (e) {
                    self.triggerEvent(ev, e || window.event);
                });
            }
        }
        return ev;
    };


    /**
     * Deattach an addEvent attached function.
     *
     * It handles differences between browsers has multi-mode input.
     * Performs search in LIFO mode (pile). Removes 1st match.
     *
     * @param       obj         string/DOM fragment     Object (or id) where deattach the function event.
     * @param       etype       string                  Event name (without the "on" prefix).
     * @param       fn          function                Function to deattach or addEvent return value.
     *
     *   - or -
     *
     * @param       obj         addEvent return value (object)      Object (or id) where deattach the function event.
     *
     * @return      boolean     True if success or false if something fails.
     *
     * @see addEvent
     * @see stopEvent
     */
    this.removeEvent = function (obj, etype, fn, ctx) {
        // normalizamos el objeto
        if (typeof obj == 'string') {
            obj = document.getElementById(obj);
        }
        var ev = obj;
        // ctx is window if not defined
        if (!ctx || typeof ctx != 'object') {
            ctx = window;
        }

        // Event mode imput:
        if (typeof etype != 'undefined' && typeof fn != 'undefined') {
            // Restore an event object.
            ev = {
                'obj': obj,
                'etype': etype,
                'fn': fn,
                'ctx': ctx
            };
        }

        // If something undefined, return false.
        if (!ev || !ev.obj || typeof ev.obj != 'object' || !ev.etype || typeof ev.etype != 'string' || !ev.fn || typeof ev.fn != 'function') {
            return false;
        }

        // If no event found of this kind, returns false.
        if (typeof events[ev.etype] === 'undefined') {
            return false;
        }

        var ret = false;
        // Search for the event and, if found, remove it:
        var k = events[ev.etype].length;
        while (k--) {
            if (events[ev.etype][k].obj == ev.obj && events[ev.etype][k].fn == ev.fn && events[ev.etype][k].ctx == ev.ctx) {
                events[ev.etype][k].splice(k, 1);
                ret = true;
            }
        }

        // If it's a DOM event and there's no more events ath this object, deattach the trigger function:
        if (ev.obj !== self) {
            k = events[ev.etype].length;
            while (k--) {
                // If one object found, return "ret" value.
                if (events[ev.etype][k].obj === ev.obj) {
                    return ret;
                }
            }

            // Remove event
            if (typeof obj.removeEventListener != 'undefined') {
                obj.removeEventListener(etype, function (e) {
                    self.triggerEvent(ev, e || window.event);
                }, false);
            } else if (typeof obj.detachEvent != 'undefined') {
                obj.detachEvent("on" + etype, function (e) {
                    self.triggerEvent(ev, e || window.event);
                });
            }

        }
        return ret;
    };


    /**
     * Triggers an object's event chain.
     *
     * Breaks the chain if "false" is returned.
     * multi-mode input.
     *
     * @param       obj         string/DOM fragment     Object (or id) where deattach the function event.
     * @param       etype       string                  Event name (without the "on" prefix).
     * @param       e           event                   Browser event.
     *
     *   - or -
     *
     * @param       obj         addEvent return value (object)      Object and event name.
     * @param       etype        event                               Browser event.
     *
     * @return      boolean            Original function return or false if something fails.
     *
     * @see addEvent
     * @see stopEvent
     */
    this.triggerEvent = function (obj, etype, e) {
        var ret = true;

        // normalizamos el objeto
        if (typeof obj == 'string') {
            obj = document.getElementById(obj);
        }

        // No event mode imput, parametrized input:
        if (typeof(obj) == 'object' && typeof obj.obj != 'undefined' && typeof obj.ctx != 'undefined' && typeof obj.etype != 'undefined') {
            e = etype;
            etype = obj.etype;
            obj = obj.obj;
        }

        // If something undefined, return false.
        if (!obj || typeof obj != 'object' || !etype || typeof etype != 'string') {
            return false;
        }
        e = e || window.event;
        if (typeof events[etype] != 'undefined') {
            var k = events[etype].length;
            // For each registered event launch the function until a false returns value or them all have been processed
            while (k-- && ret !== false) {
                if (events[etype][k].obj == obj) {
                    ret = events[etype][k].fn.call(events[etype][k].ctx, e);
                }
            }
        }
        return ret;
    };



    /**
     * stop propagation of a event.
     *
     * Cross-browser event propagation prevention
     *
     * @param       String  e   (Optional). The event to stop. Mandatory in Mozilla browsers, optional in I.Explorer.
     *
     * @see ngstk.attachEvent
     * @see ngstk.removeEvent
     */
    this.stopEvent = function (e) {
        e = e || window.event;
        e.cancelBubble = true;
        e.returnValue = false;
        if (e.stopPropagation) {
            e.stopPropagation();
            e.preventDefault();
        }
        return false;
    };



    /**
     * Firebug console logging.
     *
     * @param       msg     string      Message to log.
     */
    this.log = function (msg) {
        if (console && console.log) {
            console.log.apply(console, arguments);
        } else if (firebug && firebug.d) {
            firebug.d.console.log.apply(firebug.d.console, arguments);
        }
    };


    /**
     * Object placement.
     *
     * Grabs an object, positions it absolutely to the given coordinates.
     * Multi-mode input.
     *
     * @param       obj         string/DOM fragment     Object (or id) to move.
     * @param       posx        decimal                 Destination horitzontal position inside the page.
     * @param       posy        decimal                 Destination vertical position inside the page.
     *
     * @return      boolean            true if OK, false if fails.
     *
     * @see addEvent
     */
    this.moveObjectToXY = function (obj, posx, posy) {
        // normalizamos el objeto
        if (typeof obj == 'string') {
            obj = document.getElementById(obj);
        }
        if (!obj || typeof obj != 'object') {
            return false;
        }
        obj.style.position = "absolute";
        obj.style.left = posx;
        obj.style.top = posy;
        return true;
    };


    /**
     * Object creation.
     *
     * Uses DOM methods to create an object, apply its attributes and insert
     * its innerHTML. Handles style attribute correctly.
     *
     * @param       tipo    string      Kind of the element to create.
     * @param       attr    Object      Object or Array with attributes coded as key="value".
     * @param       cod     String      Node's innerHTML.
     *
     * @return      DOM fragment.
     *
     * @see deteleObject
     */
    this.createObject = function (tipo, attr, cod) {
        var z = document.createElement(tipo.toUpperCase());
        for (var a in attr) {
            if (attr.hasOwnProperty(a)) {
                if (a == "style") {
                    self.applyCSS(z, attr[a]);
                } else {
                    z.setAttribute(a, attr[a]);
                }
            }
        }
        if (typeof cod == 'string') {
            z.innerHTML = cod;
        }
        if (typeof cod == 'object') {
            z.appendChild(cod);
        }
        return z;
    };


    /**
     * Object deletion.
     *
     * Uses DOM methods to delete an object. Can be used with a DOM
     * node or with an ID.
     *
     * @param       obj     DOM fragment/String       DOM fragment (or id) to delete.
     *
     * @return      bool        Original function return if OK, false if error.
     *
     * @see deteleObject
     */
    this.deleteObject = function (obj) {
        // normalizamos el objeto
        if (typeof obj == 'string') {
            obj = document.getElementById(obj);
        }
        if (!obj || typeof obj != 'object') {
            return false;
        }
        obj.innerHTML = "";
        return obj.parentNode.removeChild(obj);
    };



    /**
     * Find Object position.
     *
     * Uses DOM methods to find an object coordinates. Can be used with a DOM
     * node or with an ID.
     *
     * @param       obj     DOM fragment/String       DOM fragment (or id) to delete.
     *
     * @return      decimal Object:
     *      0/left:     left position.
     *      1/top:      top position.
     *      2/right:    right position.
     *      3/bottom:   bottom position.
     */
    this.findPos = function (obj) {
        // normalizamos el objeto
        if (typeof obj == 'string') {
            obj = document.getElementById(obj);
        }
        if (typeof obj != 'object' || !obj) {
            return false;
        }

        var curleft = 0, curtop = 0, W = obj.offsetWidth, H = obj.offsetHeight;
        if (obj.offsetParent) {
            do {
                curleft += obj.offsetLeft;
                curtop += obj.offsetTop;
                obj = obj.offsetParent;
            } while (obj);
        }
        return {
            0: curleft,
            1: curtop,
            2: curleft + W,
            3: curtop + H,
            left: curleft,
            top: curtop,
            right: curleft + W,
            bottom: curtop + H
        };
    };


    /**
     * Left trim.
     *
     * Trims extra spaces at the left side of a array.
     *
     * @param       str     String      String to trim.
     *
     * @return      String      Trimmed string. "" if error.
     *
     * @see trim
     * @see trimR
     */
    this.trimL = function (str) {
        return (typeof str == 'string' ? str.replace(/^\s+/, '') : "");
    };


    /**
     * Right trim.
     *
     * Trims extra spaces at the right side of a string.
     *
     * @param       str     String      String to trim.
     *
     * @return      String      Trimmed string. "" if error.
     *
     * @see trim
     * @see trimL
     */
    this.trimR = function (str) {
        return (typeof str == 'string' ? str.replace(/\s+$/, '') : "");
    };


    /**
     * Trim.
     *
     * Trims extra spaces at the left and right sides of a string.
     *
     * @param       str     String      String to trim.
     *
     * @return      String      Trimmed string. "" if error.
     *
     * @see trimR
     * @see trimL
     */
    this.trim = function (str) {
        return (typeof str == 'string' ? str.replace(/^\s+/, '').replace(/\s+$/, '') : "");
    };


    /**
     * Applyes CSS.
     *
     * Applyes a CSS definition string to a node. It's a cross-browsers
     * method.
     *
     * @param       o       DOM node/String     DOM node (or id) to apply CSS definition.
     * @param       css     String              CSS definition.
     *
     * @return      boolean     true when OK, false if error.
     */
    this.applyCSS = function (o, css) {
        // normalizamos el objeto
        if (typeof o == 'string') {
            o = document.getElementById(o);
        }
        if (typeof o != 'object' || !o) {
            return false;
        }

        return (typeof css != 'string' ? false : o.style.cssText = css);
    };



    /**
     * Copy CSS.
     *
     * Applyes a CSS from an origin DOM node to a destiny one. It uses
     * JavaScript style access to be cross-browser.
     *
     * @param       o       DOM node/String     DOM node (or id) to get CSS definition, source.
     * @param       d       DOM node/String     DOM node (or id) to apply CSS definition, destiny.
     *
     * @return      boolean     true when OK, false if error.
     */
    this.copyCSS = function (o, d) {
        // normalizamos el objeto
        if (typeof o == 'string') {
            o = document.getElementById(o);
        }
        if (typeof o != 'object' || !o) {
            return false;
        }

        // normalizamos el objeto
        if (typeof d == 'string') {
            d = document.getElementById(d);
        }
        if (typeof d != 'object' || !d) {
            return false;
        }

        return (d.style.cssText = o.style.cssText);
    };


    /**
     * Dump object.
     *
     * Debug function which returns an XHTML dump of an object.
     *
     * @param   o       Object      Object to dump.
     *
     * @return      String      Object dump.
     */
    this.dumpObject = function (o) {
        if (typeof o === 'object') {
            var t = ["&lt;object&gt; {<dir>"];
            for (var i in o) {
                if (o.hasOwnProperty(i)) { 
                    t[t.length] = i + ": " + self.dumpObject(o[i]) + "<br />";
                }
            }
            t[t.length] = "}</dir>";
            return t.join('');
        }
        return ("&lt;" + (typeof o) + "&gt; " + o);
    };



    /**
     * Window properties.
     *
     * Returns an object with this properties:
     *      0 and windowWidth: Inner width of the window.
     *      1 and windowHeight: Inner height of the window.
     *      2 and documentWidth: Width of the document (with scrolls).
     *      3 and documentHeight: Height of the document (with scrolls).
     *      4 and scrollX: Actual horitzontal scroll.
     *      5 and scrollY: Actual vertical scroll.
     *
     * @return      Integers Array      False if error.
     */
    this.windowProperties = function () {
        if (document.body) {
            var Width = (document.compatMode == 'CSS1Compat' && !window.opera ? document.documentElement.clientWidth : document.body.clientWidth);
            var Height = (document.compatMode == 'CSS1Compat' && !window.opera ? document.documentElement.clientHeight : document.body.clientHeight);
            var scrOfX = false;
            var scrOfY = false;
            var xWithScroll = false;
            var yWithScroll = false;
            if (window.scrollX) {
                scrOfX = window.scrollX;
                scrOfY = window.scrollY;
            } else {
                scrOfX = document.documentElement.scrollLeft + document.body.scrollLeft;
                scrOfY = document.documentElement.scrollTop + document.body.scrollTop;
            }
            if (window.innerHeight && window.scrollMaxY) { // FF
                yWithScroll = window.innerHeight + window.scrollMaxY;
                xWithScroll = window.innerWidth + window.scrollMaxX;
            } else if (document.body.scrollHeight > document.body.offsetHeight) { // Todo menos IE-Mac
                yWithScroll = document.body.scrollHeight;
                xWithScroll = document.body.scrollWidth;
            } else { // IE6 Strict, Mozilla (no FF) y Safari
                yWithScroll = document.body.offsetHeight;
                xWithScroll = document.body.offsetWidth;
            }
            return {
                0: Width,
                1: Height,
                2: xWithScroll,
                3: yWithScroll,
                4: scrOfX,
                5: scrOfY,
                "windowWidth": Width,
                "windowHeight": Height,
                "documentWidth": xWithScroll,
                "documentHeight": yWithScroll,
                "scrollX": scrOfX,
                "scrollY": scrOfY
            };
        }
        return false;
    };


    /**
     * Opens a new window.
     *
     * Opens a new window centered in the screen.
     *
     * @return new window object
     */
    this.openWindow = function (url, w, h) {
        var x = screen.width / 2 - w / 2;
        var y = screen.height / 2 - h / 2;
        return window.open(url, "", "scrollbars=auto,width=" + w + ",height=" + h + ",screenX=" + x + ",screenY=" + y + ",left=" + x + ",top=" + y);
    };



    this.xhtml = {
        /**
         * Transforms code to XHTML.
         *
         * Gets xhtml valid code from a node.
         *
         * @param       String  t   Code to be transformed.
         *
         * @return      String  XHTML valid equivalent code.
         */
        getFromCode: function (t) {
            var n = self.createObject("DIV", null, t);
            var r = self.xhtml.getFromNode(t);
            self.deleteObject(r);
            return r;
        },


        /**
         * Transforms code to XHTML.
         *
         * Gets xhtml valid code from a node.
         *
         * @param       node            DOM node    DOM node to get CSS definition, source.
         * @param       other params    optional, used for recursivity.
         *
         * @return      string      XHTML valid equivalent code.
         */
        getFromNode: function (node, lang, encoding, need_nl, inside_pre) {
            var i, text = '', tag_name, page_mode = true, media_align = '';
            var need_nl_before = '|div|p|table|tbody|tr|td|th|title|head|body|script|comment|li|meta|h1|h2|h3|h4|h5|h6|hr|ul|ol|option|link|';
            var need_nl_after = '|html|head|body|p|th|style|';
            var re_comment = new RegExp("^<!--(([a]|[^a])*)-->$");
            var children = node.childNodes;
            var child_length = children.length;
            var do_nl = (need_nl ? true : false);
            var re_parsed_val = new Date().getTime();
            for (i = 0; i < child_length; i++) {
                var child = children[i];
                if (document.all) {
                    if (child.getAttribute && child.getAttribute('re_parsed') == re_parsed_val) {
                        continue;
                    }
                    if (child.setAttribute) {
                        child.setAttribute('re_parsed', re_parsed_val);
                    }
                }
                if (child.parentNode && String(node.tagName).toLowerCase() != String(child.parentNode.tagName).toLowerCase()) {
                    continue;
                }
                switch (child.nodeType) {
                case 1: //ELEMENT_NODE
                    tag_name = String(child.tagName).toLowerCase();
                    //store value of align attribute as IE cannot handle it properly
                    if (document.all && tag_name == 'embed') {
                        var parameter = /align=("[^\"]*"|'[^\']*'|[^\"\'\s]*)(\s|\>)/i;
                        var align_code = String(child.outerHTML).match(parameter);
                        if (align_code) {
                            align_code = align_code[1];
                            media_align = align_code.replace(/("|')/g, "");
                        }
                    }
                    if (tag_name == '') {
                        break;
                    }
                    if (tag_name == 'meta') {
                        var meta_name = String(child.name).toLowerCase();
                        if (meta_name == 'generator') {
                            break;
                        }
                    }
                    if (document.all && tag_name == 'object' && !(child.canHaveChildren || child.hasChildNodes())) {
                        text += self.xhtml.fixObjectCode(child.outerHTML);
                        continue;
                    }
                    if (!need_nl && tag_name == 'body') {
                        page_mode = false;
                    }
                    if (tag_name == '!') {
                        var parts = re_comment.exec(child.text);
                        if (parts) {
                            //the last char of the comment text must not be a hyphen
                            text += self.xhtml.fixComment(parts[1]);
                        }
                    } else {
                        if (tag_name == 'html') {
                            text = '<.?xml version="1.0" encoding="' + encoding + '"?>\n<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n';
                        }
                        if (need_nl_before.indexOf('|' + tag_name + '|') != -1) {
                            if ((do_nl || text != '') && !inside_pre) {
                                text += '\n';
                            } else {
                                do_nl = true;
                            }
                        }
                        text += '<' + tag_name;
                        //add attributes
                        var attr = child.attributes;
                        var attr_length = attr.length;
                        var attr_value, attr_lang = false, attr_xml_lang = false, attr_xmlns = false, is_alt_attr = false;
                        for (var j = 0; j < attr_length; j++) {
                            var attr_name = attr[j].nodeName.toLowerCase();
                            if (attr_name == 're_parsed') {
                                continue;
                            }
                            if (!attr[j].specified && attr_name != 'selected' && attr_name != 'style' && attr_name != 'value' && attr_name != 'shape' && attr_name != 'coords') {
                                continue; //IE 5.0
                            }
                            if ((attr_name == 'shape' || attr_name == 'coords') && tag_name != 'area') {
                                continue;
                            }
                            if (attr_name == 'selected' && !child.selected || attr_name == 'style' && child.style.cssText == '') {
                                continue;
                            }
                            if (attr_name == '_moz_dirty' || attr_name == '_moz_resizing' || attr_name == '_moz-userdefined' || tag_name == 'br' && attr_name == 'type' && child.getAttribute('type') == '_moz') {
                                continue;
                            }
                            var valid_attr = true;
                            switch (attr_name) {
                            case "style":
                                attr_value = child.style.cssText;
                                break;
                            case "class":
                                attr_value = child.className;
                                break;
                            case "http-equiv":
                                attr_value = child.httpEquiv;
                                break;
                            case "noshade": //this set of choices will extend
                            case "checked":
                            case "selected":
                            case "multiple":
                            case "nowrap":
                            case "disabled":
                                attr_value = attr_name;
                                break;
                            case "name":
                                attr_value = (child.name ? child.name : child.getAttribute('name'));
                                break;
                            case "for":
                                attr_value = child.htmlFor;
                                break;
                            default:
                                try {
                                    attr_value = child.getAttribute(attr_name, 2);
                                } catch (e) {
                                    valid_attr = false;
                                }
                            }
                            if (tag_name == 'embed') {
                                switch (attr_name) {
                                case 'align':
                                    attr_value = (media_align ? media_align : eval('child.' + attr_name));
                                    break;
                                case 'showstatusbar':
                                case 'showcontrols':
                                case 'autostart':
                                case 'type':
                                    attr_value = attr[j].nodeValue;
                                    break;
                                }
                            }
                            if (attr_name == 'lang' && tag_name == 'html') {
                                attr_lang = true;
                                attr_value = lang;
                            }
                            if (attr_name == 'xml:lang') {
                                attr_xml_lang = true;
                                attr_value = lang;
                            }
                            if (attr_name == 'xmlns') {
                                attr_xmlns = true;
                            }
                            if (tag_name == 'object' && attr_name == 'src' && document.all) {
                                attr_value = self.xhtml.fixObjectSrc(child.outerHTML);
                            }
                            if (valid_attr && (tag_name != 'li' || attr_name != 'value')) {
                                text += ' ' + attr_name + '="' + self.xhtml.fixAttribute(attr_value) + '"';
                            }
                            if (attr_name == 'alt') {
                                is_alt_attr = true;
                            }
                        }
                        if (tag_name == 'img' && !is_alt_attr) {
                            text += ' alt=""';
                        }
                        if (tag_name == 'html') {
                            if (!attr_lang) {
                                text += ' lang="' + lang + '"';
                            }
                            if (!attr_xml_lang) {
                                text += ' xml:lang="' + lang + '"';
                            }
                            if (!attr_xmlns) {
                                text += ' xmlns="http://www.w3.org/1999/xhtml"';
                            }
                        }
                        if (child.canHaveChildren || child.hasChildNodes()) {
                            text += '>';
                            text += self.xhtml.getFromNode(child, lang, encoding, true, (inside_pre || tag_name == 'pre' ? true : false));
                            text += '</' + tag_name + '>';
                        } else {
                            if (tag_name == 'style' || tag_name == 'title' || tag_name == 'script' || tag_name == 'textarea' || tag_name == 'a') {
                                text += '>';
                                var inner_text;
                                if (tag_name == 'script') {
                                    inner_text = child.text;
                                } else {
                                    inner_text = child.innerHTML;
                                }
                                if (tag_name == 'style') {
                                    inner_text = String(inner_text).replace(/[\n]+/g, '\n');
                                }
                                text += inner_text + '</' + tag_name + '>';
                            } else {
                                text += ' />';
                            }
                        }
                        
                        if (need_nl_after.indexOf('|' + tag_name + '|') != -1) {
                            if ((do_nl || text != '') && !inside_pre) {
                                text += '\n';
                            }
                        }
                    }
                    break;
                case 3:
                    if (!inside_pre) {
                        if (child.nodeValue != '\n') {
                            text += self.xhtml.fixEntities(self.xhtml.fixText(child.nodeValue));
                        }
                    } else {
                        text += child.nodeValue;
                    }
                    break;
                case 8:
                    text += self.xhtml.fixComment(child.nodeValue);
                    break;
                default:
                    break;
                }
            }
            if (!page_mode) {
                text = text.replace(/<\/?head>[\n]*/gi, "").replace(/<head \/>[\n]*/gi, "").replace(/<\/?body>[\n]*/gi, "");
            }
            return text;
        },


        /**
         * Fixes comments.
         *
         * Last character of a comment can't be a hyphen. If it's
         * the case, we add a trailing space.
         *
         * @param       t       String      Original string.
         *
         * @return      String      Fixed string.
         */
        fixComment: function (t) {
            var re_hyphen = new RegExp("-$");
            t = t.replace(/--/g, "__");
            if (re_hyphen.exec(t)) {
                t += " ";
            }
            return "<!--" + t + "-->";
        },

        /**
         * Fixes text.
         *
         * Replaces some entities and delete extra spaces.
         *
         * @param       t       String      Original string.
         *
         * @return      String      Fixed string.
         */
        fixText: function (t) {
/*ORIGINAL:
            var tt = String(t).replace(/\&lt;/g, "#h2x_lt").replace(/\&gt;/g, "#h2x_gt");
            tt = tt.replace(/\n{2,}/g, "\n").replace(/\&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\u00A0/g, "&nbsp;");
            return tt.replace(/#h2x_lt/g, "&lt;").replace(/#h2x_gt/g, "&gt;");
*/
            return  String(t).replace(/\n{2,}/g, "\n").replace(/\&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\u00A0/g, "&nbsp;");
        },


        /**
         * Fixes attributes.
         *
         * Replaces some entities.
         *
         * @param       t       String      Original string.
         *
         * @return      String      Fixed string.
         */
        fixAttribute: function (t) {
/*ORIGINAL:
            var tt = String(t).replace(/\&lt;/g, "#h2x_lt").replace(/\&gt;/g, "#h2x_gt");
            tt = tt.replace(/\&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;");
            return tt.replace(/#h2x_lt/g, "&lt;").replace(/#h2x_gt/g, "&gt;");
*/
            return  String(t).replace(/\&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
        },


        /**
         * Fixes object src attribute.
         *
         * Search for all ways of set src attribute:
         *      - src=xxxxxx.
         *      - src='xxxxxx'.
         *      - src="xxxxxx".
         *  In all cases it will return xxxxxx.
         *
         * @param       t       String      Object string.
         *
         * @return      String      src attribute from object or empty string.
         */
        fixObjectSrc: function (t) {
            var obj_tag_parts = t.match(/<object ([^>]+)>/i);
            if (obj_tag_parts) {
                var src_value = obj_tag_parts[1].match(/src="([^"]+)"/i);
                if (!src_value) {
                    src_value = obj_tag_parts[1].match(/src='([^']+)'/i);
                    if (!src_value) {
                        src_value = obj_tag_parts[1].match(/src=([^ ]+)/i);
                    }
                }
                if (src_value) {
                    return src_value[1];
                }
            }
            return '';
        },


        /**
         * Fixes object code lowercasing all attributes.
         *
         * Search for all attributes and lowercase them.
         *
         * @param       t       String      Object string.
         *
         * @return      String      Same string lowercased.
         */
        fixObjectCode: function (text) {
/*TODO: Se puede eliminar el EMBED... */
            return String(text).replace(/ style=/gi, ' style=').replace(/ codeBase=/gi, ' codebase=').replace(/ height=/gi, ' height=').replace(/ width=/gi, ' width=').replace(/ align=/gi, ' align=').replace(/ classid=/gi, ' classid=').replace(/ src=/gi, ' src=').replace(/ NAME=/gi, ' name=').replace(/ VALUE=/gi, ' value=').replace(/ quality=/gi, ' quality=').replace(/ TYPE=/gi, ' type=').replace(/ PLUGINSPAGE=/gi, ' pluginspage=').replace(/<OBJECT /gi, '<object ').replace(/<\/OBJECT>/gi, '</object>').replace(/<PARAM /gi, '<param ').replace(/<\/PARAM>/gi, '</param>').replace(/<EMBED /gi, '<embed ').replace(/<\/EMBED>/gi, '</embed>');
        },


        /**
         * Array of charcodes and its entities.
         *
         * It's used to replace all ocurrences of these
         * special characters to its entity.
         */
        ents: {
            8364 : "euro",
            402  : "fnof",
            8240 : "permil",
            352  : "Scaron",
            338  : "OElig",
            381  : "#381",
            8482 : "trade",
            353  : "scaron",
            339  : "oelig",
            382  : "#382",
            376  : "Yuml",
            162  : "cent",
            163  : "pound",
            164  : "curren",
            165  : "yen",
            166  : "brvbar",
            167  : "sect",
            168  : "uml",
            169  : "copy",
            170  : "ordf",
            171  : "laquo",
            172  : "not",
            173  : "shy",
            174  : "reg",
            175  : "macr",
            176  : "deg",
            177  : "plusmn",
            178  : "sup2",
            179  : "sup3",
            180  : "acute",
            181  : "micro",
            182  : "para",
            183  : "middot",
            184  : "cedil",
            185  : "sup1",
            186  : "ordm",
            187  : "raquo",
            188  : "frac14",
            189  : "frac12",
            190  : "frac34",
            191  : "iquest",
            192  : "Agrave",
            193  : "Aacute",
            194  : "Acirc",
            195  : "Atilde",
            196  : "Auml",
            197  : "Aring",
            198  : "AElig",
            199  : "Ccedil",
            200  : "Egrave",
            201  : "Eacute",
            202  : "Ecirc",
            203  : "Euml",
            204  : "Igrave",
            205  : "Iacute",
            206  : "Icirc",
            207  : "Iuml",
            208  : "ETH",
            209  : "Ntilde",
            210  : "Ograve",
            211  : "Oacute",
            212  : "Ocirc",
            213  : "Otilde",
            214  : "Ouml",
            215  : "times",
            216  : "Oslash",
            217  : "Ugrave",
            218  : "Uacute",
            219  : "Ucirc",
            220  : "Uuml",
            221  : "Yacute",
            222  : "THORN",
            223  : "szlig",
            224  : "agrave",
            225  : "aacute",
            226  : "acirc",
            227  : "atilde",
            228  : "auml",
            229  : "aring",
            230  : "aelig",
            231  : "ccedil",
            232  : "egrave",
            233  : "eacute",
            234  : "ecirc",
            235  : "euml",
            236  : "igrave",
            237  : "iacute",
            238  : "icirc",
            239  : "iuml",
            240  : "eth",
            241  : "ntilde",
            242  : "ograve",
            243  : "oacute",
            244  : "ocirc",
            245  : "otilde",
            246  : "ouml",
            247  : "divide",
            248  : "oslash",
            249  : "ugrave",
            250  : "uacute",
            251  : "ucirc",
            252  : "uuml",
            253  : "yacute",
            254  : "thorn",
            255  : "yuml",
            913  : "Alpha",
            914  : "Beta",
            915  : "Gamma",
            916  : "Delta",
            917  : "Epsilon",
            918  : "Zeta",
            919  : "Eta",
            920  : "Theta",
            921  : "Iota",
            922  : "Kappa",
            923  : "Lambda",
            924  : "Mu",
            925  : "Nu",
            926  : "Xi",
            927  : "Omicron",
            928  : "Pi",
            929  : "Rho",
            931  : "Sigma",
            932  : "Tau",
            933  : "Upsilon",
            934  : "Phi",
            935  : "Chi",
            936  : "Psi",
            937  : "Omega",
            8756 : "there4",
            8869 : "perp",
            945  : "alpha",
            946  : "beta",
            947  : "gamma",
            948  : "delta",
            949  : "epsilon",
            950  : "zeta",
            951  : "eta",
            952  : "theta",
            953  : "iota",
            954  : "kappa",
            955  : "lambda",
            956  : "mu",
            957  : "nu",
            958  : "xi",
            959  : "omicron",
            960  : "pi",
            961  : "rho",
            962  : "sigmaf",
            963  : "sigma",
            964  : "tau",
            965  : "upsilon",
            966  : "phi",
            967  : "chi",
            968  : "psi",
            969  : "omega",
            8254 : "oline",
            8804 : "le",
            8260 : "frasl",
            8734 : "infin",
            8747 : "int",
            9827 : "clubs",
            9830 : "diams",
            9829 : "hearts",
            9824 : "spades",
            8596 : "harr",
            8592 : "larr",
            8594 : "rarr",
            8593 : "uarr",
            8595 : "darr",
            8220 : "ldquo",
            8221 : "rdquo",
            8222 : "bdquo",
            8805 : "ge",
            8733 : "prop",
            8706 : "part",
            8226 : "bull",
            8800 : "ne",
            8801 : "equiv",
            8776 : "asymp",
            8230 : "hellip",
            8212 : "mdash",
            8745 : "cap",
            8746 : "cup",
            8835 : "sup",
            8839 : "supe",
            8834 : "sub",
            8838 : "sube",
            8712 : "isin",
            8715 : "ni",
            8736 : "ang",
            8711 : "nabla",
            8719 : "prod",
            8730 : "radic",
            8743 : "and",
            8744 : "or",
            8660 : "hArr",
            8658 : "rArr",
            9674 : "loz",
            8721 : "sum",
            8704 : "forall",
            8707 : "exist",
            8216 : "lsquo",
            8217 : "rsquo",
            161  : "iexcl",
            977  : "thetasym",
            978  : "upsih",
            982  : "piv",
            8242 : "prime",
            8243 : "Prime",
            8472 : "weierp",
            8465 : "image",
            8476 : "real",
            8501 : "alefsym",
            8629 : "crarr",
            8656 : "lArr",
            8657 : "uArr",
            8659 : "dArr",
            8709 : "empty",
            8713 : "notin",
            8727 : "lowast",
            8764 : "sim",
            8773 : "cong",
            8836 : "nsub",
            8853 : "oplus",
            8855 : "otimes",
            8901 : "sdot",
            8968 : "lceil",
            8969 : "rceil",
            8970 : "lfloor",
            8971 : "rfloor",
            9001 : "lang",
            9002 : "rang",
            710  : "circ",
            732  : "tilde",
            8194 : "ensp",
            8195 : "emsp",
            8201 : "thinsp",
            8204 : "zwnj",
            8205 : "zwj",
            8206 : "lrm",
            8207 : "rlm",
            8211 : "ndash",
            8218 : "sbquo",
            8224 : "dagger",
            8225 : "Dagger",
            8249 : "lsaquo",
            8250 : "rsaquo"
        },

        /**
         * Replaces special chars for its entities.
         *
         * @param       text    String      Object string.
         *
         * @return      String      Fixed string.
         *
         * @see ngstk.xhtml.ents
         */
        fixEntities: function (text) {
            var i;
            var new_text = '';
            var temp = new RegExp("[a]|[^a]", "g");
            var parts = text.match(temp);
            if (!parts) {
                return text;
            }
            for (i = 0; i < parts.length; i++) {
                var c_code = parseInt(parts[i].charCodeAt(), 10);
                new_text += (self.xhtml.ents[c_code] ? "&" + self.xhtml.ents[c_code] + ";" : parts[i]);
            }
            return new_text;
        }

    }; // end ngstk_xhtml object






    this.mouse = {
        /**
         * Mouse position objects:
         *
         *   ngstk.mouse.Xarea       X position in the viewing window.
         *   ngstk.mouse.Yarea       Y position in the viewing window.
         *   ngstk.mouse.Xpage       X position at whole document.
         *   ngstk.mouse.Ypage       Y position at whole document.
         */
        Xarea: 0,
        Yarea: 0,
        Xpage: 0,
        Ypage: 0,

        /**
         * Variable to store dragging object and properties.
         */
        dragObj: {},


        /**
         * Tracking mouse function. It's attached to mousemove event.
         *
         *   @param     e       Recieves the event in Mozilla browsers.
         */
        onMove: function (e) {
            e = e || window.event;
            self.mouse.Xarea = e.clientX;
            self.mouse.Yarea = e.clientY;
            if (document.documentElement && document.body && document.body.scrollTop) {
                self.mouse.Xscroll = document.documentElement.scrollLeft + document.body.scrollLeft;
                self.mouse.Yscroll = document.documentElement.scrollTop + document.body.scrollTop;
            } else {
                self.mouse.Xscroll = window.scrollX;
                self.mouse.Yscroll = window.scrollY;
            }
            self.mouse.Xpage = self.mouse.Xarea + self.mouse.Xscroll;
            self.mouse.Ypage = self.mouse.Yarea + self.mouse.Yscroll;

            // Si hay arrastre de ventana, lo realizamos y cortamos el evento
            if (typeof self.mouse.dragObj.elNode === 'object') {
                self.mouse.dragObj.elNode.style.left = (self.mouse.dragObj.elStartLeft + self.mouse.Xpage - self.mouse.dragObj.cursorStartX) + "px";
                self.mouse.dragObj.elNode.style.top  = (self.mouse.dragObj.elStartTop  + self.mouse.Ypage - self.mouse.dragObj.cursorStartY) + "px";
                if (typeof e.event != 'undefined') {
                    if (typeof e.cancelBubble != 'undefined') {
                        e.cancelBubble = true;
                    }
                    if (typeof e.returnValue != 'undefined') {
                        e.returnValue = false;
                    }
                }
                if (typeof e.preventDefault != 'undefined') {
                    e.preventDefault();
                }
            }
        },


        /**
         * Dragg start action
         *
         * Get initial values to start a dragg:
         *      Stores object to dragg.
         *      Stores inicial X and Y.
         *      Incrases object's z-index by 1.
         *
         * @param     obj     DOM Object/String       DOM object (or its id) to dragg.
         *
         * @return      bool        true if OK, false if object doesn't exist.
         */
        dragStart: function (obj) {
            // Normalizamos el objeto
            if (typeof obj == 'string') {
                obj = document.getElementById(obj);
            }
            if (typeof obj != 'object') {
                return false;
            }
            self.mouse.dragObj.elNode = obj;
            // Guardamos las posiciones iniciales del cursor y del elemento.
            self.mouse.dragObj.cursorStartX = self.mouse.Xpage;
            self.mouse.dragObj.cursorStartY = self.mouse.Ypage;
            var tmp = self.findPos(self.mouse.dragObj.elNode);
            self.mouse.dragObj.elStartLeft = tmp[0];
            self.mouse.dragObj.elStartTop = tmp[1];
//            if (isNaN(self.mouse.dragObj.elStartLeft) ) self.mouse.dragObj.elStartLeft = self.mouse.Xpage;
//            if (isNaN(self.mouse.dragObj.elStartTop) )  self.mouse.dragObj.elStartTop  = self.mouse.Ypage;
            // Actualizamos el z-index del objeto.
            self.mouse.dragObj.elNode.style.zIndex++;
            return true;
        },


        /**
         * Dragg stop action.
         *
         * Ends dragging action, clean dragging object data
         * and decrases z-index by 1 (restores original).
         */
        dragStop: function () {
            if (typeof self.mouse.dragObj.elNode == 'object') {
                self.mouse.dragObj.elNode.style.zIndex--;
                self.mouse.dragObj = {};
            }
        }

    }; // Final del objeto ngstk.mouse, funciones CORE.


    // Iniciamos el rastreo del raton y el liberar arrastre
    self.addEvent(document, "mousemove", self.mouse.onMove);
    self.addEvent(document, "mouseup", self.mouse.dragStop);





    this.mouse.animation = {
        /**
         * Pointer movement from actual position to indicated point.
         *
         * Moves a false pointer from actual mouse position
         * to a specified X-Y point of the page. Note that this point is a point
         * of the page's canvas, not of viewing port.
         *
         * @param     destx     integer     X destination position.
         * @param     desty     integer     Y destination position.
         * @param     milis     integer     Miliseconds of the animation.
         * @param     wait      integer     Miliseconds who stands at destiny before dissappear.
         */
        actualToPoint: function (destx, desty, milis, wait) {
            return self.mouse.animation.areaToArea(self.mouse.Xpage, self.mouse.Ypage, destx, desty, milis, wait);
        },


        /**
         * Pointer movement from actual position to the center of an object.
         *
         * Moves a false pointer from actual mouse position
         * to the center of a specified DOM node.
         *
         * @param     obj       DOM Object/String   Destiny's DOM node (or id).
         * @param     milis     integer     Miliseconds of the animation.
         * @param     wait      integer     Miliseconds who stands at destiny before dissappear.
         *
         * @return      bool    true if OK, false if object not found.
         */
        actualToObject: function (obj, milis, wait) {
            // normalizamos el objeto
            if (typeof obj == 'string') {
                obj = document.getElementById(obj);
            }
            if (typeof obj != 'object') {
                return false;
            }
            var dest = self.findPos(obj);
            return self.mouse.animation.areaToArea(self.mouse.Xpage, self.mouse.Ypage, (dest[0] + dest[2]) / 2, (dest[1] + dest[3]) / 2, milis, wait);
        },


        /**
         * Pointer movement from actual position to indicated point.
         *
         * Moves a false pointer from an X-Y point of a page to a
         * specified X-Y point of the page. Note that these points are a points
         * of the page's canvas, not of viewing port.
         *
         * @param     origx     integer     X origin position.
         * @param     origy     integer     Y origin position.
         * @param     destx     integer     X destination position.
         * @param     destY     integer     Y destination position.
         * @param     milis     integer     Miliseconds of the animation.
         * @param     wait      integer     Miliseconds who stands at destiny before dissappear.
         */
        areaToArea: function (origx, origy, destx, desty, milis, wait) {
            origx = parseInt(origx, 10);
            origy = parseInt(origy, 10);
            destx = parseInt(destx, 10);
            desty = parseInt(desty, 10);
            wait = parseInt(wait, 10);
            milis = parseInt(milis, 10);
            var actx = origx;
            var acty = origy;
            if (typeof destx != 'number') {
                destx = -1;
            }
            if (typeof desty != 'number') {
                desty = -1;
            }
            if (typeof milis != 'number') {
                milis = 1;
            }
            if (typeof wait != 'number') {
                wait = 2000;
            }
            if (milis < 1) {
                milis = 1;
            }
            if (wait < 1) {
                wait = 1;
            }
            if (destx >= 0 && desty >= 0) {
                // Creamos la imagen del puntero:
                var a = {
                    "src": self.config.IMAGE_URL + "puntero.gif",
                    "alt": "i",
                    "id": "ngstk_pointer",
                    "title": "Puntero simulado",
                    "border": "0",
                    "style": "position:absolute;top:" + self.mouse.Ypage + "px;left:" + self.mouse.Xpage + "px;z-index:99;"
                };
                document.body.appendChild(self.createObject("img", a));

                // Programamos sus moviemientos (maximo 100):
                var ciclos = milis / 40; // 25fps
                if (ciclos > 100) {
                    ciclos = 100;
                }
                var retardo = milis / ciclos;
                var incx = (destx - actx) / ciclos;
                var incy = (desty - acty) / ciclos;
                for (var i = 1; i < ciclos; i++) {
                    actx += incx;
                    acty += incy;
                    (function (x, y, d) {
                        setTimeout(function () {
                            self.moveObjectToXY('ngstk_pointer', x + "px", y + "px");
                        }, d);
                    }(actx, acty, parseInt(retardo * i, 10)));
                }
                setTimeout(function () {
                    self.moveObjectToXY('ngstk_pointer', destx + "px", desty + "px");
                }, milis);
                setTimeout(function () {
                    self.deleteObject('ngstk_pointer');
                }, parseInt(milis + wait, 10));
            }
        }
    }; // Fin objeto ngstk.mouse.animation (funciones EXTRA)

    // Precargamos la imagen del puntero
    self.preloadimg([self.config.IMAGE_URL + "puntero.gif"]);







    this.ajax = new (function Ajax() {

        /**
         * XML request internal function.
         *
         * Handles cross-browser AJAX requests, parameters and optins.
         *
         * @param     url       String          URL to make request.
         * @param     params    String/Object   Optional. Parameters to send. False, null or undefined to send nothing.
         * @param     metodo    String          Optional. Method/verb to use: GET, POST, PUT, UPLOAD.... (GET in default).
         * @param     okfn      Function        Optional. Function called when OK.
         * @param     statusfn  Function        Optional. Function called when status changes.
         * @param     errorfn   Function        Optional. Function called when error occurs.
         * @param     extra     Object          Optional. Extra parameter passed to okfn, statusfn, errorfn as second parameter.
         *
         * @return      Boolean     True if OK, false if no AJAX support.
         */
        function Xmlreq(url, params, metodo, okfn, statusfn, errorfn, extra, headers) {
            // Add extra parameter to avoid cache
            if (typeof url !== 'string') {
                return false;
            }
            url = url.replace(/#.*$/, ''); // Eliminamos las anclas.
            url += ((url.indexOf("?") == -1) ? "?" : "&");  // Concatenamos un parametro
            url += Math.random(1000);      // random para que no haga cache.

            // Normalize parameters
            if (typeof params == 'object') {
                var tmp = [];
                for (var idx in params) {
                    if (params.hasOwnProperty(idx)) {
                        tmp[tmp.length] = encodeURIComponent(idx) + "=" + encodeURIComponent(params[idx]);
                    }
                }
                params = tmp.join('&');
            }
            if (typeof params != 'string' || !params) {
                params = "";
            }

            // Normalize method
            metodo = ("" + metodo).toUpperCase();

            // if we use GET we have to concatenate the parameters
            if (metodo != "POST" && params != "") {
                // Take in mind we have at least the nocache parameter, so always use '&' to concatenate
                url += "&" + params;
            }

            // Get the xmlreq object
            var xmlreq;
            if (typeof ActiveXObject != "undefined") {
                xmlreq = new ActiveXObject("Microsoft.XMLHTTP");
            } else if (XMLHttpRequest) {
                xmlreq = new XMLHttpRequest();
            }

            // Do the xml-request
            if (xmlreq) {
                xmlreq.onreadystatechange = function () {
                    if (typeof statusfn === 'function') {
                        statusfn(xmlreq, extra);
                    }
                    if (xmlreq.readyState == 4) {
                        if (xmlreq.status >= 200 && xmlreq.status < 400) {
                            if (typeof okfn === 'function') {
                                okfn(xmlreq, extra);
                            }
                        } else {
                            if (typeof errorfn === 'function') {
                                errorfn(xmlreq, extra);
                            }
                        }
                    }
                }; // Fin de la funcion status.

                // Process the headers
                if (headers) {
                    for (var k in headers) {
                        if (headers.hasOwnProperty(k)) {
                            xmlreq.setRequestHeader(k, headers[k]);
                        }
                    }
                }
                xmlreq.open(metodo, url, true);
                xmlreq.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                if (metodo == "POST") {
                    xmlreq.setRequestHeader("Content-length", params.length);
                    xmlreq.setRequestHeader("Connection", "close");
                    xmlreq.send(params);
                } else {
                // Default method is GET
                    if (typeof ActiveXObject != "undefined") {
                        xmlreq.send();
                    } else {
                        xmlreq.send(null);
                    }
                }
            } else {
/*TODO: Implement support to xml-rpc throught iframes and return true */
                return false;
            }
            return true;
        }      // Subclass end



        /**
         * XML request manager core function.
         *
         * Handles cross-browser AJAX requests, parameters and optins and
         * allows send and recieve data.
         *
         * @param     url       String          URL to make request.
         * @param     params    String/Object   Optional. Parameters to send. False, null or undefined to send nothing.
         * @param     metodo    String          Optional. Method/verb to use: GET, POST, PUT, UPLOAD.... (GET in default).
         * @param     okfn      Function        Optional. Function called when OK.
         * @param     statusfn  Function        Optional. Function called when status changes.
         * @param     errorfn   Function        Optional. Function called when error occurs.
         * @param     extra     Object          Optional. Extra parameter passed to okfn, statusfn, errorfn as second parameter.
         *
         * @return      Boolean     True if OK, false if no AJAX support.
         */
        this.sendAndRecieve = function (url, params, metodo, okfn, statusfn, errorfn, extra) {
            return (new Xmlreq(url, params, metodo, okfn, statusfn, errorfn, extra));
        };


        /**
         * Function alias.
         *
         * @see ngstk.ajax.sendAndRecieve
         */
        this.send = this.sendAndRecieve;


        /**
         * XML request manager recieve function.
         *
         * Alias to AJAX core function without data to send.
         *
         * @param     url       String          URL to make request.
         * @param     metodo    String          Optional. Method/verb to use: GET, POST, PUT, UPLOAD.... (GET in default).
         * @param     okfn      Function        Optional. Function called when OK.
         * @param     statusfn  Function        Optional. Function called when status changes.
         * @param     errorfn   Function        Optional. Function called when error occurs.
         * @param     extra     Object          Optional. Extra parameter passed to okfn, statusfn, errorfn as second parameter.
         *
         * @return      Boolean     True if OK, false if no AJAX support.
         */
        this.recieve = function (url, metodo, okfn, statusfn, errorfn, extra) {
            return self.ajax.sendAndRecieve(url, false, metodo, okfn, statusfn, errorfn, extra);
        };

    })(); // End of ajax object









        /**
         * Audio functions. Handles .mid and .wav audio.
         */
    this.audio = {

        /**
         * Internal active audio playing count.
         */
        activep: 0,


        /**
         * Audio activation variable. Permits override all audio commands.
         */
        active: true,


        /**
         * Plays an audio file.
         *
         * Builds audio object with some optional delay.
         *
         * @param     audioFile String          URL to play.
         * @param     dur       Integer         Time to maintain audio object in seconds. It has to be little longer than audio file.
         * @param     del       Integer         Optional. Time to wait before audio play, in miliseconds.
         * @param     id        String          Optional. ID of audio object. If not pressent, one uniqueID will be assigned.
         * @param     ovln      Boolean         Optional. Permits next audio files to be overlapped with this.
         * @param     force     Boolean         Optional. Forces playing althrought there's another file playing.
         *
         * @return      Boolean     True if OK, false if error.
         */
        play: function (audioFile, dur, del, id, ovln, force) {
            if (self.audio.active && typeof dur === 'number' && typeof audioFile === 'string') {
                if (typeof del !== 'number') {
                    del = 0;
                }
                if (typeof id !== 'string') {
                    id = "ngstk_audioObject" + self.getUniqueID();
                }
                if (!ovln) {
                    ovln = false;
                }
                if (!force) {
                    force = false;
                }
                var duration = (dur * 1000) + del + 300;
                setTimeout(function () {
                    self.audio.build(audioFile, id, duration, false, ovln, force);
                }, del);
                return true;
            }
            return false;
        },


        /**
         * Decrases the internal active audio playing count.
         */
        playDecrase: function () {
            if (self.audio.active > 0) {
                self.audio.active--;
            }
        },


        /**
         * Makes all process of audio playing.
         *
         * Builds audio object with some optional delay.
         *
         * @param     file      String          URL to play.
         * @param     id        String          Optional. ID of audio object. If not pressent, one uniqueID will be assigned.
         * @param     time      Integer         Time to maintain audio object in miliseconds. It has to be little longer than audio file. If equal or less than zero of false it's infinite.
         * @param     ovln      Boolean         Optional. Permits next audio files to be overlapped with this.
         * @param     force     Boolean         Optional. Forces playing althrought there's another file playing.
         *
         * @return      Boolean     True if OK, false if error.
         */
        build: function (file, id, time, loop, ovln, force) {
            if ((force || self.audio.active === 0) && self.audio.active) {
                if (!ovln) {
                    self.audio.active++;
                }
                var cod = (document.all ? "&nbsp;<bgsound src=\"" + file + "\" autostart=\"true\" loop=\"" + loop + "\"></bgsound>" : '&nbsp;<object width="1" height="1" classid="CLSID:22d6f312-b0f6-11d0-94ab-0080c74c7e95" codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701" type="application/x-oleobject"><param name="fileName" value="' + file + '"><param name="animationatStart" value="0"><param name="transparentatStart" value="1"><param name="autoStart" value="1"><param name="ShowControls" value="0"><param name="ShowDisplay" value="0"><param name="ShowStatusBar" value="0"><param name="loop" value="' + loop + '"><embed type="application/x-mplayer2" pluginspage="http://microsoft.com/windows/mediaplayer/en/download/" id="mediaPlayer" name="mediaPlayer" displaysize="4" autosize="0" bgcolor="transparent" showcontrols="0" showtracker="0" showdisplay="0" showstatusbar="0" videoborder3d="0" width="1" height="1" src="' + file + '" autostart="1" designtimesp="5311" loop="' + loop + '"></embed></object>');
                var y = self.createObject("div", {
                    id: id,
                    style: "top:0; left:0; position:absolute; z-index:-1;"
                }, cod);
                document.body.appendChild(y);
                if (!ovln && time && time > 0) {
                    setTimeout(self.audio.playDecrase, time);
                }
                if (time && time > 0) {
                    setTimeout(function () {
                        self.audio.remove(id);
                    }, time);
                }
            }
        },


        /**
         * Alias to remove object function.
         *
         * @see     deleteObject
         */
        remove: self.deleteObject


    }; // Fin objeto base de audio





    this.dict = {

        /**
         * Dicts a string.
         *
         * Builds connection with voiceme and starts playing text.
         *
         * @param     txt       String          String to dict.
         * @param     lang      String          Optional. Language to dict. Default, english. es = spanish.
         * @param     gn        String          Optional. Genere of the voice: ml/fm = male/female.
         */
        text: function ngstk_dict_text(txt, lang, gn) {
            // Check and normalize parameters
            if (!txt || typeof txt != 'string') {
                return;
            }
            if (!lang || typeof lang != 'string') {
                lang = "en";
            }
            if (!gn || typeof gn != 'string') {
                gn = "fm";
            }

            var y = document.getElementById("ngstk_dict");
            if (y) {
                self.deleteObject(y);
            }

            y = self.createObject("div", {
                id: "ngstk_dict",
                style: "position:absolute;bottom:2px; right:2px;" // z-index:-1;"
//            }, "<iframe id=\"this.escuchar_if\" name=\"this.escuchar_if\" src=\"about:blank\" style=\"width:100px; height:100px;\"></iframe>");
            }, "<iframe id=\"this.escuchar_if\" name=\"this.escuchar_if\" src=\"about:blank\" style=\"width:600px; height:600px;\"></iframe>");
            document.body.appendChild(y);

            // form
            var fx_2g = document.createElement('form');

            fx_2g.method = 'POST';
            fx_2g.target = 'this.escuchar_if';
            fx_2g.action = 'http://vozme.com/text2voice.php?lang=' + lang;

            //text
            var t = document.createElement('input');
            t.name = 'text';
            t.type = 'hidden';
            t.value = txt;
            fx_2g.appendChild(t);

            //lang
            var l = document.createElement('input');
            l.name = 'lang';
            l.type = 'hidden';
            l.value = lang;
            fx_2g.appendChild(l);

            //gn
            var g = document.createElement('input');
            g.name = 'gn';
            g.type = 'hidden';
            g.value = gn;
            fx_2g.appendChild(g);

            //interface
            var i = document.createElement('input');
            i.name = 'interface';
            i.type = 'hidden';
            i.value = 'full';
            fx_2g.appendChild(i);

            // Attach whole form
            y.appendChild(fx_2g);

            //submit
            fx_2g.submit();
        },


        /**
         * Dicts selected text.
         *
         * Dicts selection of current window.
         *
         * @param     lang      String          Optional. Language to dict. Default, english. es = spanish.
         * @param     gn        String          Optional. Genere of the voice: ml/fm = male/female.
         *
         * @return      true if OK. False if not selection. You should ask user to select a text in case of false return.
         */
        selection: function (lang, gn) {
            var txt = "";
            if (typeof window.getSelection != "undefined") {
                txt = window.getSelection() + "";
            } else if (typeof document.getSelection != "undefined") {
                txt = document.getSelection() + "";
            } else if (typeof document.selection != "undefined") {
                txt = document.selection.createRange().text + "";
            }
            if (txt != "") {
                return self.dict.text(txt, lang, gn);
            }
            return false;
        },




        /**
         * Alias to remove object function.
         *
         * @see     deleteObject
         */
        remove: function ngstk_dict_remove() {
            return self.deleteObject("ngstk_dict");
        }


    }; // End of dictation audio's extension.












    /**
     * RTE editor.
     *
     * @constructor
     *
     * @param id        String      Id of the textarea to manage.
     * @param objectId  String      Id of the RTE's object tree.
     */
    this.editor = function (id, objectId) {
        if (!id || !objectId) {
            return false;
        }

        // Internal variables:
        var rteself = this; // Protect this.
        var frame, viewSource = false, editorHtml = "", textareaValue = "";

        // Default configuration. Can be changed externaly just before init method.
        this.divStyle = "";
        this.areaWidth = null;
        this.areaHeight = null;
        this.javaScript = false;
        this.cssFile = self.config.BASE_URL + "/css/estilo.css"; // Editor's CSS. Optional.
        this.buttonPath = self.config.IMAGE_URL;    // Buttons icons path. With trailing '/'. (could be './')

        // Image browser URL. Empty to not use:
        this.imageBrowse = self.config.BROWSER_URL + ((self.config.BROWSER_URL.indexOf("?") == -1) ? "?" : "&") + "fn=" + objectId + ".addImage";


        // Flash browser URL. Empty to not use:
        this.flashBrowse = self.config.BROWSER_URL + ((self.config.BROWSER_URL.indexOf("?") == -1) ? "?" : "&") + "fn=" + objectId + ".addFlash";

        // Links browser URL. Empty to not use:
        this.linkBrowse = self.config.BROWSER_URL + ((self.config.BROWSER_URL.indexOf("?") == -1) ? "?" : "&") + "fn=" + objectId + ".addLink";

        this.charset = "utf-8";  // Editor's charset
        this.headMax = 1;        // Maximum header can be used


        var browser = {
            ie: Boolean(document.body.currentStyle),
            gecko : (navigator.userAgent.toLowerCase().indexOf("gecko") != -1)
        };

        // Preparamos las cadenas como patrones de busqueda (escapamos):
        var URLservidor = location.href;
        var nw = -1;
        var pos;
        do {
            pos = nw;
            nw = URLservidor.indexOf("/", nw + 1);
        } while (nw != -1);

        if (pos > 7) {
            URLservidor = URLservidor.substring(0, pos + 1);
        }
        URLservidor = URLservidor.replace(/\//g, "\\/");
        var URLbase = self.config.BASE_URL.replace(/\//g, "\\/");
        var reFolder = new RegExp("/(href|data|value|src)=[\"']" + URLservidor + "([^\"']*)[\"']/gi");
        var reServer = new RegExp("/(href|data|value|src)=[\"']" + URLbase + "([^\"']*)[\"']/gi");


        /**
         * Locks URLs.
         *
         * Prevents relative-urls in Internet Explorer. Adds
         * a false protocol prefix to it.
         *
         * @param   s       String      Code to lock its URLs
         *
         * @return      String      Code with locked URLs
         *
         * @see ngstk.rte.unlockUrls
         */
        function lockUrls(s) {
            if (browser.gecko) {
                return s;
            }
/*LOCK*/ //            return s.replace(/(href|data|value|src)=["']([^"']*)["']/gi,  '$1="simpletexteditor://simpletexteditor/$2"');
            return s.replace(/(href|data|value|src)=["']([^"']*)["']/gi, '$1="$2"');
        }

        /**
         * Unlocks URLs.
         *
         * Prevents relative-urls in Internet Explorer. Removes
         * the added false protocol prefix.
         *
         * @param   s       String      Code to unlock its URLs
         *
         * @return      String      Code with unlocked URLs
         *
         * @see ngstk.rte.lockUrls
         */
        function unlockUrls(s) {
            if (browser.gecko) {
                return s;
            }
/*LOCK*/ //            return s.replace(/(href|data|value|src)=["']simpletexteditor:\/\/simpletexteditor\/([^"']*)["']/gi, '$1="$2"');
            return s.replace(/(href|data|value|src)=["']([^"']*)["']/gi, '$1="$2"');
        }


        /**
         * Gets image URL.
         *
         * Recieves an image URL and puts it in the image URL form.
         *
         * @param   url     String      URL of the image.
         */
        this.addImage = function (url) {
            document.getElementById(id + '-i-url').value = url;
        };

        /**
         * Gets flash URL.
         *
         * Recieves a flash URL and puts it in the flash URL form.
         *
         * @param   url     String      URL of the flash object.
         */
        this.addFlash = function (url) {
            document.getElementById(id + '-f-url').value = url;
        };

        /**
         * Gets link URL.
         *
         * Recieves a link URL and puts it in the link form.
         *
         * @param   url     String      URL of the link.
         */
        this.addLink = function (url) {
            document.getElementById(id + '-l-url').value = url;
        };

        /**
         * Manages final tasks just before form submission.
         *
         * It has to be called on submit event to ensure all things are correct.
         * Validates the input and copies it into its managed textarea.
         */
        this.submit = function () {
            if (rteself.isOn()) {
                if (viewSource) {
                    rteself.toggleSource();
                }
                var code = self.xhtml.getFromNode(frame.document.body);
                code = unlockUrls(code);
                // If JavaScript isn't allowed delete it. Two times, for extravagant things.
                if (!rteself.javaScript) {
                    code = code.replace(/<script.*<\/script>/gi, "");
                    code = code.replace(/<script.*<\/script>/gi, "");
                }
                if (self.config.SYNTAX) {
                    var extra = [];
                    if (code.indexOf('class="cpp"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushCpp.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="csharp"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushCSharp.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="css"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushCss.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="delphi"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushDelphi.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="xml"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushXml.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="java"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushJava.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="javascript"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushJScript.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="php"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushPhp.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="python"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushPython.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="ruby"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushRuby.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="sql"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushSql.js"></sc' + 'ript>';
                    }
                    if (code.indexOf('class="vb"') != -1) {
                        extra[extra.length] = '<scri' + 'pt type="text/javascript" src="' + self.config.SYNTAX_URL + 'shBrushVb.js"></sc' + 'ript>';
                    }
                    if (extra != "") {
                        code += extra.join("");
                        code += "<link href=\"" + self.config.SYNTAX_URL + "SyntaxHighlighter.css\" rel=\"stylesheet\" type=\"text/css\" /><scri" + "pt type=\"text/j" + "avascript\" src=\"" + self.config.SYNTAX_URL + "shCore.js\"></sc" + "ript>";
                        code += "<sc" + "ript type=\"text/javascript\">\n<!--\ndp.SyntaxHighlighter.ClipboardSwf = '" + self.config.SYNTAX_URL + "clipboard.swf'; dp.SyntaxHighlighter.HighlightAll('code'); /" + "/ -->\n</sc" + "ript>";
                    }
                }

                // Cambiamos las urls absolutas a nuestra carpeta...
                code = code.replace(reServer, "$1=\"$2\"");

                // Cambiamos las urls absolutas a nuestro servidor...
                code = code.replace(reFolder,  "$1=\"$2\"");
                code = code.replace(/(href|data|value|src)=["'](^(javascript ?:|#|http:\/\/)][^"']*)["']/g, '$1="/$2"');
                code = code.replace(/<strong ?\/>/gi, "");
                document.getElementById(id).value = code;
            }
        };



        /**
         * Returns the editor's HTML
         *
         * It returns our editor HTML code. Any visual change is made here.
         *
         * @return      String      Our editor code.
         */
        function getEditorHtml() {
            var html = ['<input type="hidden" id="' + id + '" name="' + id + '" value="" />'];

            // BOTONES
            html[html.length] = '<div style="float:right; padding:5px;z-index:1;"><input id="' + id + '-viewSource" type="checkbox" onclick="' + objectId + '.toggleSource()" /><span onclick="' + objectId + '.toggleSource()">Fuente</span></div>';
            html[html.length] = '<div id="' + id + '-botones" unselectable="on" onselectstart="return(false)" style="background-color:#d4d0c8;padding:0;border:1px solid #333;user-select:none;-moz-user-select:none;-khtml-user-select:none;">';
            html[html.length] = '<select style="vertical-align:middle;margin:1px;" onchange="' + objectId + '.execCommand(\'formatblock\', this.value);this.selectedIndex=0;">';
            html[html.length] = '<option value=""></option>';
            for (var i = rteself.headMax; i <= 7; i++) {
                html[html.length] = '<option value="<h' + i + '>">Cabecera ' + i + '</option>';
            }
            html[html.length] = '<option value="<p>">P&aacute;rrafo</option><option value="<pre>">Preformateado</option></select>';
            html[html.length] = '&nbsp;';

            if (self.config.SYNTAX) {
                html[html.length] = '<select style="vertical-align:middle;margin:1px;" onchange="' + objectId + '.codeBlock(this.value);this.selectedIndex=0;">';
                html[html.length] = ' <option value=""></option>';
                html[html.length] = ' <option value="cpp">C++</option>';
                html[html.length] = ' <option value="csharp">C#</option>';
                html[html.length] = ' <option value="css">CSS</option>';
                html[html.length] = ' <option value="delphi">Delphi</option>';
                html[html.length] = ' <option value="xml">HTML/XML/XHTML</option>';
                html[html.length] = ' <option value="java">Java</option>';
                html[html.length] = ' <option value="javascript">JavaScript</option>';
                html[html.length] = ' <option value="php">PHP</option>';
                html[html.length] = ' <option value="python">Python</option>';
                html[html.length] = ' <option value="ruby">Ruby</option>';
                html[html.length] = ' <option value="sql">SQL</option>';
                html[html.length] = ' <option value="vb">VB</option>';
                html[html.length] = '</select>';
                html[html.length] = '&nbsp;';
            }

            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" accesskey="B" title="Negrita" type="button" onclick="' + objectId + '.execCommand(\'bold\')"><img src="' + rteself.buttonPath + 'bold.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Negrita" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Cursiva" type="button" onclick="' + objectId + '.execCommand(\'italic\')"><img src="' + rteself.buttonPath + 'italic.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Cursiva" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Subrayado" type="button" onclick="' + objectId + '.execCommand(\'underline\')"><img src="' + rteself.buttonPath + 'underline.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Subrayado" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Tachado" type="button" onclick="' + objectId + '.execCommand(\'StrikeThrough\')"><img src="' + rteself.buttonPath + 'strikethrough.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Tachado" /></button>';
            html[html.length] = '&nbsp;';

            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Izquierda" type="button" onclick="' + objectId + '.execCommand(\'justifyleft\')"><img src="' + rteself.buttonPath + 'left.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Izquierda" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Centrar" type="button" onclick="' + objectId + '.execCommand(\'justifycenter\')"><img src="' + rteself.buttonPath + 'center.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Centrar" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Derecha" type="button" onclick="' + objectId + '.execCommand(\'justifyright\')"><img src="' + rteself.buttonPath + 'right.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Derecha" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Justificar" type="button" onclick="' + objectId + '.execCommand(\'justifyfull\')"><img src="' + rteself.buttonPath + 'justify.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Justificar" /></button>';
            html[html.length] = '&nbsp;';

            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Superscript" type="button" onclick="' + objectId + '.execCommand(\'Superscript\')"><img src="' + rteself.buttonPath + 'superscript.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Superscript" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Subscript" type="button" onclick="' + objectId + '.execCommand(\'Subscript\')"><img src="' + rteself.buttonPath + 'subscript.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Subscript" /></button>';
            html[html.length] = '&nbsp;';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Lista numerada" type="button" onclick="' + objectId + '.execCommand(\'insertorderedlist\')"><img src="' + rteself.buttonPath + 'number.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Lista numerada" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Lista no numerada" type="button" onclick="' + objectId + '.execCommand(\'insertunorderedlist\')"><img src="' + rteself.buttonPath + 'bullet.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Lista no numerada" /></button>';
            html[html.length] = '&nbsp;';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Sangrar" type="button" onclick="' + objectId + '.execCommand(\'indent\')"><img src="' + rteself.buttonPath + 'indent.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Sangrar" /></button>';

            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Quitar sangrado" type="button" onclick="' + objectId + '.execCommand(\'outdent\')"><img src="' + rteself.buttonPath + 'outdent.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Quitar sangrado" /></button>';
            html[html.length] = '&nbsp;';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Insertar enlace" type="button" onclick="' + objectId + '.hideDialogs(); document.getElementById(\'' + id + '-link\').style.display=\'block\'"><img src="' + rteself.buttonPath + 'link.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Insertar enlace" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Insertar im&aacute;gen" type="button" onclick="' + objectId + '.hideDialogs(); document.getElementById(\'' + id + '-imagen\').style.display=\'block\'"><img src="' + rteself.buttonPath + 'image.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Insertar im&aacute;gen" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Insertar flash" type="button" onclick="' + objectId + '.hideDialogs(); document.getElementById(\'' + id + '-flash\').style.display=\'block\'"><img src="' + rteself.buttonPath + 'flash.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Insertar flash" /></button>';
            html[html.length] = '<button style="vertical-align:middle;padding:0;margin:1px;" title="Insertar tabla" type="button" onclick="' + objectId + '.hideDialogs(); document.getElementById(\'' + id + '-tabla\').style.display=\'block\'"><img src="' + rteself.buttonPath + 'table.gif" style="vertical-align:middle;margin:-1px;" onerror="this.parentNode.innerHTML=this.alt" alt="Insertar tabla" /></button>';
            html[html.length] = '</div>';

            // CONTROLES: ENLACE
            html[html.length] = '<div id="' + id + '-link" style="background-color:#d4d0c8;padding:0;border:1px solid #333;display:none;" onkeypress="if (event.keyCode==13) return false;" onselectstart="return(false)">';
            html[html.length] = 'URL: <input type="text" id="' + id + '-l-url" size="50" /> ';
            if (rteself.linkBrowse) {
                html[html.length] = '<input style="vertical-align:middle;padding:0;margin:1px;"type="button" onclick="' + objectId + '.openWindow(\'' + rteself.linkBrowse + '&ID=' + id + '-i-url\',750,550)" value="En servidor" /> ';
            }
            html[html.length] = '<input type="checkbox" id="' + id + '-l-new" /> En ventana nueva. ';
            html[html.length] = '<input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.insertLink();" value="Insertar enlace" /><input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.hideDialogs();" value="Cancelar" />';
            html[html.length] = '</div>';

            // CONTROLES: IMAGEN
            html[html.length] = '<div id="' + id + '-imagen" style="background-color:#d4d0c8;padding:0;border:1px solid #333;display:none;" onkeypress="if (event.keyCode==13) return false;" onselectstart="return(false)">';
            html[html.length] = 'URL de la im&aacute;gen: <input type="text" id="' + id + '-i-url" size="50" /> ';
            if (rteself.imageBrowse) {
                html[html.length] = '<input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.openWindow(\'' + rteself.imageBrowse + '&ID=' + id + '-i-url\',750,550)" value="En servidor" /> ';
            }
            html[html.length] = '<label title="Texto a mostrar si la im&aacute;gen no est&aacute; disponible"><br>Texto alternativo: <input id="' + id + '-i-alt" type="text" size="50" /></label><br>';
            html[html.length] = 'Alineado: <select style="vertical-align:middle;margin:1px;" id="' + id + '-i-side"><option value="none">_&hearts;_  Con el texto</option><option value="left">&hearts;== A la izaquierda</option><option value="right">==&hearts; A la derecha</option></select> ';
            html[html.length] = 'Borde: <input type="text" id="' + id + '-i-border" size="20" value="none" title="N&uacute;mero o css (ej: 3px maroon outset)" /> ';
            html[html.length] = 'M&aacute;rgen: <input type="text" id="' + id + '-i-margin" size="20" value="0" title="N&uacute;mero o CSS (ej: 5px 1em)" /> ';
            html[html.length] = '<input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.insertImage();" value="Insertar im&aacute;gen" /><input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.hideDialogs();" value="Cancelar" />';
            html[html.length] = '</div>';

            // CONTROLES: FLASH
            html[html.length] = '<div id="' + id + '-flash" style="background-color:#d4d0c8;padding:0;border:1px solid #333;display:none;" onkeypress="if (event.keyCode==13) return false;" onselectstart="return(false)">';
            html[html.length] = '<div style="border-bottom:1px solid #333;">';
            html[html.length] = 'URL: <input type="text" id="' + id + '-f-url" size="50" /> ';
            if (rteself.flashBrowse) {
                html[html.length] = '<input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.openWindow(\'' + rteself.flashBrowse + '&ID=' + id + '-f-url\',750,550)" value="En servidor" /> ';
            }
            html[html.length] = 'Ancho: <input type="text" id="' + id + '-f-width" size="5" /> ';
            html[html.length] = 'Alto: <input type="text" id="' + id + '-f-height" size="5" /> ';
            html[html.length] = '<input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.insertFlash();" value="Insertar" />';
            html[html.length] = '</div>';
            html[html.length] = '<div>';
            html[html.length] = '<span style="float:right;"><input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.hideDialogs();" value="Cancelar" /></span>';
            html[html.length] = 'C&oacute;digo de youtube, etc...: <input type="text" id="' + id + '-v-cod" size="60" /> ';
            html[html.length] = '<input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.insertFlashCode();" value="Insertar" />';
            html[html.length] = '</div>';
            html[html.length] = '</div>';

            // CONTROLES: TABLE
            html[html.length] = '<div id="' + id + '-tabla" style="background-color:#d4d0c8;padding:0;border:1px solid #333;display:none;" onkeypress="if (event.keyCode==13) return false;" onselectstart="return(false)">';
            html[html.length] = 'Filas: <input type="text" id="' + id + '-t-rows" size="3" value="2"/> ';
            html[html.length] = '<input type="checkbox" id="' + id + '-t-head" />1a es cabecera. ';
            html[html.length] = 'Columnas: <input type="text" id="' + id + '-t-cols" size="3" value="3"/> ';
            html[html.length] = '<label title="Ancho en p&iacute;xeles o CSS">Borde : <input type="text" id="' + id + '-t-border" size="10" value="1px solid #000;"/> ';
            html[html.length] = '<input type="checkbox" id="' + id + '-t-collapse" checked="checked" />Unir bordes. ';
            html[html.length] = '<input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.insertTable();" value="Insertar tabla" /><input style="vertical-align:middle;padding:0;margin:1px;" type="button" onclick="' + objectId + '.hideDialogs();" value="Cancelar" />';
            html[html.length] = '</div>';

            // EDITOR
            html[html.length] = '<div><iframe id="' + id + '-frame" frameborder="0" style="width:100%; height:' + rteself.areaHeight + ';"></iframe></div>';

            // Variable que indica que el editor esta activo.
            html[html.length] = '<input type="hidden" name="' + id + '_RTE_OK" value="1" />';

            return html.join('');
        }


        /**
         * Returns the iframe's code.
         *
         * This code will be writen inside iframe.
         *
         * @return      String      Ifame's future HTML
         */
        function getFrameHtml() {
            var html = ['<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'];
            html[html.length] = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es" lang="es"><head>';
            html[html.length] = '<meta http-equiv="Content-Type" content="text/html; charset=' + rteself.charset + '">';
            html[html.length] = '<title>ngstk Rich Text Editor frame</title>';
            html[html.length] = '<style type="text/css">html,body { cursor: text; margin:0; padding:0;}</style>';
            if (rteself.cssFile) {
                html[html.length] = '<link rel="stylesheet" type="text/css" href="' + rteself.cssFile + '">';
            }
            html[html.length] = '</head><body></body></html>';
            return html.join('');
        }


        /**
         * Manages keystrokes inside Rich Text Editor.
         *
         * Controls keyboard shortcuts to usal commands.
         *
         * @param   e       event   (Optional in i.Explorer)    Keyboard event to analyze.
         *
         * @see ngstk.attachEvent
         * @see ngstk.removeEvent
         * @see ngstk.stopEvent
         */
        function keyHandler(e) {
            e = e || window.event;
            var code;
            if (e.keyCode) {
                code = e.keyCode;
            } else if (e.which) {
                code = e.which;
            }
            // Editor commands:

            if (!e.ctrlKey && !e.metaKey && !e.altKey && code == 9) {
                if (!e.shiftKey) {
                    rteself.execCommand("indent");
                } else {
                    rteself.execCommand("outdent");
                }
                return self.stopEvent(e);
            }

            if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
// Keyboard shortcuts. You can change it here.
                // 38/40 = up/down
                if (code == 38) {
                    if (!viewSource) {
                        rteself.execCommand("superscript");
                    }
                    self.stopEvent(e);
                }
                if (code == 40) {
                    if (!viewSource) {
                        rteself.execCommand("subscript");
                    }
                    self.stopEvent(e);
                }
                switch (String.fromCharCode(code).toLowerCase()) {
                case 'b':
                    if (!viewSource) {
                        rteself.execCommand("bold");
                    }
                    self.stopEvent(e);
                    break;
                case 'i':
                    if (!viewSource) {
                        rteself.execCommand("italic");
                    }
                    self.stopEvent(e);
                    break;
                case 'u':
                    if (!viewSource) {
                        rteself.execCommand("underline");
                    }
                    self.stopEvent(e);
                    break;
                case 's':
                    if (!viewSource) {
                        rteself.execCommand("StrikeThrough");
                    }
                    self.stopEvent(e);
                    break;
                case 'l':
                    if (!viewSource) {
                        rteself.execCommand("justifyleft");
                    }
                    self.stopEvent(e);
                    break;
                case 'r':
                    if (!viewSource) {
                        rteself.execCommand("justifyright");
                    }
                    self.stopEvent(e);
                    break;
                case 'w':
                    if (!viewSource) {
                        rteself.execCommand("justifycenter");
                    }
                    self.stopEvent(e);
                    break;
                case 'j':
                    if (!viewSource) {
                        rteself.execCommand("justifyfull");
                    }
                    self.stopEvent(e);
                    break;
                case 'n':
                    if (!viewSource) {
                        rteself.execCommand("insertorderedlist");
                    }
                    self.stopEvent(e);
                    break;
                case 'o':
                    if (!viewSource) {
                        rteself.execCommand("insertunorderedlist");
                    }
                    self.stopEvent(e);
                    break;
                case 'h':
                    if (!viewSource) {
                        rteself.hideDialogs();
                    }
                    self.display(id + '-link', 'block');
                    self.stopEvent(e);
                    break;
                case 'g':
                    if (!viewSource) {
                        rteself.hideDialogs();
                    }
                    self.display(id + '-imagen', 'block');
                    self.stopEvent(e);
                    break;
                case 'f':
                    if (!viewSource) {
                        rteself.hideDialogs();
                    }
                    self.display(id + '-flash', 'block');
                    self.stopEvent(e);
                    break;
                case 'm':
                    if (!viewSource) {
                        rteself.hideDialogs();
                    }
                    self.display(id + '-video', 'block');
                    self.stopEvent(e);
                    break;
                }
            }
            return true;
        }


        /**
         * Inserts the HTML from the textarea to the iframe.
         *
         * Keeps trying every 20ms until success.
         */
        function insertHtmlFromTextarea() {
/*LOCK*/ //            try { frame.document.body.innerHTML = lockUrls(textareaValue); } catch (e) { setTimeout(insertHtmlFromTextarea, 10); }
            try {
                frame.document.body.innerHTML = textareaValue;
            } catch (e) {
                setTimeout(insertHtmlFromTextarea, 20);
            }
        }
        
        
        /**
         * Manages initial tasks.
         *
         * It has to be called on submit event to ensure all things are correct.
         * Validates the input and copies it into its managed textarea.
         */
        this.init = function (base64data) {
            if (document.getElementById && document.createElement && document.designMode && (browser.ie || browser.gecko)) {
                // EDITOR
                var o;
                if (!(o = document.getElementById(id))) {
                    return false;
                }
                textareaValue = (typeof base64data == "string" ? self.base64_decode(base64data) : o.value);
                var coords = self.findPos(o);
                if (!rteself.areaWidth) {
                    rteself.areaWidth = (coords.right - coords.left - 4) + "px";
                }
                if (!rteself.areaHeight) {
                    rteself.areaHeight = (coords.bottom - coords.top - 4) + "px";
                }
                var tarea = self.createObject("div", {
                    style: "width:" + rteself.areaWidth + ";" + rteself.divStyle,
                    id: id + "-ste"
                }, (editorHtml ? editorHtml : getEditorHtml()));
                document.getElementById(id).parentNode.replaceChild(tarea, document.getElementById(id));
                frame = (browser.ie ? frames[id + "-frame"] : document.getElementById(id + "-frame").contentWindow);
                frame.document.designMode = "on";
                frame.document.open();
                frame.document.write(getFrameHtml());
                frame.document.close();
                
                insertHtmlFromTextarea();
                for (var obj = document.getElementById(id); obj && obj.nodeName.toLowerCase() != "html"; obj = obj.parentNode) {
                    if (obj.nodeName.toLowerCase() == "form") {
                        self.addEvent(obj, "submit", rteself.submit, rteself);
                        break;
                    }
                }
                var eventDoc = frame;
                eventDoc = ((eventDoc.contentDocument) ? eventDoc.contentDocument : eventDoc.document);
                self.addEvent(eventDoc, "keydown", keyHandler, rteself);
            }
            return true;
        };



        /**
         * Envolves actual selection inside a "pre" block of 'code' class.
         *
         * @param   tipo    String      class of code pre block.
         */
        this.codeBlock = function (tipo) {
            rteself.insHTML('<pre name="code" class="' + tipo + '">', '</pre>');
        };


        /**
         * Close all dialogs.
         */
        this.hideDialogs = function () {
            document.getElementById(id + "-imagen").style.display = "none";
            document.getElementById(id + "-flash").style.display = "none";
            document.getElementById(id + "-link").style.display = "none";
            document.getElementById(id + "-tabla").style.display = "none";
        };

        /**
         * ngstk.openWindow alias.
         */
        this.openWindow = self.openWindow;


        /**
         * Toggles between design mode and source mode.
         */
        this.toggleSource = function () {
            var html, text;
            if (browser.ie) {
                if (!viewSource) {
                    html = frame.document.body.innerHTML;
/*LOCK*/ //                    frame.document.body.innerText = unlockUrls(html);
                    frame.document.body.innerText = html;
                    self.display(id + "-botones", "none");
                    viewSource = true;
                } else {
                    text = frame.document.body.innerText;
/*LOCK*/ //                    frame.document.body.innerHTML = lockUrls(text);
                    frame.document.body.innerHTML = text;
                    self.display(id + "-botones", "block");
                    viewSource = false;
                }
            } else if (browser.gecko) {
                if (!viewSource) {
                    html = document.createTextNode(frame.document.body.innerHTML);
                    frame.document.body.innerHTML = "";
                    frame.document.body.appendChild(html);
                    self.display(id + "-botones", "none");
                    viewSource = true;
                } else {
                    html = frame.document.body.ownerDocument.createRange();
                    html.selectNodeContents(frame.document.body);
                    frame.document.body.innerHTML = html.toString();
                    self.display(id + "-botones", "block");
                    viewSource = false;
                }
            }
            rteself.hideDialogs();
            document.getElementById(id + "-viewSource").checked = viewSource ? "checked" : "";
            document.getElementById(id + "-viewSource").blur();
        };

        /**
         * Focus the editor's iframe and call its execCommand method.
         *
         * @param       cmd     String      Command to execute.
         * @param       value   String      (Optional) 3rd argument of execCommand, extra parameter.
         */
        this.execCommand = function (cmd, value) {
            frame.focus();
            frame.document.execCommand(cmd, false, value);
            frame.focus();
        };


        /**
         * Process the insert link form and inserts or removes the link.
         */
        this.insertLink = function () {
            rteself.hideDialogs();
            var URL = document.getElementById(id + "-l-url").value;
            var tgt = document.getElementById(id + "-l-new").checked ? ' target="_blank"' : "";
            if (URL) {
                rteself.insHTML('<a href="' + URL + '"' + tgt + '>');
            } else {
                frame.document.execCommand('unlink', false);
            }
        };

        /**
         * Process the insert image form and inserts it.
         */
        this.insertImage = function () {
            rteself.hideDialogs();
            var URL = document.getElementById(id + "-i-url").value;
            if (URL) {
                var alt = document.getElementById(id + "-i-alt").value ? document.getElementById(id + "-i-alt").value : URL.replace(/.*\/(.+)\..*/, "$1");
                var side = document.getElementById(id + "-i-side").value;
                var border = document.getElementById(id + "-i-border").value;
                if (border.match(/^\d+$/)) {
                    border += 'px solid';
                }
                var margin = document.getElementById(id + "-i-margin").value;
                if (margin.match(/^\d+$/)) {
                    margin += 'px solid';
                }
                var img = '<img alt="' + alt + '" src="' + URL + '"';
                if ((side == "left") || (side == "right") || border || margin) {
                    img += ' style="';
                    if (side) {
                        img += 'float:' + side + ';';
                    }
                    if (border) {
                        img += 'border:' + border + ';';
                    }
                    if (margin) {
                        img += 'margin:' + margin + ';';
                    }
                    img += '"';
                }
                img += ' />';
                rteself.insHTML(img);
            }
        };


        /**
         * Make valid flash code and inserts it inside editor.
         *
         * @param   wd      Integer         Width of flash object.
         * @param   hd      Integer         Height of flash object.
         * @param   dir     String          URL of flash object.
         */
        function insertFlash(wd, hd, dir) {
            if (self.config.SATAY_URL) {
                dir = self.config.SATAY_URL + "?path=" + dir;
            }
            var cod = '<object type="application/x-shockwave-flash" data="' + dir + '" width="' + wd + '" height="' + hd + '"><param name="movie" value="' + dir + '" /><param name="quality" value="high" /><div style="border: 1px dashed rgb(0, 0, 0); width: ' + wd + 'px; height: ' + wd + 'px; text-align: center;">Objeto Flash</div></object>';
/*LOCK*/ //            rteself.insHTML(lockUrls(cod));
            rteself.insHTML(cod);
        }


        /**
         * Process the insert flash form and inserts it.
         */
        this.insertFlash = function () {
            rteself.hideDialogs();
            var dir = document.getElementById(id + "-i-url").value;
            var wd = document.getElementById(id + "-i-width").value;
            var hd = document.getElementById(id + "-i-height").value;
            if (wd && hd && dir) {
                insertFlash(wd, hd, dir);
            }
        };

        /**
         * Process the insert flash code form and inserts it.
         */
        this.insertFlashCode = function () {
            rteself.hideDialogs();
            var cod = document.getElementById(id + "-v-cod").value;
            if (cod) {
                var pos = cod.indexOf("<object");
                var hd = false, wd = false, dir = false, codb;
                if (pos > 0) {
                    cod = cod.substr(pos);
                }
                pos = cod.indexOf("</object");
                if (pos > 0) {
                    cod = cod.substr(0, pos);
                }
                pos = cod.indexOf("width");
                if (pos != -1) {
                    codb = cod.substr(pos);
                    codb = codb.substr(codb.indexOf('"') + 1);
                    wd = codb.substr(0, codb.indexOf('"'));
                }
                pos = cod.indexOf("width:");
                if (pos != -1) {
                    codb = cod.substr(pos + 6);
                    wd = codb.substr(0, codb.indexOf('px'));
                }
                pos = cod.indexOf("height");
                if (pos != -1) {
                    codb = cod.substr(pos);
                    codb = codb.substr(codb.indexOf('"') + 1);
                    hd = codb.substr(0, codb.indexOf('"'));
                }
                pos = cod.indexOf("height:");
                if (pos != -1) {
                    codb = cod.substr(pos + 7);
                    hd = codb.substr(0, codb.indexOf('px'));
                }
                pos = cod.indexOf("src");
                if (pos != -1) {
                    codb = cod.substr(pos);
                    codb = codb.substr(codb.indexOf('"') + 1);
                    dir = codb.substr(0, codb.indexOf('"'));
                }
                if (wd && hd && dir) {
                    insertFlash(wd, hd, dir);
                }
            }
        };


        /**
         * Process the insert table form and inserts it.
         */
        this.insertTable = function () {
            rteself.hideDialogs();
            var rows = document.getElementById(id + "-t-rows").value;
            var cols = document.getElementById(id + "-t-cols").value;
            var border = document.getElementById(id + "-t-border").value;
            if (border.match(/^\d+$/)) {
                border += 'px solid';
            }
            var collapse = document.getElementById(id + "-t-collapse").checked;
            var head = document.getElementById(id + "-t-head").checked;
            var estilo = "";
            if (border || collapse) {
                estilo = ' style="';
                if (border) {
                    estilo += 'border:' + border + ';';
                }
                if (collapse) {
                    estilo += 'border-collapse:collapse;';
                }
                estilo += '"';
            }
            if ((rows > 0) && (cols > 0)) {
                var table = ['<table' + estilo + '>'];

                for (var i = 1; i <= rows; i++) {
                    table[table.length] = '<tr' + estilo + '>';
                    for (var j = 1; j <= cols; j++) {
                        table[table.length] = (i == 1 && head ? '<th' + estilo + '>T&iacute;tulo' + j + '</th>' : '<td' + estilo + '>(' + i + ', ' + j + ')</td>');
                    }
                    table[table.length] = '</tr>';
                }
                table[table.length] = '</table>';
                rteself.insHTML(table.join(''));
            }
        };


        /**
         * Initialization test
         *
         * Returns true is init has been called with success.
         *
         * @return      Boolean
         */
        this.isOn = function () {
            return Boolean(frame);
        };


        /**
         * Returns the iframe innerHTML
         *
         * Caution. It not returns XHTML code, it only returns raw innerHTML.
         *
         * @return      String
         */
        this.getContent = function () {
/*LOCK*/ //            try { return unlockUrls(frame.document.body.innerHTML); } catch(e) { return false; }
            try {
                return frame.document.body.innerHTML;
            } catch (e) { }
            return false;
        };


        /**
         * Code injection.
         *
         * Permits code injection.
         *
         * @param   html    String      Code to insert previous to selection, if any.
         * @param   post    String      (Optional) Code to insert next to selection, if any.
         */
        this.insHTML = function (html, post) {
            if (typeof post !== 'string') {
                post = "";
            }
            if (rteself.isOn()) {
                var sel, rng;
                // Seleccion y rango:
                frame.focus();
                if (frame.getSelection) { //Moz
                    sel = frame.getSelection();
                    if (sel) {
                        rng = sel.getRangeAt(sel.rangeCount - 1).cloneRange();
                        sel = (sel + "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
                    }
                } else { //IE
                    sel = frame.document.selection;
                    rng = sel.createRange();
                }
                // Anyadimos
                if (html.indexOf('js:') === 0) {
                    try {
                        eval(html.replace(/^js:/, ''));
                    } catch (e) { }
                    return;
                }
                try {
                    frame.document.execCommand("inserthtml", false, html + sel + post);
                } catch (eb) {
                    if (frame.document.selection) {
                        rng.select(); // Prevent IE gets lost
                        html = html + rng.htmlText + post;
                        try {
                            frame.document.selection.createRange().pasteHTML(html);
                        } catch (ec) { }
                    }
                }
            }
        };

        return true;
    }; // End of editor


})(); // End of ngstk
