client/js/renderer/noderepr.js
changeset 284 fa8035885814
child 293 fba23fde14ba
equal deleted inserted replaced
283:67f3a24a7c01 284:fa8035885814
       
     1 /* paper-renderer.js */
       
     2 "use strict";
       
     3 
       
     4 define(['jquery', 'underscore', 'requtils', 'renderer/baserepresentation'], function ($, _, requtils, BaseRepresentation) {
       
     5     
       
     6     var Utils = requtils.getUtils();
       
     7 
       
     8     /* Rkns.Renderer.Node Class */
       
     9 
       
    10     /* The representation for the node : A circle, with an image inside and a text label underneath.
       
    11      * The circle and the image are drawn on canvas and managed by Paper.js.
       
    12      * The text label is an HTML node, managed by jQuery. */
       
    13 
       
    14     //var NodeRepr = Renderer.Node = Utils.inherit(Renderer._BaseRepresentation);
       
    15     var NodeRepr = Utils.inherit(BaseRepresentation);
       
    16 
       
    17     _(NodeRepr.prototype).extend({
       
    18         _init: function() {
       
    19             this.renderer.node_layer.activate();
       
    20             this.type = "Node";
       
    21             this.circle = new paper.Path.Circle([0, 0], 1);
       
    22             this.circle.__representation = this;
       
    23             if (this.options.show_node_circles) {
       
    24                 this.circle.strokeWidth = this.options.node_stroke_width;
       
    25                 this.h_ratio = 1;
       
    26             } else {
       
    27                 this.h_ratio = 0;
       
    28             }
       
    29             this.title = $('<div class="Rk-Label">').appendTo(this.renderer.labels_$);
       
    30             if (this.options.editor_mode) {
       
    31                 var Renderer = requtils.getRenderer();
       
    32                 this.normal_buttons = [
       
    33                                        new Renderer.NodeEditButton(this.renderer, null),
       
    34                                        new Renderer.NodeRemoveButton(this.renderer, null),
       
    35                                        new Renderer.NodeLinkButton(this.renderer, null),
       
    36                                        new Renderer.NodeEnlargeButton(this.renderer, null),
       
    37                                        new Renderer.NodeShrinkButton(this.renderer, null)
       
    38                                        ];
       
    39                 this.pending_delete_buttons = [
       
    40                                                new Renderer.NodeRevertButton(this.renderer, null)
       
    41                                                ];
       
    42                 this.all_buttons = this.normal_buttons.concat(this.pending_delete_buttons);
       
    43                 for (var i = 0; i < this.all_buttons.length; i++) {
       
    44                     this.all_buttons[i].source_representation = this;
       
    45                 }
       
    46                 this.active_buttons = [];
       
    47             } else {
       
    48                 this.active_buttons = this.all_buttons = [];
       
    49             }
       
    50             this.last_circle_radius = 1;
       
    51 
       
    52             if (this.renderer.minimap) {
       
    53                 this.renderer.minimap.node_layer.activate();
       
    54                 this.minimap_circle = new paper.Path.Circle([0, 0], 1);
       
    55                 this.minimap_circle.__representation = this.renderer.minimap.miniframe.__representation;
       
    56                 this.renderer.minimap.node_group.addChild(this.minimap_circle);
       
    57             }
       
    58         },
       
    59         redraw: function(_dontRedrawEdges) {
       
    60             var _model_coords = new paper.Point(this.model.get("position")),
       
    61             _baseRadius = this.options.node_size_base * Math.exp((this.model.get("size") || 0) * Utils._NODE_SIZE_STEP);
       
    62             if (!this.is_dragging || !this.paper_coords) {
       
    63                 this.paper_coords = this.renderer.toPaperCoords(_model_coords);
       
    64             }
       
    65             this.circle_radius = _baseRadius * this.renderer.scale;
       
    66             if (this.last_circle_radius !== this.circle_radius) {
       
    67                 this.all_buttons.forEach(function(b) {
       
    68                     b.setSectorSize();
       
    69                 });
       
    70                 this.circle.scale(this.circle_radius / this.last_circle_radius);
       
    71                 if (this.node_image) {
       
    72                     this.node_image.scale(this.circle_radius / this.last_circle_radius);
       
    73                 }
       
    74             }
       
    75             this.circle.position = this.paper_coords;
       
    76             if (this.node_image) {
       
    77                 this.node_image.position = this.paper_coords.subtract(this.image_delta.multiply(this.circle_radius));
       
    78             }
       
    79             this.last_circle_radius = this.circle_radius;
       
    80 
       
    81             var old_act_btn = this.active_buttons;
       
    82 
       
    83             var opacity = 1;
       
    84             if (this.model.get("delete_scheduled")) {
       
    85                 opacity = .5;
       
    86                 this.active_buttons = this.pending_delete_buttons;
       
    87                 this.circle.dashArray = [2,2];
       
    88             } else {
       
    89                 opacity = 1;
       
    90                 this.active_buttons = this.normal_buttons;
       
    91                 this.circle.dashArray = null;
       
    92             }
       
    93 
       
    94             if (this.selected && this.renderer.isEditable()) {
       
    95                 if (old_act_btn !== this.active_buttons) {
       
    96                     old_act_btn.forEach(function(b) {
       
    97                         b.hide();
       
    98                     });
       
    99                 }
       
   100                 this.active_buttons.forEach(function(b) {
       
   101                     b.show();
       
   102                 });
       
   103             }
       
   104 
       
   105             if (this.node_image) {
       
   106                 this.node_image.opacity = this.highlighted ? opacity * .5 : (opacity - .01);
       
   107             }
       
   108 
       
   109             this.circle.fillColor = this.highlighted ? this.options.highlighted_node_fill_color : this.options.node_fill_color;
       
   110 
       
   111             this.circle.opacity = this.options.show_node_circles ? opacity : .01;
       
   112 
       
   113             var _text = this.model.get("title") || this.renkan.translate(this.options.label_untitled_nodes) || "";
       
   114             _text = Utils.shortenText(_text, this.options.node_label_max_length);
       
   115 
       
   116             if (typeof this.highlighted === "object") {
       
   117                 this.title.html(this.highlighted.replace(_(_text).escape(),'<span class="Rk-Highlighted">$1</span>'));
       
   118             } else {
       
   119                 this.title.text(_text);
       
   120             }
       
   121 
       
   122             this.title.css({
       
   123                 left: this.paper_coords.x,
       
   124                 top: this.paper_coords.y + this.circle_radius * this.h_ratio + this.options.node_label_distance,
       
   125                 opacity: opacity
       
   126             });
       
   127             var _color = this.model.get("color") || (this.model.get("created_by") || Utils._USER_PLACEHOLDER(this.renkan)).get("color");
       
   128             this.circle.strokeColor = _color;
       
   129             var _pc = this.paper_coords;
       
   130             this.all_buttons.forEach(function(b) {
       
   131                 b.moveTo(_pc);
       
   132             });
       
   133             var lastImage = this.img;
       
   134             this.img = this.model.get("image");
       
   135             if (this.img && this.img !== lastImage) {
       
   136                 this.showImage();
       
   137             }
       
   138             if (this.node_image && !this.img) {
       
   139                 this.node_image.remove();
       
   140                 delete this.node_image;
       
   141             }
       
   142 
       
   143             if (this.renderer.minimap) {
       
   144                 this.minimap_circle.fillColor = _color;
       
   145                 var minipos = this.renderer.toMinimapCoords(_model_coords),
       
   146                 miniradius = this.renderer.minimap.scale * _baseRadius,
       
   147                 minisize = new paper.Size([miniradius, miniradius]);
       
   148                 this.minimap_circle.fitBounds(minipos.subtract(minisize), minisize.multiply(2));
       
   149             }
       
   150 
       
   151             if (!_dontRedrawEdges) {
       
   152                 var _this = this;
       
   153                 _.each(
       
   154                         this.project.get("edges").filter(
       
   155                                 function (ed) {
       
   156                                     return ((ed.get("to") === _this.model) || (ed.get("from") === _this.model));
       
   157                                 }
       
   158                         ),
       
   159                         function(edge, index, list) {
       
   160                             var repr = _this.renderer.getRepresentationByModel(edge);
       
   161                             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") {
       
   162                                 repr.redraw();
       
   163                             }
       
   164                         }
       
   165                 );
       
   166             }
       
   167 
       
   168         },
       
   169         showImage: function() {
       
   170             var _image = null;
       
   171             if (typeof this.renderer.image_cache[this.img] === "undefined") {
       
   172                 _image = new Image();
       
   173                 this.renderer.image_cache[this.img] = _image;
       
   174                 _image.src = this.img;
       
   175             } else {
       
   176                 _image = this.renderer.image_cache[this.img];
       
   177             }
       
   178             if (_image.width) {
       
   179                 if (this.node_image) {
       
   180                     this.node_image.remove();
       
   181                 }
       
   182                 this.renderer.node_layer.activate();
       
   183                 var width = _image.width,
       
   184                 height = _image.height,
       
   185                 clipPath = this.model.get("clip_path"),
       
   186                 hasClipPath = (typeof clipPath !== "undefined" && clipPath),
       
   187                 _clip = null,
       
   188                 baseRadius = null,
       
   189                 centerPoint = null;
       
   190 
       
   191                 if (hasClipPath) {
       
   192                     _clip = new paper.Path();
       
   193                     var instructions = clipPath.match(/[a-z][^a-z]+/gi) || [],
       
   194                     lastCoords = [0,0],
       
   195                     minX = Infinity,
       
   196                     minY = Infinity,
       
   197                     maxX = -Infinity,
       
   198                     maxY = -Infinity;
       
   199 
       
   200                     var transformCoords = function(tabc, relative) {
       
   201                         var newCoords = tabc.slice(1).map(function(v, k) {
       
   202                             var res = parseFloat(v),
       
   203                             isY = k % 2;
       
   204                             if (isY) {
       
   205                                 res = ( res - .5 ) * height;
       
   206                             } else {
       
   207                                 res = ( res - .5 ) * width;
       
   208                             }
       
   209                             if (relative) {
       
   210                                 res += lastCoords[isY];
       
   211                             }
       
   212                             if (isY) {
       
   213                                 minY = Math.min(minY, res);
       
   214                                 maxY = Math.max(maxY, res);
       
   215                             } else {
       
   216                                 minX = Math.min(minX, res);
       
   217                                 maxX = Math.max(maxX, res);
       
   218                             }
       
   219                             return res;
       
   220                         });
       
   221                         lastCoords = newCoords.slice(-2);
       
   222                         return newCoords;
       
   223                     };
       
   224 
       
   225                     instructions.forEach(function(instr) {
       
   226                         var coords = instr.match(/([a-z]|[0-9.-]+)/ig) || [""];
       
   227                         switch(coords[0]) {
       
   228                         case "M":
       
   229                             _clip.moveTo(transformCoords(coords));
       
   230                             break;
       
   231                         case "m":
       
   232                             _clip.moveTo(transformCoords(coords, true));
       
   233                             break;
       
   234                         case "L":
       
   235                             _clip.lineTo(transformCoords(coords));
       
   236                             break;
       
   237                         case "l":
       
   238                             _clip.lineTo(transformCoords(coords, true));
       
   239                             break;
       
   240                         case "C":
       
   241                             _clip.cubicCurveTo(transformCoords(coords));
       
   242                             break;
       
   243                         case "c":
       
   244                             _clip.cubicCurveTo(transformCoords(coords, true));
       
   245                             break;
       
   246                         case "Q":
       
   247                             _clip.quadraticCurveTo(transformCoords(coords));
       
   248                             break;
       
   249                         case "q":
       
   250                             _clip.quadraticCurveTo(transformCoords(coords, true));
       
   251                             break;
       
   252                         }
       
   253                     });
       
   254 
       
   255                     baseRadius = Math[this.options.node_images_fill_mode ? "min" : "max"](maxX - minX, maxY - minY) / 2;
       
   256                     centerPoint = new paper.Point((maxX + minX) / 2, (maxY + minY) / 2);
       
   257                     if (!this.options.show_node_circles) {
       
   258                         this.h_ratio = (maxY - minY) / (2 * baseRadius);
       
   259                     }
       
   260                 } else {
       
   261                     baseRadius = Math[this.options.node_images_fill_mode ? "min" : "max"](width, height) / 2;
       
   262                     centerPoint = new paper.Point(0,0);
       
   263                     if (!this.options.show_node_circles) {
       
   264                         this.h_ratio = height / (2 * baseRadius);
       
   265                     }
       
   266                 }
       
   267                 var _raster = new paper.Raster(_image);
       
   268                 _raster.locked = true; // Disable mouse events on icon
       
   269                 if (hasClipPath) {
       
   270                     _raster = new paper.Group(_clip, _raster);
       
   271                     _raster.opacity = .99;
       
   272                     /* This is a workaround to allow clipping at group level
       
   273                      * If opacity was set to 1, paper.js would merge all clipping groups in one (known bug).
       
   274                      */
       
   275                     _raster.clipped = true;
       
   276                     _clip.__representation = this;
       
   277                 }
       
   278                 if (this.options.clip_node_images) {
       
   279                     var _circleClip = new paper.Path.Circle(centerPoint, baseRadius);
       
   280                     _raster = new paper.Group(_circleClip, _raster);
       
   281                     _raster.opacity = .99;
       
   282                     _raster.clipped = true;
       
   283                     _circleClip.__representation = this;
       
   284                 }
       
   285                 this.image_delta = centerPoint.divide(baseRadius);
       
   286                 this.node_image = _raster;
       
   287                 this.node_image.__representation = _this;
       
   288                 this.node_image.scale(this.circle_radius / baseRadius);
       
   289                 this.node_image.position = this.paper_coords.subtract(this.image_delta.multiply(this.circle_radius));
       
   290                 this.redraw();
       
   291                 this.renderer.throttledPaperDraw();
       
   292             } else {
       
   293                 var _this = this;
       
   294                 $(_image).on("load", function() {
       
   295                     _this.showImage();
       
   296                 });
       
   297             }
       
   298         },
       
   299         paperShift: function(_delta) {
       
   300             if (this.options.editor_mode) {
       
   301                 if (!this.renkan.read_only) {
       
   302                     this.is_dragging = true;
       
   303                     this.paper_coords = this.paper_coords.add(_delta);
       
   304                     this.redraw();
       
   305                 }
       
   306             } else {
       
   307                 this.renderer.paperShift(_delta);
       
   308             }
       
   309         },
       
   310         openEditor: function() {
       
   311             this.renderer.removeRepresentationsOfType("editor");
       
   312             var _editor = this.renderer.addRepresentation("NodeEditor",null);
       
   313             _editor.source_representation = this;
       
   314             _editor.draw();
       
   315         },
       
   316         select: function() {
       
   317             this.selected = true;
       
   318             this.circle.strokeWidth = this.options.selected_node_stroke_width;
       
   319             if (this.renderer.isEditable()) {
       
   320                 this.active_buttons.forEach(function(b) {
       
   321                     b.show();
       
   322                 });
       
   323             }
       
   324             var _uri = this.model.get("uri");
       
   325             if (_uri) {
       
   326                 $('.Rk-Bin-Item').each(function() {
       
   327                     var _el = $(this);
       
   328                     if (_el.attr("data-uri") == _uri) {
       
   329                         _el.addClass("selected");
       
   330                     }
       
   331                 });
       
   332             }
       
   333             if (!this.options.editor_mode) {
       
   334                 this.openEditor();
       
   335             }
       
   336 
       
   337             if (this.renderer.minimap) {
       
   338                 this.minimap_circle.strokeWidth = this.options.minimap_highlight_weight;
       
   339                 this.minimap_circle.strokeColor = this.options.minimap_highlight_color;
       
   340             }
       
   341             this._super("select");
       
   342         },
       
   343         unselect: function(_newTarget) {
       
   344             if (!_newTarget || _newTarget.source_representation !== this) {
       
   345                 this.selected = false;
       
   346                 this.all_buttons.forEach(function(b) {
       
   347                     b.hide();
       
   348                 });
       
   349                 this.circle.strokeWidth = this.options.node_stroke_width;
       
   350                 $('.Rk-Bin-Item').removeClass("selected");
       
   351                 if (this.renderer.minimap) {
       
   352                     this.minimap_circle.strokeColor = undefined;
       
   353                 }
       
   354                 this._super("unselect");
       
   355             }
       
   356         },
       
   357         highlight: function(textToReplace) {
       
   358             var hlvalue = textToReplace || true;
       
   359             if (this.highlighted === hlvalue) {
       
   360                 return;
       
   361             }
       
   362             this.highlighted = hlvalue;
       
   363             this.redraw();
       
   364             this.renderer.throttledPaperDraw();
       
   365         },
       
   366         unhighlight: function() {
       
   367             if (!this.highlighted) {
       
   368                 return;
       
   369             }
       
   370             this.highlighted = false;
       
   371             this.redraw();
       
   372             this.renderer.throttledPaperDraw();
       
   373         },
       
   374         saveCoords: function() {
       
   375             var _coords = this.renderer.toModelCoords(this.paper_coords),
       
   376             _data = {
       
   377                 position: {
       
   378                     x: _coords.x,
       
   379                     y: _coords.y
       
   380                 }
       
   381             };
       
   382             if (this.renderer.isEditable()) {
       
   383                 this.model.set(_data);
       
   384             }
       
   385         },
       
   386         mousedown: function(_event, _isTouch) {
       
   387             if (_isTouch) {
       
   388                 this.renderer.unselectAll();
       
   389                 this.select();
       
   390             }
       
   391         },
       
   392         mouseup: function(_event, _isTouch) {
       
   393             if (this.renderer.is_dragging && this.renderer.isEditable()) {
       
   394                 this.saveCoords();
       
   395             } else {
       
   396                 if (!_isTouch && !this.model.get("delete_scheduled")) {
       
   397                     this.openEditor();
       
   398                 }
       
   399                 this.model.trigger("clicked");
       
   400             }
       
   401             this.renderer.click_target = null;
       
   402             this.renderer.is_dragging = false;
       
   403             this.is_dragging = false;
       
   404         },
       
   405         destroy: function(_event) {
       
   406             this._super("destroy");
       
   407             this.all_buttons.forEach(function(b) {
       
   408                 b.destroy();
       
   409             });
       
   410             this.circle.remove();
       
   411             this.title.remove();
       
   412             if (this.renderer.minimap) {
       
   413                 this.minimap_circle.remove();
       
   414             }
       
   415             if (this.node_image) {
       
   416                 this.node_image.remove();
       
   417             }
       
   418         }
       
   419     });
       
   420     
       
   421     return NodeRepr;
       
   422     
       
   423 });
       
   424