client/js/renderer/noderepr.js
author rougeronj
Wed, 20 May 2015 10:39:35 +0200
changeset 450 88e8673aaeeb
parent 435 e529b633c339
child 453 04b7d46e9d67
permissions -rw-r--r--
add the hidden/ghost behaviour: - The view hide or show the nodes calling their functions hide() or show(ghost). - The node and the edges have the attributes 'hidden' and 'ghost' to properly redraw them selves.

define(['jquery', 'underscore', 'requtils', 'renderer/baserepresentation', 'renderer/shapebuilder'], function ($, _, requtils, BaseRepresentation, ShapeBuilder) {
    'use strict';

    var Utils = requtils.getUtils();

    /* Rkns.Renderer.Node Class */

    /* The representation for the node : A circle, with an image inside and a text label underneath.
     * The circle and the image are drawn on canvas and managed by Paper.js.
     * The text label is an HTML node, managed by jQuery. */

    //var NodeRepr = Renderer.Node = Utils.inherit(Renderer._BaseRepresentation);
    var NodeRepr = Utils.inherit(BaseRepresentation);

    _(NodeRepr.prototype).extend({
        _init: function() {
            this.renderer.node_layer.activate();
            this.type = "Node";
            this.buildShape();
            this.hidden = false;
            this.ghost= false;
            if (this.options.show_node_circles) {
                this.circle.strokeWidth = this.options.node_stroke_width;
                this.h_ratio = 1;
            } else {
                this.h_ratio = 0;
            }
            this.title = $('<div class="Rk-Label">').appendTo(this.renderer.labels_$);

            if (this.options.editor_mode) {
                var Renderer = requtils.getRenderer();
                this.normal_buttons = [
                                       new Renderer.NodeEditButton(this.renderer, null),
                                       new Renderer.NodeRemoveButton(this.renderer, null),
                                       new Renderer.NodeHideButton(this.renderer, null),
                                       new Renderer.NodeLinkButton(this.renderer, null),
                                       new Renderer.NodeEnlargeButton(this.renderer, null),
                                       new Renderer.NodeShrinkButton(this.renderer, null)
                                       ];
                this.pending_delete_buttons = [
                                               new Renderer.NodeRevertButton(this.renderer, null)
                                               ];
                this.all_buttons = this.normal_buttons.concat(this.pending_delete_buttons);

                for (var i = 0; i < this.all_buttons.length; i++) {
                    this.all_buttons[i].source_representation = this;
                }
                this.active_buttons = [];
            } else {
                this.active_buttons = this.all_buttons = [];
            }
            this.last_circle_radius = 1;

            if (this.renderer.minimap) {
                this.renderer.minimap.node_layer.activate();
                this.minimap_circle = new paper.Path.Circle([0, 0], 1);
                this.minimap_circle.__representation = this.renderer.minimap.miniframe.__representation;
                this.renderer.minimap.node_group.addChild(this.minimap_circle);
            }
        },
        buildShape: function(){
            if( 'shape' in this.model.changed ) {
                delete this.img;
            }
            if(this.circle){
                this.circle.remove();
                delete this.circle;
            }
            // "circle" "rectangle" "ellipse" "polygon" "star" "diamond"
            this.shapeBuilder = new ShapeBuilder(this.model.get("shape"));
            this.circle = this.shapeBuilder.getShape();
            this.circle.__representation = this;
            this.circle.sendToBack();
            this.last_circle_radius = 1;
        },
        redraw: function(options) {
            if( 'shape' in this.model.changed && 'change' in options && options.change ) {
            //if( 'shape' in this.model.changed ) {
                this.buildShape();
            }
            var _model_coords = new paper.Point(this.model.get("position")),
                _baseRadius = this.options.node_size_base * Math.exp((this.model.get("size") || 0) * Utils._NODE_SIZE_STEP);
            if (!this.is_dragging || !this.paper_coords) {
                this.paper_coords = this.renderer.toPaperCoords(_model_coords);
            }
            this.circle_radius = _baseRadius * this.renderer.scale;
            if (this.last_circle_radius !== this.circle_radius) {
                this.all_buttons.forEach(function(b) {
                    b.setSectorSize();
                });
                this.circle.scale(this.circle_radius / this.last_circle_radius);
                if (this.node_image) {
                    this.node_image.scale(this.circle_radius / this.last_circle_radius);
                }
            }
            this.circle.position = this.paper_coords;
            if (this.node_image) {
                this.node_image.position = this.paper_coords.subtract(this.image_delta.multiply(this.circle_radius));
            }
            this.last_circle_radius = this.circle_radius;

            var old_act_btn = this.active_buttons;
            
            var opacity = 1;
            if (this.model.get("delete_scheduled")) {
                opacity = 0.5;
                this.active_buttons = this.pending_delete_buttons;
                this.circle.dashArray = [2,2];
            } else {
                opacity = 1;
                this.active_buttons = this.normal_buttons;
                this.circle.dashArray = null;
            }
            if (this.selected && this.renderer.isEditable() && !this.ghost) {
                if (old_act_btn !== this.active_buttons) {
                    old_act_btn.forEach(function(b) {
                        b.hide();
                    });
                }
                this.active_buttons.forEach(function(b) {
                    b.show();
                });
            }

            if (this.node_image) {
                this.node_image.opacity = this.highlighted ? opacity * 0.5 : (opacity - 0.01);
            }

            this.circle.fillColor = this.highlighted ? this.options.highlighted_node_fill_color : this.options.node_fill_color;

            this.circle.opacity = this.options.show_node_circles ? opacity : 0.01;

            var _text = this.model.get("title") || this.renkan.translate(this.options.label_untitled_nodes) || "";
            _text = Utils.shortenText(_text, this.options.node_label_max_length);

            if (typeof this.highlighted === "object") {
                this.title.html(this.highlighted.replace(_(_text).escape(),'<span class="Rk-Highlighted">$1</span>'));
            } else {
                this.title.text(_text);
            }
            this.title.css({
                left: this.paper_coords.x,
                top: this.paper_coords.y + this.circle_radius * this.h_ratio + this.options.node_label_distance,
                opacity: opacity
            });
            var _color = this.model.get("color") || (this.model.get("created_by") || Utils._USER_PLACEHOLDER(this.renkan)).get("color");
            this.circle.strokeColor = _color;
            var _pc = this.paper_coords;
            this.all_buttons.forEach(function(b) {
                b.moveTo(_pc);
            });
            var lastImage = this.img;
            this.img = this.model.get("image");
            if (this.img && this.img !== lastImage) {
                this.showImage();
                if(this.circle) {
                    this.circle.sendToBack();
                }
            }
            if (this.node_image && !this.img) {
                this.node_image.remove();
                delete this.node_image;
            }
            
            
            if (this.renderer.minimap) {
                this.minimap_circle.fillColor = _color;
                var minipos = this.renderer.toMinimapCoords(_model_coords),
                miniradius = this.renderer.minimap.scale * _baseRadius,
                minisize = new paper.Size([miniradius, miniradius]);
                this.minimap_circle.fitBounds(minipos.subtract(minisize), minisize.multiply(2));
            }

            if (typeof options === 'undefined' || !('dontRedrawEdges' in options) || !options.dontRedrawEdges) {
                var _this = this;
                _.each(
                        this.project.get("edges").filter(
                                function (ed) {
                                    return ((ed.get("to") === _this.model) || (ed.get("from") === _this.model));
                                }
                        ),
                        function(edge, index, list) {
                            var repr = _this.renderer.getRepresentationByModel(edge);
                            if (repr && typeof repr.from_representation !== "undefined" && typeof repr.from_representation.paper_coords !== "undefined" && typeof repr.to_representation !== "undefined" && typeof repr.to_representation.paper_coords !== "undefined") {
                                repr.redraw();
                            }
                        }
                );
            }
            if (this.ghost){
                this.show(true);
            } else {
                if (this.hidden) { this.hide(); }
            }
        },
        showImage: function() {
            var _image = null;
            if (typeof this.renderer.image_cache[this.img] === "undefined") {
                _image = new Image();
                this.renderer.image_cache[this.img] = _image;
                _image.src = this.img;
            } else {
                _image = this.renderer.image_cache[this.img];
            }
            if (_image.width) {
                if (this.node_image) {
                    this.node_image.remove();
                }
                this.renderer.node_layer.activate();
                var width = _image.width,
                    height = _image.height,
                    clipPath = this.model.get("clip_path"),
                    hasClipPath = (typeof clipPath !== "undefined" && clipPath),
                    _clip = null,
                    baseRadius = null,
                    centerPoint = null;

                if (hasClipPath) {
                    _clip = new paper.Path();
                    var instructions = clipPath.match(/[a-z][^a-z]+/gi) || [],
                    lastCoords = [0,0],
                    minX = Infinity,
                    minY = Infinity,
                    maxX = -Infinity,
                    maxY = -Infinity;

                    var transformCoords = function(tabc, relative) {
                        var newCoords = tabc.slice(1).map(function(v, k) {
                            var res = parseFloat(v),
                            isY = k % 2;
                            if (isY) {
                                res = ( res - 0.5 ) * height;
                            } else {
                                res = ( res - 0.5 ) * width;
                            }
                            if (relative) {
                                res += lastCoords[isY];
                            }
                            if (isY) {
                                minY = Math.min(minY, res);
                                maxY = Math.max(maxY, res);
                            } else {
                                minX = Math.min(minX, res);
                                maxX = Math.max(maxX, res);
                            }
                            return res;
                        });
                        lastCoords = newCoords.slice(-2);
                        return newCoords;
                    };

                    instructions.forEach(function(instr) {
                        var coords = instr.match(/([a-z]|[0-9.-]+)/ig) || [""];
                        switch(coords[0]) {
                        case "M":
                            _clip.moveTo(transformCoords(coords));
                            break;
                        case "m":
                            _clip.moveTo(transformCoords(coords, true));
                            break;
                        case "L":
                            _clip.lineTo(transformCoords(coords));
                            break;
                        case "l":
                            _clip.lineTo(transformCoords(coords, true));
                            break;
                        case "C":
                            _clip.cubicCurveTo(transformCoords(coords));
                            break;
                        case "c":
                            _clip.cubicCurveTo(transformCoords(coords, true));
                            break;
                        case "Q":
                            _clip.quadraticCurveTo(transformCoords(coords));
                            break;
                        case "q":
                            _clip.quadraticCurveTo(transformCoords(coords, true));
                            break;
                        }
                    });

                    baseRadius = Math[this.options.node_images_fill_mode ? "min" : "max"](maxX - minX, maxY - minY) / 2;
                    centerPoint = new paper.Point((maxX + minX) / 2, (maxY + minY) / 2);
                    if (!this.options.show_node_circles) {
                        this.h_ratio = (maxY - minY) / (2 * baseRadius);
                    }
                } else {
                    baseRadius = Math[this.options.node_images_fill_mode ? "min" : "max"](width, height) / 2;
                    centerPoint = new paper.Point(0,0);
                    if (!this.options.show_node_circles) {
                        this.h_ratio = height / (2 * baseRadius);
                    }
                }
                var _raster = new paper.Raster(_image);
                _raster.locked = true; // Disable mouse events on icon
                if (hasClipPath) {
                    _raster = new paper.Group(_clip, _raster);
                    _raster.opacity = 0.99;
                    /* This is a workaround to allow clipping at group level
                     * If opacity was set to 1, paper.js would merge all clipping groups in one (known bug).
                     */
                    _raster.clipped = true;
                    _clip.__representation = this;
                }
                if (this.options.clip_node_images) {
                    var _circleClip = this.shapeBuilder.getImageShape(centerPoint, baseRadius);
                    _raster = new paper.Group(_circleClip, _raster);
                    _raster.opacity = 0.99;
                    _raster.clipped = true;
                    _circleClip.__representation = this;
                }
                this.image_delta = centerPoint.divide(baseRadius);
                this.node_image = _raster;
                this.node_image.__representation = _this;
                this.node_image.scale(this.circle_radius / baseRadius);
                this.node_image.position = this.paper_coords.subtract(this.image_delta.multiply(this.circle_radius));
                this.node_image.insertAbove(this.circle);
            } else {
                var _this = this;
                $(_image).on("load", function() {
                    _this.showImage();
                });
            }
        },
        paperShift: function(_delta) {
            if (this.options.editor_mode) {
                if (!this.renkan.read_only) {
                    this.is_dragging = true;
                    this.paper_coords = this.paper_coords.add(_delta);
                    this.redraw();
                }
            } else {
                this.renderer.paperShift(_delta);
            }
        },
        openEditor: function() {
            this.renderer.removeRepresentationsOfType("editor");
            var _editor = this.renderer.addRepresentation("NodeEditor",null);
            _editor.source_representation = this;
            _editor.draw();
        },
        select: function() {
            this.selected = true;
            this.circle.strokeWidth = this.options.selected_node_stroke_width;
            if (this.renderer.isEditable() && !this.hidden) {
                this.active_buttons.forEach(function(b) {
                    b.show();
                });
            }
            var _uri = this.model.get("uri");
            if (_uri) {
                $('.Rk-Bin-Item').each(function() {
                    var _el = $(this);
                    if (_el.attr("data-uri") === _uri) {
                        _el.addClass("selected");
                    }
                });
            }
            if (!this.options.editor_mode) {
                this.openEditor();
            }

            if (this.renderer.minimap) {
                this.minimap_circle.strokeWidth = this.options.minimap_highlight_weight;
                this.minimap_circle.strokeColor = this.options.minimap_highlight_color;
            }
            //if the node is hidden and the mouse hover it, it appears as a ghost
            if (this.hidden){
                this.show(true);
            }
            this._super("select");
        },
        hideButtons: function() {
            this.all_buttons.forEach(function(b) {
                b.hide();
            });
            delete(this.buttonTimeout);
        },
        unselect: function(_newTarget) {
            if (!_newTarget || _newTarget.source_representation !== this) {
                this.selected = false;
                var _this = this;
                this.buttons_timeout = setTimeout(function() { _this.hideButtons(); }, 200);
                this.circle.strokeWidth = this.options.node_stroke_width;
                $('.Rk-Bin-Item').removeClass("selected");
                if (this.renderer.minimap) {
                    this.minimap_circle.strokeColor = undefined;
                }
                //when the mouse don't hover the node anymore, we hide it
                if (this.hidden){
                    this.hide();
                }
                this._super("unselect");
            }
        },
        hide: function(){
            var _this = this;
            this.ghost = false;
            this.hidden = true;
            if (typeof this.node_image !== 'undefined'){
                this.node_image.opacity = 0;                
            }
            this.hideButtons();
            this.circle.opacity = 0;
            this.title.css('opacity', 0);
            this.minimap_circle.opacity = 0;
            
            
            _.each(
                    this.project.get("edges").filter(
                            function (ed) {
                                return ((ed.get("to") === _this.model) || (ed.get("from") === _this.model));
                            }
                    ),
                    function(edge, index, list) {
                        var repr = _this.renderer.getRepresentationByModel(edge);
                        if (repr && typeof repr.from_representation !== "undefined" && typeof repr.from_representation.paper_coords !== "undefined" && typeof repr.to_representation !== "undefined" && typeof repr.to_representation.paper_coords !== "undefined") {
                            repr.hide();
                        }
                    }
            );
        },
        show: function(ghost){
            var _this = this;
            this.ghost = ghost;
            if (this.ghost){
                if (typeof this.node_image !== 'undefined'){
                    this.node_image.opacity = 0.3;
                }
                this.circle.opacity = 0.3;
                this.title.css('opacity', 0.3);
                this.minimap_circle.opacity = 0.3;
            } else {
                this.hidden = false;
                this.redraw();
            }
            
            _.each(
                    this.project.get("edges").filter(
                            function (ed) {
                                return ((ed.get("to") === _this.model) || (ed.get("from") === _this.model));
                            }
                    ),
                    function(edge, index, list) {
                        var repr = _this.renderer.getRepresentationByModel(edge);
                        if (repr && typeof repr.from_representation !== "undefined" && typeof repr.from_representation.paper_coords !== "undefined" && typeof repr.to_representation !== "undefined" && typeof repr.to_representation.paper_coords !== "undefined") {
                            repr.show(_this.ghost);
                        }
                    }
            );            
        },
        highlight: function(textToReplace) {
            var hlvalue = textToReplace || true;
            if (this.highlighted === hlvalue) {
                return;
            }
            this.highlighted = hlvalue;
            this.redraw();
            this.renderer.throttledPaperDraw();
        },
        unhighlight: function() {
            if (!this.highlighted) {
                return;
            }
            this.highlighted = false;
            this.redraw();
            this.renderer.throttledPaperDraw();
        },
        saveCoords: function() {
            var _coords = this.renderer.toModelCoords(this.paper_coords),
            _data = {
                position: {
                    x: _coords.x,
                    y: _coords.y
                }
            };
            if (this.renderer.isEditable()) {
                this.model.set(_data);
            }
        },
        mousedown: function(_event, _isTouch) {
            if (_isTouch) {
                this.renderer.unselectAll();
                this.select();
            }
        },
        mouseup: function(_event, _isTouch) {
            if (this.renderer.is_dragging && this.renderer.isEditable()) {
                this.saveCoords();
            } else {
                if (this.hidden){
                    var index = this.renderer.hiddenNodes.indexOf(this.model.id);
                    if (index !== -1){
                        this.renderer.hiddenNodes.splice(index, 1);
                    }
                    this.show(false);
                    this.select();
                }else{
                    if (!_isTouch && !this.model.get("delete_scheduled")) {
                        this.openEditor();
                    }
                    this.model.trigger("clicked");                    
                }
            }
            this.renderer.click_target = null;
            this.renderer.is_dragging = false;
            this.is_dragging = false;
        },
        destroy: function(_event) {
            this._super("destroy");
            this.all_buttons.forEach(function(b) {
                b.destroy();
            });
            this.circle.remove();
            this.title.remove();
            if (this.renderer.minimap) {
                this.minimap_circle.remove();
            }
            if (this.node_image) {
                this.node_image.remove();
            }
        }
    }).value();

    return NodeRepr;

});