client/js/renderer/noderepr.js
changeset 284 fa8035885814
child 293 fba23fde14ba
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/js/renderer/noderepr.js	Mon May 05 17:43:37 2014 +0200
@@ -0,0 +1,424 @@
+/* paper-renderer.js */
+"use strict";
+
+define(['jquery', 'underscore', 'requtils', 'renderer/baserepresentation'], function ($, _, requtils, BaseRepresentation) {
+    
+    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.circle = new paper.Path.Circle([0, 0], 1);
+            this.circle.__representation = this;
+            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.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);
+            }
+        },
+        redraw: function(_dontRedrawEdges) {
+            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 = .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()) {
+                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 * .5 : (opacity - .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 : .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.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 (!_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();
+                            }
+                        }
+                );
+            }
+
+        },
+        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 - .5 ) * height;
+                            } else {
+                                res = ( res - .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 = .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 = new paper.Path.Circle(centerPoint, baseRadius);
+                    _raster = new paper.Group(_circleClip, _raster);
+                    _raster.opacity = .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.redraw();
+                this.renderer.throttledPaperDraw();
+            } 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.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;
+            }
+            this._super("select");
+        },
+        unselect: function(_newTarget) {
+            if (!_newTarget || _newTarget.source_representation !== this) {
+                this.selected = false;
+                this.all_buttons.forEach(function(b) {
+                    b.hide();
+                });
+                this.circle.strokeWidth = this.options.node_stroke_width;
+                $('.Rk-Bin-Item').removeClass("selected");
+                if (this.renderer.minimap) {
+                    this.minimap_circle.strokeColor = undefined;
+                }
+                this._super("unselect");
+            }
+        },
+        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 (!_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();
+            }
+        }
+    });
+    
+    return NodeRepr;
+    
+});
+