Source: nodo.js

/**
 * @file nodo.js Librería para renderizar nodos del MM.
 * @author José Luis Molina Soria
 * @version 20130723
 */

/**
 * @class MM.NodoSimple
 * @classdesc Render de NodoSimple. Se trata de un nodo con un texto y una 
 *            simple línea inferior. El render crea un grupo donde incluye 
 *            los distintos elementos gráficos. 
 * @constructor MM.NodoSimple
 * @param {MM.Render} render    Escenario donde pintar el mensaje
 * @param {MM.Arbol}  arbol         Capa donde pintar el grid
 * @param {Object}    propiedades   Conjunto de propiedades a establecer al 
 *                                  nodo. Como Color fuente, posición, etc.
 *                                  Ver refencias Kinetic.Text, Kinetic.Blob,
 *                                  Kinetic.Line, Kinectic.Group.
 */
MM.NodoSimple = MM.Mensaje.extend(/** @lends MM.NodoSimple.prototype */{

    /**
     * @desc Valores por defecto para Nodo
     * @memberof MM.NodoSimple
     */
    defecto: {
        x: 0,
        y: 0,
        text: '',
        fontSize: 13,
        fontFamily: 'helvetica',
        fill: '#555',
        width: 'auto',
        padding: 5
    },

    init: function (render, arbol, propiedades) {
        this.render = render;
        this.escenario = render.escenario;
        this.capa = render.capaNodos;
        this.arbol = arbol;
        this.hslColor = MM.color.randomHslColor();
        this.colorFondo = MM.color.hslToCSS(this.hslColor, 40);
        this.color = MM.color.hslToCSS(this.hslColor);
        this.aristas = [];

        var prop = MM.Properties.add(this.defecto, propiedades);
        prop.x = 0;
        prop.y = 0;
        prop.fill = this.color;
        this.kText = new Kinetic.Text(prop);

        this.group = new Kinetic.Group({
            x : propiedades.x,
            y : propiedades.y,
            width: this.kText.getWidth(),
            height: this.kText.getHeight(),
            draggable: true, 
            dragBoundFunc: function (pos) {
		console.debug('drag Nodo');
		MM.ponerFoco(arbol);
		MM.render.renderAristas();
                return pos;
            }
        });

        var w = this.kText.getWidth();
        var h = this.kText.getHeight();

        this.rect = new Kinetic.Blob({
            points: [ { x: 0, y: 0 }, 
                      { x: w+17, y: 0 }, 
                      { x: w+17, y: h }, 
                      { x: 0, y: h } ],
            stroke: this.color,
            strokeWidth: 2,
            fill: this.colorFondo,
            shadowColor: this.color,
            shadowBlur: 4,
            shadowOffset: {x:4, y:4},
            shadowOpacity: 0.7,
            tension: 0.3
        });

        var t = 10;
        var x = w + 5;
        var y = (h - t) / 2;
        var puntos = this.arbol.elemento.plegado?
                [x, y, x + t, y, x + (t*0.5), y + t]:
                [x, y, x, y + t, x + t, y + (t *0.5)];
        this.triangle = new Kinetic.Polygon({
            points: puntos,
            fill: this.color,
            visible: !this.arbol.esHoja()
        });

        this.triangle.on('mouseover', MM.Class.bind ( this, function() {
            MM.render.contenedor.style.cursor = 'pointer';
            if ( this.arbol.elemento.plegado ) {
                MM.render.contenedor.setAttribute('title', 'desplegar');
            } else {
                MM.render.contenedor.setAttribute('title', 'plegar');
            }
        }));

        this.triangle.on('mouseout', MM.Class.bind ( this, function() {
            MM.render.contenedor.style.cursor = 'default';
            MM.render.contenedor.setAttribute('title', '');
        }));

	var clickTriangulo = MM.Class.bind ( this, function(evt) {
            MM.render.contenedor.style.cursor = 'default';
            MM.render.contenedor.setAttribute('title', '');
            MM.ponerFoco ( this.arbol );
            MM.plegarRama(!this.arbol.elemento.plegado);
	    evt.cancelBubble = true;
        });
        this.triangle.on('click', clickTriangulo );
	this.triangle.on('tap', clickTriangulo );
        this.line = new Kinetic.Line({
            points: [{x:0, y: this.kText.getHeight()},
                     {x:this.kText.getWidth(), y:this.kText.getHeight()}],
            stroke: this.color,
            strokeWidth: 3,
            lineCap: 'round',
            lineJoin: 'round'
        });
        this.group.add(this.rect);
        this.group.add(this.triangle);
        this.group.add(this.line);
        this.group.add(this.kText);
        this.capa.add(this.group);
        this.rect.hide();

        var bindEditar = MM.Class.bind(MM.render, MM.render.editar);
        var bindNOP = MM.Class.bind(this, this.nop);
        var bindPonerFoco = MM.Class.bind(this, function(evt) {
	    MM.ponerFoco(this.arbol);
	});
        this.group.on('click', bindPonerFoco);
	this.group.on('tap', bindPonerFoco);
        this.group.on('dblclick', bindEditar);
	this.group.on('dbltap', bindEditar);
        h = w = t = x = y = null;
    },


    /**
     * @desc Establece el foco en el nodo resaltándolo.
     */
    ponerFoco : function () {
        this.kText.setFontStyle('bold');
        this.kText.setText('<' + this.arbol.elemento.texto + '>' );
        this.group.draw();
    },

    /**
     * @desc Quita el foco del nodo.
     */
    quitarFoco : function () {
        this.kText.setFontStyle('normal');
        this.kText.setLineHeight(1);
        this.kText.setText(this.arbol.elemento.texto);
        this.group.draw();
    },

    /**
     * @desc Pone el nodo visible o lo oculta en función del valor pasado
     * @param {Boolean} valor Visible Si / No.
     */
    setVisible : function (valor) {
        var w = this.kText.getWidth();
        var h = this.kText.getHeight();
        var t = 10;
        var x = w + 5;
        var y = (h - t) / 2;
        this.triangle.setPoints ( this.arbol.elemento.plegado?
                [x, y, x + t, y, x + (t*0.5), y + t]:
                [x, y, x, y + t, x + t, y + (t *0.5)]);

        this.triangle.setVisible(!this.arbol.esHoja());
        this.group.setVisible(valor);
    },


    /**
     * @desc Pone el nodo en la posición x
     * @param {number} x Posición x donde poner el nodo.
     */
    setX : function (x) {
        this.group.setX(x);
    },

    /**
     * @desc Posición x del nodo.
     * @return {number} Posición x del nodo.
     */
    getX : function () {
        return this.group.getX();
    },

    /**
     * @desc Pone el nodo en la posición y
     * @param {number} y Posición y donde poner el nodo.
     */
    setY : function (y) {
        this.group.setY(y);
    },


    /**
     * @desc Posición y del nodo.
     * @return {number} Posición y del nodo.
     */
    getY : function () {
        return this.group.getY();
    },

    getGroup: function () {
        return this.group;
    },

    /**
     * @desc Ancho del nodo
     * @return {number} Ancho que ocupa el nodo.
     */
    getWidth: function () {
        return this.group.getWidth();
    },

    /**
     * @desc Alto del nodo
     * @return {number} Alto que ocupa el nodo.
     */
    getHeight: function () {
        return this.group.getHeight();
    },

    /**
     * @desc Pone el nodo el modo edición.
     */
    editar: function () {
        var texto = this.getText();
        var fc = this.calcularFilasColumnas(texto);
        var top = (this.getY() * MM.render.getEscala() + MM.render.offset.y - 5); 
        var left = (this.getX() * MM.render.getEscala() + MM.render.offset.x - 5);

        this.editor = new MM.DOM.create('textarea',
            { 'id': 'editNodo',
              'innerHTML': texto,
              'rows' :  fc.filas,
              'cols' : fc.columnas + 1,
              'style': 'position: absolute; ' +
                    'top : ' + top + 'px; ' +
                    'left: ' + left  + 'px; ' +
                    'height: auto;' +
                    'border: 3px solid ' + this.color + '; ' +
                    'border-radius: 5px;' +
                    'background-color: ' + this.colorFondo + '; ' +
                    'color: ' + this.color + '; ' +
                    'font-family: ' + this.kText.getFontFamily() + '; ' +
                    'font-size: ' + this.kText.getFontSize() + 'pt; ' +
                    'white-space: pre-wrap; word-wrap: break-word; overflow:hidden; resize:true;'
            });
        this.handlerBlur = MM.Class.bind (MM.render, MM.render.editar);
        this.handlerKeyUp = MM.Class.bind (this, this.setTamanoEditor);
        this.editor.addEventListener('blur', this.handlerBlur );
        this.editor.addEventListener('keyup', this.handlerKeyUp );
        this.escenario.content.appendChild(this.editor);
        this.editor.select();
        this.editor.focus();
        texto = fc = top = left = null;
    },

    /**
     * @desc Cierra el modo de edición
     */
    cerrarEdicion : function () {
        this.editor.removeEventListener('blur', this.handlerBlur);
        this.editor.removeEventListener('keyup', this.handlerKeyUp);
        if ( this.arbol.elemento.texto !== this.editor.value ) {
            MM.undoManager.add ( new MM.comandos.Editar ( this.arbol.elemento.id, 
                                                          this.arbol.elemento.texto, 
                                                          this.editor.value ) );
        }
        this.arbol.elemento.texto = this.editor.value;
        this.setText(this.editor.value);
        var w = this.kText.getWidth();
        var h = this.kText.getHeight();
        this.group.setWidth(w);
        this.group.setHeight(h);

        this.rect.setPoints ( [ { x: 0, y: 0 }, 
                                { x: w+17, y: 0 }, 
                                { x: w+17, y: h }, 
                                { x: 0, y: h } ] );

        var t = 10;
        var x = w + 5;
        var y = (h - t) / 2;
        this.triangle.setPoints ( this.arbol.elemento.plegado?
                                  [x, y, x + t, y, x + (t*0.5), y + t]:
                                  [x, y, x, y + t, x + t, y + (t *0.5)]);
        this.editor.remove();
        delete this.editor;
        MM.ponerFoco(this.arbol);
        MM.render.dibujar(MM.arbol);
        window.focus();
        t = x = y = w = h = null;
    },

    calcularFilasColumnas : function ( texto ) {
        var lineas = texto.split("\n");
        var c = 0, f = lineas.length;
        lineas.forEach( function(linea) {
            if ( linea.length > c ) { c = linea.length; }
        });
        lineas = null;
        return {filas: f, columnas: c };
    },

    setTamanoEditor : function() {
        var tamano = this.calcularFilasColumnas(this.editor.value);
        this.editor.setAttribute('rows', tamano.filas );
        this.editor.setAttribute('cols', tamano.columnas );
        tamano = null;
    },

    nop: function () { },

    /**
     * @desc Destruye el nodo actual.
     */
    destroy : function () {
        this.rect.destroy();
        this.kText.destroy();
        this.group.destroy();
        delete this.kText;
        delete this.group;
    }
});


