src/js/libs/renkan-publish.js
branchnew-model
changeset 938 991f897f1cb1
child 950 b7e236e4ec1c
equal deleted inserted replaced
937:eb3c442cec50 938:991f897f1cb1
       
     1 /* 
       
     2  *  Copyright 2012 Institut de recherche et d'innovation 
       
     3  *  contributor(s) : Yves-Marie Haussonne, Raphael Velt, Samuel Huron
       
     4  *   
       
     5  *  contact@iri.centrepompidou.fr
       
     6  *  http://www.iri.centrepompidou.fr 
       
     7  *   
       
     8  *  This software is a computer program whose purpose is to show and add annotations on a video .
       
     9  *  This software is governed by the CeCILL-C license under French law and
       
    10  *  abiding by the rules of distribution of free software. You can  use, 
       
    11  *  modify and/ or redistribute the software under the terms of the CeCILL-C
       
    12  *  license as circulated by CEA, CNRS and INRIA at the following URL
       
    13  *  "http://www.cecill.info". 
       
    14  *  
       
    15  *  The fact that you are presently reading this means that you have had
       
    16  *  knowledge of the CeCILL-C license and that you accept its terms.
       
    17 */
       
    18 
       
    19 if (typeof Rkns !== "object") {
       
    20     Rkns = {}
       
    21 }
       
    22 
       
    23 Rkns.$ = jQuery;
       
    24 
       
    25 Rkns._ = _;
       
    26 
       
    27 Rkns.i18n = {
       
    28     en: {
       
    29         zoom_in: "Zoom In",
       
    30         zoom_out: "Zoom Out"
       
    31     }
       
    32 }
       
    33 
       
    34 Rkns.Utils = {
       
    35     inherit : function(_baseClass) {
       
    36         var _class = function() {
       
    37             _baseClass.apply(this, Array.prototype.slice.call(arguments, 0));
       
    38             if (typeof this._init == "function") {
       
    39                 this._init.apply(this, Array.prototype.slice.call(arguments, 0));
       
    40             }
       
    41         }
       
    42         _class.prototype = new _baseClass();
       
    43         return _class;
       
    44     }
       
    45 }
       
    46 
       
    47 Rkns.Models = {};
       
    48 
       
    49 Rkns.Models.getUID = function(obj) {
       
    50     var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
       
    51         var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
       
    52         return v.toString(16);
       
    53     });
       
    54     return obj.type + "-" + guid; 
       
    55 };
       
    56 
       
    57 Rkns.Models.RenkanModel = Backbone.RelationalModel.extend({
       
    58     idAttribute : "_id",
       
    59     constructor: function(options) {
       
    60         
       
    61         if (typeof options !== "undefined") {
       
    62             options._id = options._id || options.id || Rkns.Models.getUID(this);
       
    63             options.title = options.title || "(untitled " + this.type + ")";
       
    64             options.description = options.description || "";
       
    65             options.uri = options.uri || "";
       
    66             
       
    67             if(typeof this.prepare === "function") {
       
    68                 options = this.prepare(options);
       
    69             }
       
    70         }
       
    71         Backbone.RelationalModel.prototype.constructor.call(this, options);
       
    72     },
       
    73     validate: function() {
       
    74         if(!this.type) {
       
    75             return "object has no type";
       
    76         }
       
    77     },
       
    78     addReference : function(_options, _propName, _list, _id, _default) {
       
    79         var _element = _list.get(_id);
       
    80         if (typeof _element === "undefined" && typeof _default !== "undefined") {
       
    81             _options[_propName ] = _default;
       
    82         }
       
    83         else {
       
    84             _options[_propName ] = _element;
       
    85         }
       
    86     }
       
    87 });
       
    88     
       
    89 // USER
       
    90 Rkns.Models.User = Rkns.Models.RenkanModel.extend({
       
    91     type: "user",
       
    92     prepare: function(options) {
       
    93         options.color = options.color || "#666666";
       
    94         return options;
       
    95     },
       
    96     toJSON: function() {
       
    97         return {
       
    98             id: this.get("_id"),
       
    99             title: this.get("title"),
       
   100             uri: this.get("uri"),
       
   101             description: this.get("description"),
       
   102             color: this.get("color"),
       
   103         }
       
   104     },
       
   105 });
       
   106 
       
   107 // NODE
       
   108 Rkns.Models.Node = Rkns.Models.RenkanModel.extend({
       
   109     type: "node",
       
   110     relations: [{
       
   111         type: Backbone.HasOne,
       
   112         key: "created_by",
       
   113         relatedModel: Rkns.Models.User
       
   114     }],
       
   115     prepare: function(options) {
       
   116         project = options.project;
       
   117         this.addReference(options, "created_by", project.get("users"), options.created_by, project.current_user);
       
   118         options.description = options.description || "";
       
   119         return options;
       
   120     },
       
   121     toJSON: function() {
       
   122         return {
       
   123             id: this.get("_id"),
       
   124             title: this.get("title"),
       
   125             uri: this.get("uri"),
       
   126             description: this.get("description"),
       
   127             position: this.get("position"),
       
   128             created_by: this.get("created_by").get("_id")
       
   129         }
       
   130     },
       
   131 });
       
   132 
       
   133 // EDGE
       
   134 Rkns.Models.Edge = Rkns.Models.RenkanModel.extend({
       
   135     type: "edge",
       
   136     relations: [
       
   137       {
       
   138         type: Backbone.HasOne,
       
   139         key: "created_by",
       
   140         relatedModel: Rkns.Models.User
       
   141       },
       
   142       {
       
   143         type: Backbone.HasOne,
       
   144         key: "from",
       
   145         relatedModel: Rkns.Models.Node
       
   146       },
       
   147       {
       
   148         type: Backbone.HasOne,
       
   149         key: "to",
       
   150         relatedModel: Rkns.Models.Node
       
   151       },
       
   152     ],
       
   153     prepare: function(options) {
       
   154         project = options.project;
       
   155         this.addReference(options, "created_by", project.get("users"), options.created_by, project.current_user);
       
   156         this.addReference(options, "from", project.get("nodes"), options.from);
       
   157         this.addReference(options, "to", project.get("nodes"), options.to);
       
   158         return options;
       
   159     },
       
   160     toJSON: function() {
       
   161         return {
       
   162             id: this.get("_id"),
       
   163             title: this.get("title"),
       
   164             uri: this.get("uri"),
       
   165             description: this.get("description"),
       
   166             from: this.get("from").get("_id"),
       
   167             to: this.get("to").get("_id"),
       
   168             created_by: this.get("created_by").get("_id"),
       
   169         }
       
   170     },
       
   171 });
       
   172     
       
   173 // PROJECT
       
   174 Rkns.Models.Project = Rkns.Models.RenkanModel.extend({
       
   175     type: "project",
       
   176     relations: [
       
   177       {
       
   178         type: Backbone.HasMany,
       
   179         key: "users",
       
   180         relatedModel: Rkns.Models.User,
       
   181         reverseRelation: {
       
   182             key: 'project',
       
   183             includeInJSON: '_id'
       
   184         },
       
   185       },
       
   186       {
       
   187         type: Backbone.HasMany,
       
   188         key: "nodes",
       
   189         relatedModel: Rkns.Models.Node,
       
   190         reverseRelation: {
       
   191             key: 'project',
       
   192             includeInJSON: '_id'
       
   193         },
       
   194       },
       
   195       {
       
   196         type: Backbone.HasMany,
       
   197         key: "edges",
       
   198         relatedModel: Rkns.Models.Edge,
       
   199         reverseRelation: {
       
   200             key: 'project',
       
   201             includeInJSON: '_id'
       
   202         },
       
   203       }
       
   204     ],
       
   205     addUser: function(_props) {
       
   206         _props.project = this;
       
   207         var _user = new Rkns.Models.User(_props);
       
   208         this.get("users").push(_user);
       
   209         return _user;
       
   210     },
       
   211     addNode: function(_props) {
       
   212         _props.project = this;
       
   213         var _node = new Rkns.Models.Node(_props);
       
   214         this.get("nodes").push(_node);
       
   215         return _node;
       
   216     },
       
   217     addEdge: function(_props) {
       
   218         _props.project = this;
       
   219         var _edge = new Rkns.Models.Edge(_props);
       
   220         this.get("edges").push(_edge);
       
   221         return _edge;
       
   222     },
       
   223     removeNode: function(_model) {
       
   224         this.get("nodes").remove(_model);
       
   225     },
       
   226     removeEdge: function(_model) {
       
   227         this.get("edges").remove(_model);
       
   228     },
       
   229     validate: function(options) {
       
   230         var _project = this;
       
   231         Rkns._(options.users).each(function(_item) {
       
   232             _item.project = _project;
       
   233         });
       
   234         Rkns._(options.nodes).each(function(_item) {
       
   235             _item.project = _project;
       
   236         });
       
   237         Rkns._(options.edges).each(function(_item) {
       
   238             _item.project = _project;
       
   239         });
       
   240     },
       
   241     // Add event handler to remove edges when a node is removed
       
   242     initialize: function() {
       
   243         var _this = this;
       
   244         this.on("remove:nodes", function(_node) {
       
   245             _this.get("edges").remove(
       
   246                 _this.get("edges").filter(function(_edge) {
       
   247                     return _edge.get("from") == _node || _edge.get("to") == _node;
       
   248                 })
       
   249             );
       
   250         });
       
   251     }
       
   252 });
       
   253 
       
   254 /* Point of entry */
       
   255 
       
   256 Rkns.Renkan = function(_opts) {
       
   257     if (typeof _opts.language !== "string" || typeof Rkns.i18n[_opts.language] == "undefined") {
       
   258         _opts.language = "en";
       
   259     }
       
   260     if (typeof _opts.container !== "string") {
       
   261         _opts.container = "renkan";
       
   262     }
       
   263     if (typeof _opts.search !== "object" || !_opts.search) {
       
   264         _opts.search = [];
       
   265     }
       
   266     this.project = new Rkns.Models.Project();
       
   267     this.l10n = Rkns.i18n[_opts.language];
       
   268     this.$ = Rkns.$("#" + _opts.container);
       
   269     this.$.html(this.template());
       
   270     this.renderer = new Rkns.Renderer.Scene(this);
       
   271 }
       
   272 Rkns.Renkan.prototype.template = Rkns._.template(
       
   273     '<div class="Rk-Render Rk-Render-Full"></div>'
       
   274 );
       
   275 
       
   276 Rkns.jsonImport = function(_renkan, _opts) {
       
   277     var _proj = _renkan.project;
       
   278     if (typeof _opts.http_method == "undefined") {
       
   279         _opts.http_method = 'PUT';
       
   280     }
       
   281     var _load = function() {
       
   282         Rkns.$.getJSON(_opts.url, function(_data) {
       
   283             _proj.set(_data);
       
   284             _renkan.renderer.autoScale();
       
   285         });
       
   286     }
       
   287     _load();
       
   288 }
       
   289 
       
   290 Rkns.Renderer = {
       
   291     _MARGIN_X: 80,
       
   292     _MARGIN_Y: 50,
       
   293     _MIN_DRAG_DISTANCE: 2,
       
   294     _NODE_RADIUS: 15,
       
   295     _NODE_FONT_SIZE: 10,
       
   296     _EDGE_FONT_SIZE: 9,
       
   297     _NODE_MAX_CHAR: 30,
       
   298     _EDGE_MAX_CHAR: 20,
       
   299     _ARROW_LENGTH: 16,
       
   300     _ARROW_WIDTH: 8,
       
   301     _TOOLTIP_ARROW_LENGTH : 15,
       
   302     _TOOLTIP_ARROW_WIDTH : 26,
       
   303     _TOOLTIP_MARGIN : 10,
       
   304     _TOOLTIP_PADDING : 8,
       
   305     _TOOLTIP_GRADIENT : new paper.Gradient(['#f0f0f0', '#d0d0d0'])
       
   306 }
       
   307 
       
   308 Rkns.Renderer.Utils = {
       
   309     shortenText : function(_text,_length) {
       
   310         var _rgxp = new RegExp('^(.{' + _length + '}).+$');
       
   311         return _text.replace(/(\n|\r|\r\n)/mg,' ').replace(_rgxp,'$1…');
       
   312     },
       
   313     drawTooltip : function(_coords, _path, _width, _xmargin, _selector) {
       
   314         _selector.css({
       
   315             width: (_width - 2* Rkns.Renderer._TOOLTIP_PADDING),
       
   316         });
       
   317         var _height = _selector.outerHeight() + 2* Rkns.Renderer._TOOLTIP_PADDING,
       
   318             _isLeft = (_coords.x < paper.view.center.x ? 1 : -1),
       
   319             _left = _coords.x + _isLeft * ( _xmargin + Rkns.Renderer._TOOLTIP_ARROW_LENGTH ),
       
   320             _right = _coords.x + _isLeft * ( _xmargin + Rkns.Renderer._TOOLTIP_ARROW_LENGTH + _width ),
       
   321             _top = _coords.y - _height / 2;
       
   322         if (_top < Rkns.Renderer._TOOLTIP_MARGIN) {
       
   323             _top = Math.min( Rkns.Renderer._TOOLTIP_MARGIN, _coords.y - Rkns.Renderer._TOOLTIP_ARROW_WIDTH / 2 );
       
   324         }
       
   325         var _bottom = _top + _height;
       
   326         if (_bottom > (paper.view.size.height - Rkns.Renderer._TOOLTIP_MARGIN)) {
       
   327             _bottom = Math.max( paper.view.size.height - Rkns.Renderer._TOOLTIP_MARGIN, _coords.y + Rkns.Renderer._TOOLTIP_ARROW_WIDTH / 2 );
       
   328             _top = _bottom - _height;
       
   329         }
       
   330         _path.segments[0].point
       
   331             = _path.segments[7].point
       
   332             = _coords.add([_isLeft * _xmargin, 0]);
       
   333         _path.segments[1].point.x
       
   334             = _path.segments[2].point.x
       
   335             = _path.segments[5].point.x
       
   336             = _path.segments[6].point.x
       
   337             = _left;
       
   338         _path.segments[3].point.x
       
   339             = _path.segments[4].point.x
       
   340             = _right;
       
   341         _path.segments[2].point.y
       
   342             = _path.segments[3].point.y
       
   343             = _top;
       
   344         _path.segments[4].point.y
       
   345             = _path.segments[5].point.y
       
   346             = _bottom;
       
   347         _path.segments[1].point.y = _coords.y - Rkns.Renderer._TOOLTIP_ARROW_WIDTH / 2;
       
   348         _path.segments[6].point.y = _coords.y + Rkns.Renderer._TOOLTIP_ARROW_WIDTH / 2;
       
   349         _path.closed = true;
       
   350         _path.fillColor = new paper.GradientColor(Rkns.Renderer._TOOLTIP_GRADIENT, [0,_top], [0, _bottom]);
       
   351         _selector.css({
       
   352             left: (Rkns.Renderer._TOOLTIP_PADDING + Math.min(_left, _right)),
       
   353             top: (Rkns.Renderer._TOOLTIP_PADDING + _top)
       
   354         });
       
   355     }
       
   356 }
       
   357 
       
   358 Rkns.Renderer._BaseRepresentation = function(_renderer, _model) {
       
   359     if (typeof _renderer !== "undefined") {
       
   360         this.renderer = _renderer;
       
   361         this.project = _renderer.renkan.project;
       
   362         this.model = _model;
       
   363         if (_model) {
       
   364             var _this = this;
       
   365             _model.on("select", function() {
       
   366                 _this.select();
       
   367             });
       
   368             _model.on("unselect", function() {
       
   369                 _this.unselect();
       
   370             });
       
   371         }
       
   372     }
       
   373 }
       
   374 
       
   375 Rkns.Renderer._BaseRepresentation.prototype.select = function() {}
       
   376 
       
   377 Rkns.Renderer._BaseRepresentation.prototype.unselect = function() {}
       
   378 
       
   379 Rkns.Renderer._BaseRepresentation.prototype.highlight = function() {}
       
   380 
       
   381 Rkns.Renderer._BaseRepresentation.prototype.unhighlight = function() {}
       
   382 
       
   383 Rkns.Renderer._BaseRepresentation.prototype.mouseup = function() {}
       
   384 
       
   385 Rkns.Renderer._BaseRepresentation.prototype.destroy = function() {}
       
   386 
       
   387 Rkns.Renderer.Node = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
       
   388 
       
   389 Rkns.Renderer.Node.prototype._init = function() {
       
   390     this.renderer.node_layer.activate();
       
   391     this.type = "Node";
       
   392     this.circle = new paper.Path.Circle([0, 0], Rkns.Renderer._NODE_RADIUS);
       
   393     this.circle.fillColor = '#ffffff';
       
   394     this.circle.__representation = this;
       
   395     this.title = new paper.PointText([0,0]);
       
   396     this.title.characterStyle = {
       
   397         fontSize: Rkns.Renderer._NODE_FONT_SIZE,
       
   398         fillColor: 'black'
       
   399     };
       
   400     this.title.paragraphStyle.justification = 'center';
       
   401     this.title.__representation = this;
       
   402     this.model_coords = new paper.Point(this.model.get("position"));
       
   403 }
       
   404 
       
   405 Rkns.Renderer.Node.prototype.redraw = function() {
       
   406     this.paper_coords = this.renderer.toPaperCoords(this.model_coords);
       
   407     this.circle.position = this.paper_coords;
       
   408     this.title.content = Rkns.Renderer.Utils.shortenText(this.model.get("title"), Rkns.Renderer._NODE_MAX_CHAR);
       
   409     this.title.position = this.paper_coords.add([0, 2 * Rkns.Renderer._NODE_RADIUS]);
       
   410     this.circle.strokeColor = this.model.get("created_by").get("color");
       
   411 }
       
   412 
       
   413 Rkns.Renderer.Node.prototype.paperShift = function(_delta) {
       
   414     this.paper_coords = this.paper_coords.add(_delta);
       
   415     this.model_coords = this.renderer.toModelCoords(this.paper_coords);
       
   416     this.renderer.redraw();
       
   417 }
       
   418 
       
   419 Rkns.Renderer.Node.prototype.openTooltip = function() {
       
   420     this.renderer.removeRepresentationsOfType("tooltip");
       
   421     var _tooltip = this.renderer.addRepresentation("NodeTooltip",null);
       
   422     _tooltip.node_representation = this;
       
   423     _tooltip.redraw();
       
   424 }
       
   425 
       
   426 Rkns.Renderer.Node.prototype.select = function() {
       
   427     this.circle.strokeWidth = 3;
       
   428     this.circle.fillColor = "#ffffc0";
       
   429     paper.view.draw();
       
   430 }
       
   431 
       
   432 Rkns.Renderer.Node.prototype.unselect = function() {
       
   433     this.circle.strokeWidth = 1;
       
   434     this.circle.fillColor = "#ffffff";
       
   435     paper.view.draw();
       
   436 }
       
   437 
       
   438 Rkns.Renderer.Node.prototype.mouseup = function(_event) {
       
   439 }
       
   440 
       
   441 Rkns.Renderer.Node.prototype.destroy = function(_event) {
       
   442     this.circle.remove();
       
   443     this.title.remove();
       
   444 }
       
   445 
       
   446 /* */
       
   447 
       
   448 Rkns.Renderer.Edge = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
       
   449 
       
   450 Rkns.Renderer.Edge.prototype._init = function() {
       
   451     this.renderer.edge_layer.activate();
       
   452     this.type = "Edge";
       
   453     this.from_representation = this.renderer.getRepresentationByModel(this.model.get("from"));
       
   454     this.to_representation = this.renderer.getRepresentationByModel(this.model.get("to"));
       
   455     this.line = new paper.Path();
       
   456     this.line.add([0,0],[0,0]);
       
   457     this.line.__representation = this;
       
   458     this.arrow = new paper.Path();
       
   459     this.arrow.add([0,0],[Rkns.Renderer._ARROW_LENGTH,Rkns.Renderer._ARROW_WIDTH / 2],[0,Rkns.Renderer._ARROW_WIDTH]);
       
   460     this.arrow.__representation = this;
       
   461     this.text = new paper.PointText();
       
   462     this.text.characterStyle = {
       
   463         fontSize: Rkns.Renderer._EDGE_FONT_SIZE,
       
   464         fillColor: 'black'
       
   465     };
       
   466     this.text.paragraphStyle.justification = 'center';
       
   467     this.text.__representation = this;
       
   468     this.text_angle = 0;
       
   469     this.arrow_angle = 0;
       
   470 }
       
   471 
       
   472 Rkns.Renderer.Edge.prototype.redraw = function() {
       
   473     var _p0o = this.from_representation.paper_coords,
       
   474         _p1o = this.to_representation.paper_coords,
       
   475         _v = _p1o.subtract(_p0o),
       
   476         _r = _v.length,
       
   477         _u = _v.divide(_r),
       
   478         _delta = new paper.Point([- _u.y, _u.x]).multiply( 4 ),
       
   479         _p0 = _p0o.add(_delta), /* Adding a 4 px difference */
       
   480         _p1 = _p1o.add(_delta), /* to differentiate inbound and outbound links */
       
   481         _a = _v.angle,
       
   482         _color = this.model.get("created_by").get("color");
       
   483     this.paper_coords = _p0.add(_p1).divide(2);
       
   484     this.line.strokeColor = _color;
       
   485     this.line.segments[0].point = _p0;
       
   486     this.line.segments[1].point = _p1;
       
   487     this.arrow.rotate(_a - this.arrow_angle);
       
   488     this.arrow.fillColor = _color;
       
   489     this.arrow.position = this.paper_coords.subtract(_u.multiply(4));
       
   490     this.arrow_angle = _a;
       
   491     if (_a > 90) {
       
   492         _a -= 180;
       
   493     }
       
   494     if (_a < -90) {
       
   495         _a += 180;
       
   496     }
       
   497     this.text.rotate(_a - this.text_angle);
       
   498     this.text.content = Rkns.Renderer.Utils.shortenText(this.model.get("title"), Rkns.Renderer._EDGE_MAX_CHAR);
       
   499     this.text.position = this.paper_coords;
       
   500     this.text_angle = _a;
       
   501 }
       
   502 
       
   503 Rkns.Renderer.Edge.prototype.openTooltip = function() {
       
   504     this.renderer.removeRepresentationsOfType("tooltip");
       
   505     var _tooltip = this.renderer.addRepresentation("EdgeTooltip",null);
       
   506     _tooltip.edge_representation = this;
       
   507     _tooltip.redraw();
       
   508 }
       
   509 
       
   510 Rkns.Renderer.Edge.prototype.select = function() {
       
   511     this.line.strokeWidth = 3;
       
   512     this.openTooltip();
       
   513     paper.view.draw();
       
   514 }
       
   515 
       
   516 Rkns.Renderer.Edge.prototype.unselect = function() {
       
   517     this.line.strokeWidth = 1;
       
   518     paper.view.draw();
       
   519 }
       
   520 
       
   521 Rkns.Renderer.Edge.prototype.mouseup = function(_event) {
       
   522 }
       
   523 
       
   524 Rkns.Renderer.Edge.prototype.paperShift = function(_delta) {
       
   525     this.from_representation.paperShift(_delta);
       
   526     this.to_representation.paperShift(_delta);
       
   527     this.renderer.redraw();
       
   528 }
       
   529 
       
   530 Rkns.Renderer.Edge.prototype.destroy = function() {
       
   531     this.line.remove();
       
   532     this.arrow.remove();
       
   533     this.text.remove();
       
   534 }
       
   535 
       
   536 /* */
       
   537 
       
   538 Rkns.Renderer.NodeTooltip = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
       
   539 
       
   540 Rkns.Renderer.NodeTooltip.prototype._init = function() {
       
   541     this.renderer.overlay_layer.activate();
       
   542     this.type = "tooltip";
       
   543     this.tooltip_block = new paper.Path();
       
   544     var _pts = Rkns._(Rkns._.range(8)).map(function() {return [0,0]});
       
   545     this.tooltip_block.add.apply(this.tooltip_block, _pts);
       
   546     this.tooltip_block.strokeWidth = 2;
       
   547     this.tooltip_block.strokeColor = "#999999";
       
   548     this.tooltip_block.fillColor = "#e0e0e0";
       
   549     this.tooltip_block.opacity = .8;
       
   550     this.tooltip_$ = Rkns.$('<div>')
       
   551         .appendTo(this.renderer.tooltip_$)
       
   552         .css({
       
   553             position: "absolute",
       
   554             opacity: .8
       
   555         })
       
   556         .hide();
       
   557 }
       
   558 
       
   559 Rkns.Renderer.NodeTooltip.prototype.template = Rkns._.template(
       
   560     '<h2><span class="Rk-CloseX">&times;</span><%=a%></h2>'
       
   561     + '<p><%=description%></p>'
       
   562 );
       
   563 
       
   564 Rkns.Renderer.NodeTooltip.prototype.redraw = function() {
       
   565     var _coords = this.node_representation.paper_coords,
       
   566         _model = this.node_representation.model,
       
   567         _title = _model.get("title"),
       
   568         _uri = _model.get("uri");
       
   569     this.tooltip_$
       
   570         .html(this.template({
       
   571             a: (_uri ? '<a href="' + _uri + '" target="_blank">' : '' ) + _title + (_uri ? '</a>' : '' ),
       
   572             description: _model.get("description").replace(/(\n|\r|\r\n)/mg,' ').substr(0,180).replace(/(^.{150,179})[\s].+$/m,'$1&hellip;')
       
   573         }))
       
   574         .show();
       
   575     Rkns.Renderer.Utils.drawTooltip(_coords, this.tooltip_block, 250, 15, this.tooltip_$);
       
   576     var _this = this;
       
   577     this.tooltip_$.find(".Rk-CloseX").click(function() {
       
   578         _this.renderer.removeRepresentation(_this);
       
   579         paper.view.draw();
       
   580     });
       
   581     this.tooltip_$.find("input, textarea").bind("keyup change", function() {
       
   582         _this.tooltip_$.find(".Rk-Edit-Goto").attr("href",_this.tooltip_$.find(".Rk-Edit-URI").val());
       
   583     });
       
   584     paper.view.draw();
       
   585 }
       
   586 
       
   587 Rkns.Renderer.NodeTooltip.prototype.destroy = function() {
       
   588     this.tooltip_block.remove();
       
   589     this.tooltip_$.detach();
       
   590 }
       
   591 
       
   592 /* */
       
   593 
       
   594 Rkns.Renderer.EdgeTooltip = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
       
   595 
       
   596 Rkns.Renderer.EdgeTooltip.prototype._init = function() {
       
   597     this.renderer.overlay_layer.activate();
       
   598     this.type = "tooltip";
       
   599     this.tooltip_block = new paper.Path();
       
   600     var _pts = Rkns._(Rkns._.range(8)).map(function() {return [0,0]});
       
   601     this.tooltip_block.add.apply(this.tooltip_block, _pts);
       
   602     this.tooltip_block.strokeWidth = 2;
       
   603     this.tooltip_block.strokeColor = "#999999";
       
   604     this.tooltip_block.fillColor = "#e0e0e0";
       
   605     this.tooltip_block.opacity = .8;
       
   606     this.tooltip_$ = Rkns.$('<div>')
       
   607         .appendTo(this.renderer.tooltip_$)
       
   608         .css({
       
   609             position: "absolute",
       
   610             opacity: .8
       
   611         })
       
   612         .hide();
       
   613 }
       
   614 
       
   615 Rkns.Renderer.EdgeTooltip.prototype.template = Rkns._.template(
       
   616     '<h2><span class="Rk-CloseX">&times;</span><%=a%></h2>'
       
   617     + '<p><%=description%></p>'
       
   618 );
       
   619 
       
   620 Rkns.Renderer.EdgeTooltip.prototype.redraw = function() {
       
   621     var _coords = this.edge_representation.paper_coords,
       
   622         _model = this.edge_representation.model,
       
   623         _title = _model.get("title"),
       
   624         _uri = _model.get("uri");
       
   625     this.tooltip_$
       
   626         .html(this.template({
       
   627             a: (_uri ? '<a href="' + _uri + '" target="_blank">' : '' ) + _title + (_uri ? '</a>' : '' ),
       
   628             description: _model.get("description").replace(/(\n|\r|\r\n)/mg,' ').substr(0,180).replace(/(^.{150,179})[\s].+$/m,'$1&hellip;')
       
   629         }))
       
   630         .show();
       
   631     Rkns.Renderer.Utils.drawTooltip(_coords, this.tooltip_block, 250, 5, this.tooltip_$);
       
   632     var _this = this;
       
   633     this.tooltip_$.find(".Rk-CloseX").click(function() {
       
   634         _this.renderer.removeRepresentation(_this);
       
   635         paper.view.draw();
       
   636     });
       
   637     paper.view.draw();
       
   638 }
       
   639 
       
   640 Rkns.Renderer.EdgeTooltip.prototype.destroy = function() {
       
   641     this.tooltip_block.remove();
       
   642     this.tooltip_$.detach();
       
   643 }
       
   644 
       
   645 /* */
       
   646 
       
   647 Rkns.Renderer.Scene = function(_renkan) {
       
   648     this.renkan = _renkan;
       
   649     this.$ = Rkns.$(".Rk-Render");
       
   650     this.representations = [];
       
   651     this.$.html(this.template({
       
   652         width: this.$.width(),
       
   653         height: this.$.height(),
       
   654         l10n: _renkan.l10n
       
   655     }))
       
   656     this.canvas_$ = this.$.find(".Rk-Canvas");
       
   657     this.tooltip_$ = this.$.find(".Rk-Editor");
       
   658     paper.setup(this.canvas_$[0]);
       
   659     this.scale = 1;
       
   660     this.offset = paper.view.center;
       
   661     this.totalScroll = 0;
       
   662     this.click_target = null;
       
   663     this.selected_target = null;
       
   664     this.edge_layer = new paper.Layer();
       
   665     this.node_layer = new paper.Layer();
       
   666     this.overlay_layer = new paper.Layer();
       
   667     var _tool = new paper.Tool(),
       
   668         _this = this;
       
   669     _tool.minDistance = Rkns.Renderer._MIN_DRAG_DISTANCE;
       
   670     _tool.onMouseMove = function(_event) {
       
   671         _this.onMouseMove(_event);
       
   672     }
       
   673     _tool.onMouseDown = function(_event) {
       
   674         _this.onMouseDown(_event);
       
   675     }
       
   676     _tool.onMouseDrag = function(_event) {
       
   677         _this.onMouseDrag(_event);
       
   678     }
       
   679     this.canvas_$.mouseup(function(_event) {
       
   680         _this.onMouseUp(_event);
       
   681     });
       
   682     this.canvas_$.mousewheel(function(_event, _delta) {
       
   683         _this.onScroll(_event, _delta);
       
   684     });
       
   685     this.tooltip_$.find(".Rk-ZoomOut").click(function() {
       
   686         _this.offset = new paper.Point([
       
   687             _this.canvas_$.width(),
       
   688             _this.canvas_$.height()
       
   689         ]).multiply( .5 * ( 1 - Math.SQRT1_2 ) ).add(_this.offset.multiply( Math.SQRT1_2 ));
       
   690         _this.scale *= Math.SQRT1_2;
       
   691         _this.redraw();
       
   692     });
       
   693     this.tooltip_$.find(".Rk-ZoomIn").click(function() {
       
   694         _this.offset = new paper.Point([
       
   695             _this.canvas_$.width(),
       
   696             _this.canvas_$.height()
       
   697         ]).multiply( .5 * ( 1 - Math.SQRT2 ) ).add(_this.offset.multiply( Math.SQRT2 ));
       
   698         _this.scale *= Math.SQRT2;
       
   699         _this.redraw();
       
   700     });
       
   701     paper.view.onResize = function(_event) {
       
   702         _this.offset = _this.offset.add(_event.delta.divide(2));
       
   703         _this.redraw();
       
   704     }
       
   705     
       
   706     var _thRedraw = Rkns._.throttle(function() {
       
   707         _this.redraw();
       
   708     },50);
       
   709     
       
   710     this.addRepresentations("Node", this.renkan.project.get("nodes"));
       
   711     this.addRepresentations("Edge", this.renkan.project.get("edges"));
       
   712     
       
   713     this.renkan.project.on("add:nodes", function(_node) {
       
   714         _this.addRepresentation("Node", _node);
       
   715         _thRedraw();
       
   716     });
       
   717     this.renkan.project.on("add:edges", function(_edge) {
       
   718         _this.addRepresentation("Edge", _edge);
       
   719         _thRedraw();
       
   720     });
       
   721     
       
   722     this.redraw();
       
   723 }
       
   724 
       
   725 Rkns.Renderer.Scene.prototype.template = Rkns._.template(
       
   726     '<canvas class="Rk-Canvas" width="<%=width%>" height="<%=height%>"></canvas><div class="Rk-Editor">'
       
   727     + '<div class="Rk-ZoomButtons"><div class="Rk-ZoomIn" title="<%=l10n.zoom_in%>"></div><div class="Rk-ZoomOut" title="<%=l10n.zoom_out%>"></div></div>'
       
   728     + '</div>'
       
   729 );
       
   730 
       
   731 Rkns.Renderer.Scene.prototype.autoScale = function() {
       
   732     if (this.renkan.project.get("nodes").length) {
       
   733         var _xx = this.renkan.project.get("nodes").map(function(_node) { return _node.get("position").x }),
       
   734             _yy = this.renkan.project.get("nodes").map(function(_node) { return _node.get("position").y }),
       
   735             _minx = Math.min.apply(Math, _xx),
       
   736             _miny = Math.min.apply(Math, _yy),
       
   737             _maxx = Math.max.apply(Math, _xx),
       
   738             _maxy = Math.max.apply(Math, _yy);
       
   739         this.scale = Math.min((paper.view.size.width - 2 * Rkns.Renderer._MARGIN_X) / (_maxx - _minx), (paper.view.size.height - 2 * Rkns.Renderer._MARGIN_Y) / (_maxy - _miny));
       
   740         this.offset = paper.view.center.subtract(new paper.Point([(_maxx + _minx) / 2, (_maxy + _miny) / 2]).multiply(this.scale));
       
   741         this.redraw();
       
   742     }
       
   743 }
       
   744 
       
   745 Rkns.Renderer.Scene.prototype.toPaperCoords = function(_point) {
       
   746     return _point.multiply(this.scale).add(this.offset);
       
   747 }
       
   748 
       
   749 
       
   750 Rkns.Renderer.Scene.prototype.toModelCoords = function(_point) {
       
   751     return _point.subtract(this.offset).divide(this.scale);
       
   752 }
       
   753 
       
   754 Rkns.Renderer.Scene.prototype.addRepresentation = function(_type, _model) {
       
   755     var _repr = new Rkns.Renderer[_type](this, _model);
       
   756     this.representations.push(_repr);
       
   757     if (_model) {
       
   758         var _this = this;
       
   759         _model.on("change", function() {
       
   760             _repr.redraw();
       
   761         });
       
   762         _model.on("remove", function() {
       
   763             _this.removeRepresentation(_repr);
       
   764             _this.redraw();
       
   765         });
       
   766     }
       
   767     return _repr;
       
   768 }
       
   769 
       
   770 Rkns.Renderer.Scene.prototype.addRepresentations = function(_type, _collection) {
       
   771     var _this = this;
       
   772     _collection.forEach(function(_model) {
       
   773         _this.addRepresentation(_type, _model);
       
   774     });
       
   775 }
       
   776 
       
   777 Rkns.Renderer.Scene.prototype.removeRepresentation = function(_representation) {
       
   778     _representation.destroy();
       
   779     this.representations = Rkns._(this.representations).reject(
       
   780         function(_repr) {
       
   781             return _repr == _representation
       
   782         }
       
   783     );
       
   784 }
       
   785 
       
   786 Rkns.Renderer.Scene.prototype.getRepresentationByModel = function(_model) {
       
   787     return Rkns._(this.representations).find(function(_repr) {
       
   788         return _repr.model === _model;
       
   789     });
       
   790 }
       
   791 
       
   792 Rkns.Renderer.Scene.prototype.removeRepresentationsOfType = function(_type) {
       
   793     var _representations = Rkns._(this.representations).filter(function(_repr) {
       
   794             return _repr.type == _type;
       
   795         }),
       
   796         _this = this;
       
   797     Rkns._(_representations).each(function(_repr) {
       
   798         _this.removeRepresentation(_repr);
       
   799     });
       
   800 }
       
   801 
       
   802 Rkns.Renderer.Scene.prototype.unselectAll = function() {
       
   803     Rkns._(this.representations).each(function(_repr) {
       
   804         _repr.model.trigger("unselect");
       
   805     });
       
   806 }
       
   807 
       
   808 Rkns.Renderer.Scene.prototype.redraw = function() {
       
   809     Rkns._(this.representations).each(function(_representation) {
       
   810         _representation.redraw();
       
   811     });
       
   812     paper.view.draw();
       
   813 }
       
   814 
       
   815 Rkns.Renderer.Scene.prototype.addTempEdge = function(_from, _point) {
       
   816     var _tmpEdge = this.addRepresentation("TempEdge",null);
       
   817     _tmpEdge.end_pos = _point;
       
   818     _tmpEdge.from_representation = _from;
       
   819     _tmpEdge.redraw();
       
   820     this.click_target = _tmpEdge;
       
   821 }
       
   822 
       
   823 Rkns.Renderer.Scene.prototype.findTarget = function(_hitResult) {
       
   824     if (_hitResult && typeof _hitResult.item.__representation !== "undefined") {
       
   825         var _newTarget = _hitResult.item.__representation;
       
   826         if (this.selected_target !== _newTarget && _newTarget.model) {
       
   827             if (this.selected_target) {
       
   828                 this.selected_target.model.trigger("unselect");
       
   829             }
       
   830             _newTarget.model.trigger("select");
       
   831             if (typeof _newTarget.openTooltip === "function") {
       
   832                 _newTarget.openTooltip();
       
   833             }
       
   834             this.selected_target = _newTarget;
       
   835         }
       
   836     } else {
       
   837         if (!_hitResult) {
       
   838             this.removeRepresentationsOfType("tooltip");
       
   839         }
       
   840         if (this.selected_target) {
       
   841             this.selected_target.model.trigger("unselect");
       
   842         }
       
   843         this.selected_target = null;
       
   844     }
       
   845 }
       
   846 
       
   847 Rkns.Renderer.Scene.prototype.onMouseMove = function(_event) {
       
   848     var _hitResult = paper.project.hitTest(_event.point);
       
   849     if (this.is_dragging) {
       
   850         if (this.click_target && typeof this.click_target.paperShift === "function") {
       
   851             this.click_target.paperShift(_event.delta);
       
   852         } else {
       
   853             this.offset = this.offset.add(_event.delta);
       
   854             this.redraw();
       
   855         }
       
   856     } else {
       
   857         this.findTarget(_hitResult);
       
   858     }
       
   859 }
       
   860 
       
   861 Rkns.Renderer.Scene.prototype.onMouseDown = function(_event) {
       
   862     this.is_dragging = false;
       
   863     var _hitResult = paper.project.hitTest(_event.point);
       
   864     if (_hitResult && typeof _hitResult.item.__representation !== "undefined") {
       
   865         this.click_target = _hitResult.item.__representation;
       
   866     } else {
       
   867         this.click_target = null;
       
   868     }
       
   869 }
       
   870 
       
   871 Rkns.Renderer.Scene.prototype.onMouseDrag = function(_event) {
       
   872     this.is_dragging = true;
       
   873     this.onMouseMove(_event);
       
   874 }
       
   875 
       
   876 Rkns.Renderer.Scene.prototype.onMouseUp = function(_event) {
       
   877     if (this.click_target) {
       
   878         var _off = this.canvas_$.offset();
       
   879         if (this.click_target.model) {
       
   880             this.click_target.model.trigger("click");
       
   881         }
       
   882     }
       
   883     this.is_dragging = false;
       
   884     this.click_target = null;
       
   885 }
       
   886 
       
   887 Rkns.Renderer.Scene.prototype.onScroll = function(_event, _scrolldelta) {
       
   888     this.totalScroll += _scrolldelta;
       
   889     if (Math.abs(this.totalScroll) >= 1) {
       
   890         var _off = this.canvas_$.offset(),
       
   891             _delta = new paper.Point([
       
   892                 _event.pageX - _off.left,
       
   893                 _event.pageY - _off.top
       
   894             ]).subtract(this.offset).multiply( Math.SQRT2 - 1 );
       
   895         if (this.totalScroll > 0) {
       
   896             this.offset = this.offset.subtract(_delta);
       
   897             this.scale *= Math.SQRT2;
       
   898         } else {
       
   899             this.offset = this.offset.add(_delta.divide( Math.SQRT2 ));
       
   900             this.scale *= Math.SQRT1_2;
       
   901         }
       
   902         this.totalScroll = 0;
       
   903         this.redraw();
       
   904     }
       
   905 }