/**
 * @class MM.Globo
 * @classdesc Render de Globo. Se trata de un nodo con un texto y dentro de
 *            un globo. El render crea un grupo donde incluye los distintos 
 *            elementos gráficos. Hereda del MM.NodoSimple.
 * @constructor MM.Globo
 * @param {MM.Render} render    Escenario donde pintar el mensaje
 * @param {MM.Arbol}  arbol         Capa donde pintar el grid
 * @param {Object}    propiedades   Conjunto de propiedades a establecer al 
 *                                  nodo. Como Color fuente, posición, etc.
 *                                  Ver refencias Kinetic.Text, Kinetic.Blob,
 *                                  Kinetic.Line, Kinectic.Group.
 */
MM.Globo = MM.NodoSimple.extend(/** @lends MM.Globo.prototype */{
    init: function (render, arbol, propiedades) {
        propiedades.fontSize = 12;
        this._super(render, arbol, propiedades);

        this.line.hide();
        this.rect.show();
    },

    /**
     * @desc Pone el foco el nodo
     */
    ponerFoco : function () {
        this.rect.setStroke(this.colorFondo);
        this.rect.setFill(this.color);
        this.rect.setShadowColor(this.color);
        this.kText.setFill(this.colorFondo);
        this.triangle.setFill(this.colorFondo);
	this.group.draw();
    },

    /**
     * @desc Quita el foco del nodo
     */
    quitarFoco : function () {
        this.rect.setStroke(this.color);
        this.rect.setFill(this.colorFondo);
        this.rect.setShadowColor(this.colorFondo);
        this.kText.setFill(this.color);
        this.triangle.setFill(this.color);
	this.group.draw();
    }

});