client/js/renderer/scene.js
changeset 284 fa8035885814
child 286 3ab8c63d26db
equal deleted inserted replaced
283:67f3a24a7c01 284:fa8035885814
       
     1 "use strict";
       
     2 
       
     3 define(['jquery', 'underscore', 'requtils', 'renderer/miniframe'], function ($, _, requtils, MiniFrame) {
       
     4     
       
     5     var Utils = requtils.getUtils();
       
     6     
       
     7     /* Scene Begin */
       
     8 
       
     9     var Scene = function(_renkan) {
       
    10         this.renkan = _renkan;
       
    11         this.$ = $(".Rk-Render");
       
    12         this.representations = [];
       
    13         this.$.html(this.template(_renkan));
       
    14         this.onStatusChange();
       
    15         this.canvas_$ = this.$.find(".Rk-Canvas");
       
    16         this.labels_$ = this.$.find(".Rk-Labels");
       
    17         this.editor_$ = this.$.find(".Rk-Editor");
       
    18         this.notif_$ = this.$.find(".Rk-Notifications");
       
    19         paper.setup(this.canvas_$[0]);
       
    20         this.scale = 1;
       
    21         this.initialScale = 1;
       
    22         this.offset = paper.view.center;
       
    23         this.totalScroll = 0;
       
    24         this.mouse_down = false;
       
    25         this.click_target = null;
       
    26         this.selected_target = null;
       
    27         this.edge_layer = new paper.Layer();
       
    28         this.node_layer = new paper.Layer();
       
    29         this.buttons_layer = new paper.Layer();
       
    30         this.delete_list = [];
       
    31         
       
    32         if (_renkan.options.show_minimap) {
       
    33             this.minimap = {
       
    34                     background_layer: new paper.Layer(),
       
    35                     edge_layer: new paper.Layer(),
       
    36                     node_layer: new paper.Layer(),
       
    37                     node_group: new paper.Group(),
       
    38                     size: new paper.Size( _renkan.options.minimap_width, _renkan.options.minimap_height )
       
    39             };
       
    40 
       
    41             this.minimap.background_layer.activate();
       
    42             this.minimap.topleft = paper.view.bounds.bottomRight.subtract(this.minimap.size);
       
    43             this.minimap.rectangle = new paper.Path.Rectangle(this.minimap.topleft.subtract([2,2]), this.minimap.size.add([4,4]));
       
    44             this.minimap.rectangle.fillColor = _renkan.options.minimap_background_color;
       
    45             this.minimap.rectangle.strokeColor = _renkan.options.minimap_border_color;
       
    46             this.minimap.rectangle.strokeWidth = 4;
       
    47             this.minimap.offset = new paper.Point(this.minimap.size.divide(2));
       
    48             this.minimap.scale = .1;
       
    49 
       
    50             this.minimap.node_layer.activate();
       
    51             this.minimap.cliprectangle = new paper.Path.Rectangle(this.minimap.topleft, this.minimap.size);
       
    52             this.minimap.node_group.addChild(this.minimap.cliprectangle);
       
    53             this.minimap.node_group.clipped = true;
       
    54             this.minimap.miniframe = new paper.Path.Rectangle(this.minimap.topleft, this.minimap.size);
       
    55             this.minimap.node_group.addChild(this.minimap.miniframe);
       
    56             this.minimap.miniframe.fillColor = '#c0c0ff';
       
    57             this.minimap.miniframe.opacity = .3;
       
    58             this.minimap.miniframe.strokeColor = '#000080';
       
    59             this.minimap.miniframe.strokeWidth = 3;
       
    60             this.minimap.miniframe.__representation = new MiniFrame(this, null);
       
    61         }
       
    62 
       
    63         this.throttledPaperDraw = _(function() {
       
    64             paper.view.draw();
       
    65         }).throttle(100);
       
    66 
       
    67         this.bundles = [];
       
    68         this.click_mode = false;
       
    69 
       
    70         var _this = this,
       
    71         _allowScroll = true,
       
    72         _originalScale = 1,
       
    73         _zooming = false,
       
    74         _lastTapX = 0,
       
    75         _lastTapY = 0;
       
    76 
       
    77         this.image_cache = {};
       
    78         this.icon_cache = {};
       
    79 
       
    80         ['edit', 'remove', 'link', 'enlarge', 'shrink', 'revert' ].forEach(function(imgname) {
       
    81             var img = new Image();
       
    82             img.src = _renkan.options.static_url + 'img/' + imgname + '.png';
       
    83             _this.icon_cache[imgname] = img;
       
    84         });
       
    85         
       
    86         var throttledMouseMove = _.throttle(function(_event, _isTouch) {
       
    87             _this.onMouseMove(_event, _isTouch);
       
    88         }, Utils._MOUSEMOVE_RATE);
       
    89 
       
    90         this.canvas_$.on({
       
    91             mousedown: function(_event) {
       
    92                 _event.preventDefault();
       
    93                 _this.onMouseDown(_event, false);
       
    94             },
       
    95             mousemove: function(_event) {
       
    96                 _event.preventDefault();
       
    97                 throttledMouseMove(_event, false);
       
    98             },
       
    99             mouseup: function(_event) {
       
   100                 _event.preventDefault();
       
   101                 _this.onMouseUp(_event, false);
       
   102             },
       
   103             mousewheel: function(_event, _delta) {
       
   104                 if(_renkan.options.zoom_on_scroll) {
       
   105                     _event.preventDefault();
       
   106                     if (_allowScroll) {
       
   107                         _this.onScroll(_event, _delta);
       
   108                     }
       
   109                 }
       
   110             },
       
   111             touchstart: function(_event) {
       
   112                 _event.preventDefault();
       
   113                 var _touches = _event.originalEvent.touches[0];
       
   114                 if (
       
   115                         _renkan.options.allow_double_click
       
   116                         && new Date() - _lastTap < Utils._DOUBLETAP_DELAY
       
   117                         && ( Math.pow(_lastTapX - _touches.pageX, 2) + Math.pow(_lastTapY - _touches.pageY, 2) < Utils._DOUBLETAP_DISTANCE )
       
   118                 ) {
       
   119                     _lastTap = 0;
       
   120                     _this.onDoubleClick(_touches);
       
   121                 } else {
       
   122                     _lastTap = new Date();
       
   123                     _lastTapX = _touches.pageX;
       
   124                     _lastTapY = _touches.pageY;
       
   125                     _originalScale = _this.scale;
       
   126                     _zooming = false;
       
   127                     _this.onMouseDown(_touches, true);
       
   128                 }
       
   129             },
       
   130             touchmove: function(_event) {
       
   131                 _event.preventDefault();
       
   132                 _lastTap = 0;
       
   133                 if (_event.originalEvent.touches.length == 1) {
       
   134                     _this.onMouseMove(_event.originalEvent.touches[0], true);
       
   135                 } else {
       
   136                     if (!_zooming) {
       
   137                         _this.onMouseUp(_event.originalEvent.touches[0], true);
       
   138                         _this.click_target = null;
       
   139                         _this.is_dragging = false;
       
   140                         _zooming = true;
       
   141                     }
       
   142                     if (_event.originalEvent.scale === "undefined") {
       
   143                         return;
       
   144                     }
       
   145                     var _newScale = _event.originalEvent.scale * _originalScale,
       
   146                     _scaleRatio = _newScale / _this.scale,
       
   147                     _newOffset = new paper.Point([
       
   148                                                   _this.canvas_$.width(),
       
   149                                                   _this.canvas_$.height()
       
   150                                                   ]).multiply( .5 * ( 1 - _scaleRatio ) ).add(_this.offset.multiply( _scaleRatio ));
       
   151                     _this.setScale(_newScale, _newOffset);
       
   152                 }
       
   153             },
       
   154             touchend: function(_event) {
       
   155                 _event.preventDefault();
       
   156                 _this.onMouseUp(_event.originalEvent.changedTouches[0], true);
       
   157             },
       
   158             dblclick: function(_event) {
       
   159                 _event.preventDefault();
       
   160                 if (_renkan.options.allow_double_click) {
       
   161                     _this.onDoubleClick(_event);
       
   162                 }
       
   163             },
       
   164             mouseleave: function(_event) {
       
   165                 _event.preventDefault();
       
   166                 _this.onMouseUp(_event, false);
       
   167                 _this.click_target = null;
       
   168                 _this.is_dragging = false;
       
   169             },
       
   170             dragover: function(_event) {
       
   171                 _event.preventDefault();
       
   172             },
       
   173             dragenter: function(_event) {
       
   174                 _event.preventDefault();
       
   175                 _allowScroll = false;
       
   176             },
       
   177             dragleave: function(_event) {
       
   178                 _event.preventDefault();
       
   179                 _allowScroll = true;
       
   180             },
       
   181             drop: function(_event) {
       
   182                 _event.preventDefault();
       
   183                 _allowScroll = true;
       
   184                 var res = {};
       
   185                 _(_event.originalEvent.dataTransfer.types).each(function(t) {
       
   186                     try {
       
   187                         res[t] = _event.originalEvent.dataTransfer.getData(t);
       
   188                     } catch(e) {}
       
   189                 });
       
   190                 var text = _event.originalEvent.dataTransfer.getData("Text");
       
   191                 if (typeof text === "string") {
       
   192                     switch(text[0]) {
       
   193                     case "{":
       
   194                     case "[":
       
   195                         try {
       
   196                             var data = JSON.parse(text);
       
   197                             _(res).extend(data);
       
   198                         }
       
   199                         catch(e) {
       
   200                             if (!res["text/plain"]) {
       
   201                                 res["text/plain"] = text;
       
   202                             }
       
   203                         }
       
   204                         break;
       
   205                     case "<":
       
   206                         if (!res["text/html"]) {
       
   207                             res["text/html"] = text;
       
   208                         }
       
   209                         break;
       
   210                     default:
       
   211                         if (!res["text/plain"]) {
       
   212                             res["text/plain"] = text;
       
   213                         }
       
   214                     }
       
   215                 }
       
   216                 var url = _event.originalEvent.dataTransfer.getData("URL");
       
   217                 if (url && !res["text/uri-list"]) {
       
   218                     res["text/uri-list"] = url;
       
   219                 }
       
   220                 _this.dropData(res, _event.originalEvent);
       
   221             }
       
   222         });
       
   223 
       
   224         var bindClick = function(selector, fname) {
       
   225             _this.$.find(selector).click(function(evt) {
       
   226                 _this[fname](evt);
       
   227                 return false;
       
   228             });
       
   229         };
       
   230 
       
   231         bindClick(".Rk-ZoomOut", "zoomOut");
       
   232         bindClick(".Rk-ZoomIn", "zoomIn");
       
   233         bindClick(".Rk-ZoomFit", "autoScale");
       
   234         this.$.find(".Rk-ZoomSave").click( function() {
       
   235             // Save scale and offset point
       
   236             _this.renkan.project.addView( { zoom_level:_this.scale, offset_x:_this.offset.x, offset_y:_this.offset.y } );
       
   237         });
       
   238         this.$.find(".Rk-ZoomSetSaved").click( function() {
       
   239             var view = _this.renkan.project.get("views").last();
       
   240             if(view){
       
   241                 _this.setScale(view.get("zoom_level"), new paper.Point(view.get("offset_x"), view.get("offset_y")));
       
   242             }
       
   243         });
       
   244         if(this.renkan.read_only && !isNaN(parseInt(this.renkan.options.default_view))){
       
   245             this.$.find(".Rk-ZoomSetSaved").show();
       
   246         }
       
   247         this.$.find(".Rk-CurrentUser").mouseenter(
       
   248                 function() { _this.$.find(".Rk-UserList").slideDown(); }
       
   249         );
       
   250         this.$.find(".Rk-Users").mouseleave(
       
   251                 function() { _this.$.find(".Rk-UserList").slideUp(); }
       
   252         );
       
   253         bindClick(".Rk-FullScreen-Button", "fullScreen");
       
   254         bindClick(".Rk-AddNode-Button", "addNodeBtn");
       
   255         bindClick(".Rk-AddEdge-Button", "addEdgeBtn");
       
   256         bindClick(".Rk-Save-Button", "save");
       
   257         bindClick(".Rk-Open-Button", "open");
       
   258         this.$.find(".Rk-Bookmarklet-Button")
       
   259         .attr("href","javascript:" + Utils._BOOKMARKLET_CODE(_renkan))
       
   260         .click(function(){
       
   261             _this.notif_$
       
   262             .text(_renkan.translate("Drag this button to your bookmark bar. When on a third-party website, click it to enable drag-and-drop from the website to Renkan."))
       
   263             .fadeIn()
       
   264             .delay(5000)
       
   265             .fadeOut();
       
   266             return false;
       
   267         });
       
   268         this.$.find(".Rk-TopBar-Button").mouseover(function() {
       
   269             $(this).find(".Rk-TopBar-Tooltip").show();
       
   270         }).mouseout(function() {
       
   271             $(this).find(".Rk-TopBar-Tooltip").hide();
       
   272         });
       
   273         bindClick(".Rk-Fold-Bins", "foldBins");
       
   274 
       
   275         paper.view.onResize = function(_event) {
       
   276             // Because of paper bug which does not calculate the good height (and width a fortiori)
       
   277             // We have to update manually the canvas's height
       
   278             paper.view._viewSize.height =  _event.size.height = _this.canvas_$.parent().height();
       
   279 
       
   280             if (_this.minimap) {
       
   281                 _this.minimap.topleft = paper.view.bounds.bottomRight.subtract(_this.minimap.size);
       
   282                 _this.minimap.rectangle.fitBounds(_this.minimap.topleft.subtract([2,2]), _this.minimap.size.add([4,4]));
       
   283                 _this.minimap.cliprectangle.fitBounds(_this.minimap.topleft, _this.minimap.size);
       
   284             }
       
   285             _this.redraw();
       
   286         };
       
   287 
       
   288         var _thRedraw = _.throttle(function() {
       
   289             _this.redraw();
       
   290         },50);
       
   291 
       
   292         this.addRepresentations("Node", this.renkan.project.get("nodes"));
       
   293         this.addRepresentations("Edge", this.renkan.project.get("edges"));
       
   294         this.renkan.project.on("change:title", function() {
       
   295             _this.$.find(".Rk-PadTitle").val(_renkan.project.get("title"));
       
   296         });
       
   297 
       
   298         this.$.find(".Rk-PadTitle").on("keyup input paste", function() {
       
   299             _renkan.project.set({"title": $(this).val()});
       
   300         });
       
   301 
       
   302         var _thRedrawUsers = _.throttle(function() {
       
   303             _this.redrawUsers();
       
   304         }, 100);
       
   305 
       
   306         _thRedrawUsers();
       
   307 
       
   308         // register model events
       
   309         this.renkan.project.on("add:users remove:users", _thRedrawUsers);
       
   310 
       
   311         this.renkan.project.on("add:views remove:views", function(_node) {
       
   312             if(_this.renkan.project.get('views').length > 0) {
       
   313                 _this.$.find(".Rk-ZoomSetSaved").show();
       
   314             }
       
   315             else {
       
   316                 _this.$.find(".Rk-ZoomSetSaved").hide();
       
   317             }
       
   318         });
       
   319 
       
   320         this.renkan.project.on("add:nodes", function(_node) {
       
   321             _this.addRepresentation("Node", _node);
       
   322             _thRedraw();
       
   323         });
       
   324         this.renkan.project.on("add:edges", function(_edge) {
       
   325             _this.addRepresentation("Edge", _edge);
       
   326             _thRedraw();
       
   327         });
       
   328         this.renkan.project.on("change:title", function(_model, _title) {
       
   329             var el = _this.$.find(".Rk-PadTitle");
       
   330             if (el.is("input")) {
       
   331                 if (el.val() !== _title) {
       
   332                     el.val(_title);
       
   333                 }
       
   334             } else {
       
   335                 el.text(_title);
       
   336             }
       
   337         });
       
   338 
       
   339         if (_renkan.options.size_bug_fix) {
       
   340             var _delay = (
       
   341                     typeof _renkan.options.size_bug_fix === "number"
       
   342                         ? _renkan.options.size_bug_fix
       
   343                                 : 500
       
   344             );
       
   345             window.setTimeout(
       
   346                     function() {
       
   347                         _this.fixSize(true);
       
   348                     },
       
   349                     _delay
       
   350             );
       
   351         }
       
   352 
       
   353         if (_renkan.options.force_resize) {
       
   354             $(window).resize(function() {
       
   355                 _this.fixSize(false);
       
   356             });
       
   357         }
       
   358 
       
   359         if (_renkan.options.show_user_list && _renkan.options.user_color_editable) {
       
   360             var $cpwrapper = this.$.find(".Rk-Users .Rk-Edit-ColorPicker-Wrapper"),
       
   361             $cplist = this.$.find(".Rk-Users .Rk-Edit-ColorPicker");
       
   362 
       
   363             $cpwrapper.hover(
       
   364                     function(_e) {
       
   365                         if (_this.isEditable()) {
       
   366                             _e.preventDefault();
       
   367                             $cplist.show();
       
   368                         }
       
   369                     },
       
   370                     function(_e) {
       
   371                         _e.preventDefault();
       
   372                         $cplist.hide();
       
   373                     }
       
   374             );
       
   375 
       
   376             $cplist.find("li").mouseenter(
       
   377                     function(_e) {
       
   378                         if (_this.isEditable()) {
       
   379                             _e.preventDefault();
       
   380                             _this.$.find(".Rk-CurrentUser-Color").css("background", $(this).attr("data-color"));
       
   381                         }
       
   382                     }
       
   383             );
       
   384         }
       
   385 
       
   386         if (_renkan.options.show_search_field) {
       
   387 
       
   388             var lastval = '';
       
   389 
       
   390             this.$.find(".Rk-GraphSearch-Field").on("keyup change paste input", function() {
       
   391                 var $this = $(this),
       
   392                 val = $this.val();
       
   393                 if (val === lastval) {
       
   394                     return;
       
   395                 }
       
   396                 lastval = val;
       
   397                 if (val.length < 2) {
       
   398                     _renkan.project.get("nodes").each(function(n) {
       
   399                         _this.getRepresentationByModel(n).unhighlight();
       
   400                     });
       
   401                 } else {
       
   402                     var rxs = Utils.regexpFromTextOrArray(val);
       
   403                     _renkan.project.get("nodes").each(function(n) {
       
   404                         if (rxs.test(n.get("title")) || rxs.test(n.get("description"))) {
       
   405                             _this.getRepresentationByModel(n).highlight(rxs);
       
   406                         } else {
       
   407                             _this.getRepresentationByModel(n).unhighlight(); 
       
   408                         }
       
   409                     });
       
   410                 }
       
   411             });
       
   412         }
       
   413 
       
   414         this.redraw();
       
   415 
       
   416         window.setInterval(function() {
       
   417             var _now = new Date().valueOf();
       
   418             _this.delete_list.forEach(function(d) {
       
   419                 if (_now >= d.time) {
       
   420                     var el = _renkan.project.get("nodes").findWhere({"delete_scheduled":d.id});
       
   421                     if (el) {
       
   422                         project.removeNode(el);
       
   423                     }
       
   424                     el = _renkan.project.get("edges").findWhere({"delete_scheduled":d.id});
       
   425                     if (el) {
       
   426                         project.removeEdge(el);
       
   427                     }
       
   428                 }
       
   429             });
       
   430             _this.delete_list = _this.delete_list.filter(function(d) {
       
   431                 return _renkan.project.get("nodes").findWhere({"delete_scheduled":d.id}) || _renkan.project.get("edges").findWhere({"delete_scheduled":d.id});
       
   432             });
       
   433         }, 500);
       
   434 
       
   435         if (this.minimap) {
       
   436             window.setInterval(function() {
       
   437                 _this.rescaleMinimap();
       
   438             }, 2000);
       
   439         }
       
   440 
       
   441     };
       
   442 
       
   443     _(Scene.prototype).extend({
       
   444         template: _.template(
       
   445                 '<% if (options.show_top_bar) { %><div class="Rk-TopBar"><% if (!options.editor_mode) { %><h2 class="Rk-PadTitle"><%- project.get("title") || translate("Untitled project")%></h2>'
       
   446                 + '<% } else { %><input type="text" class="Rk-PadTitle" value="<%- project.get("title") || "" %>" placeholder="<%-translate("Untitled project")%>" /><% } %>'
       
   447                 + '<% if (options.show_user_list) { %><div class="Rk-Users"><div class="Rk-CurrentUser"><div class="Rk-Edit-ColorPicker-Wrapper"><span class="Rk-CurrentUser-Color"><% if (options.user_color_editable) { %><span class="Rk-Edit-ColorTip"></span><% } %></span>'
       
   448                 + '<% if (options.user_color_editable) { print(colorPicker) } %></div><span class="Rk-CurrentUser-Name">&lt;unknown user&gt;</span></div><ul class="Rk-UserList"></ul></div><% } %>'
       
   449                 + '<% if (options.home_button_url) {%><div class="Rk-TopBar-Separator"></div><a class="Rk-TopBar-Button Rk-Home-Button" href="<%- options.home_button_url %>"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents">'
       
   450                 + '<%- translate(options.home_button_title) %></div></div></a><% } %>'
       
   451                 + '<% if (options.show_fullscreen_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-FullScreen-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents"><%-translate("Full Screen")%></div></div></div><% } %>'
       
   452                 + '<% if (options.editor_mode) { %>'
       
   453                 + '<% if (options.show_addnode_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-AddNode-Button"><div class="Rk-TopBar-Tooltip">'
       
   454                 + '<div class="Rk-TopBar-Tooltip-Contents"><%-translate("Add Node")%></div></div></div><% } %>'
       
   455                 + '<% if (options.show_addedge_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-AddEdge-Button"><div class="Rk-TopBar-Tooltip">'
       
   456                 + '<div class="Rk-TopBar-Tooltip-Contents"><%-translate("Add Edge")%></div></div></div><% } %>'
       
   457                 + '<% if (options.show_save_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-Save-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents"> </div></div></div><% } %>'
       
   458                 + '<% if (options.show_open_button) { %><div class="Rk-TopBar-Separator"></div><div class="Rk-TopBar-Button Rk-Open-Button"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents"><%-translate("Open Project")%></div></div></div><% } %>'
       
   459                 + '<% if (options.show_bookmarklet) { %><div class="Rk-TopBar-Separator"></div><a class="Rk-TopBar-Button Rk-Bookmarklet-Button" href="#"><div class="Rk-TopBar-Tooltip"><div class="Rk-TopBar-Tooltip-Contents">'
       
   460                 + '<%-translate("Renkan \'Drag-to-Add\' bookmarklet")%></div></div></a><% } %>'
       
   461                 + '<div class="Rk-TopBar-Separator"></div><% }; if (options.show_search_field) { %>'
       
   462                 + '<form action="#" class="Rk-GraphSearch-Form"><input type="search" class="Rk-GraphSearch-Field" placeholder="<%- translate("Search in graph") %>" /></form><div class="Rk-TopBar-Separator"></div><% } %></div><% } %>'
       
   463                 + '<div class="Rk-Editing-Space<% if (!options.show_top_bar) { %> Rk-Editing-Space-Full<% } %>">'
       
   464                 + '<div class="Rk-Labels"></div><canvas class="Rk-Canvas" resize></canvas><div class="Rk-Notifications"></div><div class="Rk-Editor">'
       
   465                 + '<% if (options.show_bins) { %><div class="Rk-Fold-Bins">&laquo;</div><% } %>'
       
   466                 + '<div class="Rk-ZoomButtons"><div class="Rk-ZoomIn" title="<%-translate("Zoom In")%>"></div><div class="Rk-ZoomFit" title="<%-translate("Zoom Fit")%>"></div><div class="Rk-ZoomOut" title="<%-translate("Zoom Out")%>"></div>'
       
   467                 + '<% if (options.editor_mode) { %><div class="Rk-ZoomSave" title="<%-translate("Zoom Save")%>"></div><% } %>'
       
   468                 + '<% if (options.editor_mode || !isNaN(parseInt(options.default_view))) { %><div class="Rk-ZoomSetSaved" title="<%-translate("View saved zoom")%>"></div><% } %></div>'
       
   469                 + '</div></div>'
       
   470         ),
       
   471         fixSize: function(_autoscale) {
       
   472             var w = this.$.width(),
       
   473             h = this.$.height();
       
   474             if (this.renkan.options.show_top_bar) {
       
   475                 h -= this.$.find(".Rk-TopBar").height();
       
   476             }
       
   477             this.canvas_$.attr({
       
   478                 width: w,
       
   479                 height: h
       
   480             });
       
   481 
       
   482             paper.view.viewSize = new paper.Size([w, h]);
       
   483 
       
   484             if (_autoscale) {
       
   485                 // If _autoscale, we get the initial view (zoom+offset) set in the project datas.
       
   486                 if(this.renkan.read_only && !isNaN(parseInt(this.renkan.options.default_view))){
       
   487                     this.autoScale(this.renkan.project.get("views")[parseInt(this.renkan.options.default_view)]);
       
   488                 }
       
   489                 else{
       
   490                     this.autoScale();
       
   491                 }
       
   492             }
       
   493         },
       
   494         drawSector: function(_repr, _inR, _outR, _startAngle, _endAngle, _padding, _imgname, _caption) {
       
   495             var _options = this.renkan.options,
       
   496             _startRads = _startAngle * Math.PI / 180,
       
   497             _endRads = _endAngle * Math.PI / 180,
       
   498             _img = this.icon_cache[_imgname],
       
   499             _startdx = - Math.sin(_startRads),
       
   500             _startdy = Math.cos(_startRads),
       
   501             _startXIn = Math.cos(_startRads) * _inR + _padding * _startdx,
       
   502             _startYIn = Math.sin(_startRads) * _inR + _padding * _startdy,
       
   503             _startXOut = Math.cos(_startRads) * _outR + _padding * _startdx,
       
   504             _startYOut = Math.sin(_startRads) * _outR + _padding * _startdy,
       
   505             _enddx = - Math.sin(_endRads),
       
   506             _enddy = Math.cos(_endRads),
       
   507             _endXIn = Math.cos(_endRads) * _inR - _padding * _enddx,
       
   508             _endYIn = Math.sin(_endRads) * _inR - _padding * _enddy,
       
   509             _endXOut = Math.cos(_endRads) * _outR - _padding * _enddx,
       
   510             _endYOut = Math.sin(_endRads) * _outR - _padding * _enddy,
       
   511             _centerR = (_inR + _outR)/2,
       
   512             _centerRads = (_startRads + _endRads) / 2,
       
   513             _centerX = Math.cos(_centerRads) * _centerR,
       
   514             _centerY = Math.sin(_centerRads) * _centerR,
       
   515             _centerXIn = Math.cos(_centerRads) * _inR,
       
   516             _centerXOut = Math.cos(_centerRads) * _outR,
       
   517             _centerYIn = Math.sin(_centerRads) * _inR,
       
   518             _centerYOut = Math.sin(_centerRads) * _outR,
       
   519             _textX = Math.cos(_centerRads) * (_outR + 3),
       
   520             _textY = Math.sin(_centerRads) * (_outR + _options.buttons_label_font_size) + _options.buttons_label_font_size / 2;
       
   521             this.buttons_layer.activate();
       
   522             var _path = new paper.Path();
       
   523             _path.add([_startXIn, _startYIn]);
       
   524             _path.arcTo([_centerXIn, _centerYIn], [_endXIn, _endYIn]);
       
   525             _path.lineTo([_endXOut,  _endYOut]);
       
   526             _path.arcTo([_centerXOut, _centerYOut], [_startXOut, _startYOut]);
       
   527             _path.fillColor = _options.buttons_background;
       
   528             _path.opacity = .5;
       
   529             _path.closed = true;
       
   530             _path.__representation = _repr;
       
   531             var _text = new paper.PointText(_textX,_textY);
       
   532             _text.characterStyle = {
       
   533                     fontSize: _options.buttons_label_font_size,
       
   534                     fillColor: _options.buttons_label_color
       
   535             };
       
   536             if (_textX > 2) {
       
   537                 _text.paragraphStyle.justification = 'left';
       
   538             } else if (_textX < -2) {
       
   539                 _text.paragraphStyle.justification = 'right';
       
   540             } else {
       
   541                 _text.paragraphStyle.justification = 'center';
       
   542             }
       
   543             _text.visible = false;
       
   544             var _visible = false,
       
   545             _restPos = new paper.Point(-200, -200),
       
   546             _grp = new paper.Group([_path, _text]),
       
   547             _delta = _grp.position,
       
   548             _imgdelta = new paper.Point([_centerX, _centerY]),
       
   549             _currentPos = new paper.Point(0,0);
       
   550             _text.content = _caption;
       
   551             _grp.visible = false;
       
   552             _grp.position = _restPos;
       
   553             var _res = {
       
   554                     show: function() {
       
   555                         _visible = true;
       
   556                         _grp.position = _currentPos.add(_delta);
       
   557                         _grp.visible = true;
       
   558                     },
       
   559                     moveTo: function(_point) {
       
   560                         _currentPos = _point;
       
   561                         if (_visible) {
       
   562                             _grp.position = _point.add(_delta);
       
   563                         }
       
   564                     },
       
   565                     hide: function() {
       
   566                         _visible = false;
       
   567                         _grp.visible = false;
       
   568                         _grp.position = _restPos;
       
   569                     },
       
   570                     select: function() {
       
   571                         _path.opacity = .8;
       
   572                         _text.visible = true;
       
   573                     },
       
   574                     unselect: function() {
       
   575                         _path.opacity = .5;
       
   576                         _text.visible = false;
       
   577                     },
       
   578                     destroy: function() {
       
   579                         _grp.remove();
       
   580                     }
       
   581             };
       
   582             var showImage = function() {
       
   583                 var _raster = new paper.Raster(_img);
       
   584                 _raster.position = _imgdelta.add(_grp.position).subtract(_delta);
       
   585                 _raster.locked = true; // Disable mouse events on icon
       
   586                 _grp.addChild(_raster);
       
   587             };
       
   588             if (_img.width) {
       
   589                 showImage();
       
   590             } else {
       
   591                 $(_img).on("load",showImage);
       
   592             }
       
   593 
       
   594             return _res;
       
   595         },
       
   596         addToBundles: function(_edgeRepr) {
       
   597             var _bundle = _(this.bundles).find(function(_bundle) {
       
   598                 return ( 
       
   599                         ( _bundle.from === _edgeRepr.from_representation && _bundle.to === _edgeRepr.to_representation )
       
   600                         || ( _bundle.from === _edgeRepr.to_representation && _bundle.to === _edgeRepr.from_representation )
       
   601                 );
       
   602             });
       
   603             if (typeof _bundle !== "undefined") {
       
   604                 _bundle.edges.push(_edgeRepr);
       
   605             } else {
       
   606                 _bundle = {
       
   607                         from: _edgeRepr.from_representation,
       
   608                         to: _edgeRepr.to_representation,
       
   609                         edges: [ _edgeRepr ],
       
   610                         getPosition: function(_er) {
       
   611                             var _dir = (_er.from_representation === this.from) ? 1 : -1;
       
   612                             return _dir * ( _(this.edges).indexOf(_er) - (this.edges.length - 1) / 2 );
       
   613                         }
       
   614                 };
       
   615                 this.bundles.push(_bundle);
       
   616             }
       
   617             return _bundle;
       
   618         },
       
   619         isEditable: function() {
       
   620             return (this.renkan.options.editor_mode && !this.renkan.read_only);
       
   621         },
       
   622         onStatusChange: function() {
       
   623             var savebtn = this.$.find(".Rk-Save-Button"),
       
   624             tip = savebtn.find(".Rk-TopBar-Tooltip-Contents");
       
   625             if (this.renkan.read_only) {
       
   626                 savebtn.removeClass("disabled Rk-Save-Online").addClass("Rk-Save-ReadOnly");
       
   627                 tip.text(this.renkan.translate("Connection lost"));
       
   628             } else {
       
   629                 if (this.renkan.options.snapshot_mode) {
       
   630                     savebtn.removeClass("Rk-Save-ReadOnly Rk-Save-Online");
       
   631                     tip.text(this.renkan.translate("Save Project"));
       
   632                 } else {
       
   633                     savebtn.removeClass("disabled Rk-Save-ReadOnly").addClass("Rk-Save-Online");
       
   634                     tip.text(this.renkan.translate("Auto-save enabled"));
       
   635                 }
       
   636             }
       
   637             this.redrawUsers();
       
   638         },
       
   639         setScale: function(_newScale, _offset) {
       
   640             if ((_newScale/this.initialScale) > Utils._MIN_SCALE && (_newScale/this.initialScale) < Utils._MAX_SCALE) {
       
   641                 this.scale = _newScale;
       
   642                 if (_offset) {
       
   643                     this.offset = _offset;
       
   644                 }
       
   645                 this.redraw();
       
   646             }
       
   647         },
       
   648         autoScale: function(force_view) {
       
   649             var nodes = this.renkan.project.get("nodes");
       
   650             if (nodes.length > 1) {
       
   651                 var _xx = nodes.map(function(_node) { return _node.get("position").x; }),
       
   652                 _yy = nodes.map(function(_node) { return _node.get("position").y; }),
       
   653                 _minx = Math.min.apply(Math, _xx),
       
   654                 _miny = Math.min.apply(Math, _yy),
       
   655                 _maxx = Math.max.apply(Math, _xx),
       
   656                 _maxy = Math.max.apply(Math, _yy);
       
   657                 var _scale = Math.min( (paper.view.size.width - 2 * this.renkan.options.autoscale_padding) / (_maxx - _minx), (paper.view.size.height - 2 * this.renkan.options.autoscale_padding) / (_maxy - _miny));
       
   658                 this.initialScale = _scale;
       
   659                 // Override calculated scale if asked
       
   660                 if((typeof force_view !== "undefined") && parseFloat(force_view.zoom_level)>0 && parseFloat(force_view.offset_x)>0 && parseFloat(force_view.offset_y)>0){
       
   661                     this.setScale(parseFloat(force_view.zoom_level), new paper.Point(parseFloat(force_view.offset_x), parseFloat(force_view.offset_y)));
       
   662                 }
       
   663                 else{
       
   664                     this.setScale(_scale, paper.view.center.subtract(new paper.Point([(_maxx + _minx) / 2, (_maxy + _miny) / 2]).multiply(_scale)));
       
   665                 }
       
   666             }
       
   667             if (nodes.length === 1) {
       
   668                 this.setScale(1, paper.view.center.subtract(new paper.Point([nodes.at(0).get("position").x, nodes.at(0).get("position").y])));
       
   669             }
       
   670         },
       
   671         redrawMiniframe: function() {
       
   672             var topleft = this.toMinimapCoords(this.toModelCoords(new paper.Point([0,0]))),
       
   673             bottomright = this.toMinimapCoords(this.toModelCoords(paper.view.bounds.bottomRight));
       
   674             this.minimap.miniframe.fitBounds(topleft, bottomright);
       
   675         },
       
   676         rescaleMinimap: function() {
       
   677             var nodes = this.renkan.project.get("nodes");
       
   678             if (nodes.length > 1) {
       
   679                 var _xx = nodes.map(function(_node) { return _node.get("position").x; }),
       
   680                 _yy = nodes.map(function(_node) { return _node.get("position").y; }),
       
   681                 _minx = Math.min.apply(Math, _xx),
       
   682                 _miny = Math.min.apply(Math, _yy),
       
   683                 _maxx = Math.max.apply(Math, _xx),
       
   684                 _maxy = Math.max.apply(Math, _yy);
       
   685                 var _scale = Math.min(
       
   686                         this.scale * .8 * this.renkan.options.minimap_width / paper.view.bounds.width,
       
   687                         this.scale * .8 * this.renkan.options.minimap_height / paper.view.bounds.height,
       
   688                         ( this.renkan.options.minimap_width - 2 * this.renkan.options.minimap_padding ) / (_maxx - _minx),
       
   689                         ( this.renkan.options.minimap_height - 2 * this.renkan.options.minimap_padding ) / (_maxy - _miny)
       
   690                 );
       
   691                 this.minimap.offset = this.minimap.size.divide(2).subtract(new paper.Point([(_maxx + _minx) / 2, (_maxy + _miny) / 2]).multiply(_scale));
       
   692                 this.minimap.scale = _scale;
       
   693             }
       
   694             if (nodes.length === 1) {
       
   695                 this.minimap.scale = .1;
       
   696                 this.minimap.offset = this.minimap.size.divide(2).subtract(new paper.Point([nodes.at(0).get("position").x, nodes.at(0).get("position").y]).multiply(this.minimap.scale));
       
   697             }
       
   698             this.redraw();
       
   699         },
       
   700         toPaperCoords: function(_point) {
       
   701             return _point.multiply(this.scale).add(this.offset);
       
   702         },
       
   703         toMinimapCoords: function(_point) {
       
   704             return _point.multiply(this.minimap.scale).add(this.minimap.offset).add(this.minimap.topleft);
       
   705         },
       
   706         toModelCoords: function(_point) {
       
   707             return _point.subtract(this.offset).divide(this.scale);
       
   708         },
       
   709         addRepresentation: function(_type, _model) {
       
   710             var r = requtils.getRenderer()[_type];
       
   711             var _repr = new r(this, _model);
       
   712             this.representations.push(_repr);
       
   713             return _repr;
       
   714         },
       
   715         addRepresentations: function(_type, _collection) {
       
   716             var _this = this;
       
   717             _collection.forEach(function(_model) {
       
   718                 _this.addRepresentation(_type, _model);
       
   719             });
       
   720         },
       
   721         userTemplate: _.template(
       
   722                 '<li class="Rk-User"><span class="Rk-UserColor" style="background:<%=background%>;"></span><%=name%></li>'
       
   723         ),
       
   724         redrawUsers: function() {
       
   725             if (!this.renkan.options.show_user_list) {
       
   726                 return;
       
   727             }
       
   728             var allUsers = [].concat((this.renkan.project.current_user_list || {}).models || [], (this.renkan.project.get("users") || {}).models || []),
       
   729             ulistHtml = '',
       
   730             $userpanel = this.$.find(".Rk-Users"),
       
   731             $name = $userpanel.find(".Rk-CurrentUser-Name"),
       
   732             $cpitems = $userpanel.find(".Rk-Edit-ColorPicker li"),
       
   733             $colorsquare = $userpanel.find(".Rk-CurrentUser-Color"),
       
   734             _this = this;
       
   735             $name.off("click").text(this.renkan.translate("<unknown user>"));
       
   736             $cpitems.off("mouseleave click");
       
   737             allUsers.forEach(function(_user) {
       
   738                 if (_user.get("_id") === _this.renkan.current_user) {
       
   739                     $name.text(_user.get("title"));
       
   740                     $colorsquare.css("background", _user.get("color"));
       
   741                     if (_this.isEditable()) {
       
   742 
       
   743                         if (_this.renkan.options.user_name_editable) {
       
   744                             $name.click(function() {
       
   745                                 var $this = $(this),
       
   746                                 $input = $('<input>').val(_user.get("title")).blur(function() {
       
   747                                     _user.set("title", $(this).val());
       
   748                                     _this.redrawUsers();
       
   749                                     _this.redraw();
       
   750                                 });
       
   751                                 $this.empty().html($input);
       
   752                                 $input.select();
       
   753                             });
       
   754                         }
       
   755 
       
   756                         if (_this.renkan.options.user_color_editable) {
       
   757                             $cpitems.click(
       
   758                                     function(_e) {
       
   759                                         _e.preventDefault();
       
   760                                         if (_this.isEditable()) {
       
   761                                             _user.set("color", $(this).attr("data-color"));
       
   762                                         }
       
   763                                         $(this).parent().hide();
       
   764                                     }
       
   765                             ).mouseleave(function() {
       
   766                                 $colorsquare.css("background", _user.get("color"));
       
   767                             });
       
   768                         }
       
   769                     }
       
   770 
       
   771                 } else {
       
   772                     ulistHtml += _this.userTemplate({
       
   773                         name: _user.get("title"),
       
   774                         background: _user.get("color")
       
   775                     });
       
   776                 }
       
   777             });
       
   778             $userpanel.find(".Rk-UserList").html(ulistHtml);
       
   779         },
       
   780         removeRepresentation: function(_representation) {
       
   781             _representation.destroy();
       
   782             this.representations = _(this.representations).reject(
       
   783                     function(_repr) {
       
   784                         return _repr == _representation;
       
   785                     }
       
   786             );
       
   787         },
       
   788         getRepresentationByModel: function(_model) {
       
   789             if (!_model) {
       
   790                 return undefined;
       
   791             }
       
   792             return _(this.representations).find(function(_repr) {
       
   793                 return _repr.model === _model;
       
   794             });
       
   795         },
       
   796         removeRepresentationsOfType: function(_type) {
       
   797             var _representations = _(this.representations).filter(function(_repr) {
       
   798                 return _repr.type == _type;
       
   799             }),
       
   800             _this = this;
       
   801             _(_representations).each(function(_repr) {
       
   802                 _this.removeRepresentation(_repr);
       
   803             });
       
   804         },
       
   805         highlightModel: function(_model) {
       
   806             var _repr = this.getRepresentationByModel(_model);
       
   807             if (_repr) {
       
   808                 _repr.highlight();
       
   809             }
       
   810         },
       
   811         unhighlightAll: function(_model) {
       
   812             _(this.representations).each(function(_repr) {
       
   813                 _repr.unhighlight();
       
   814             });
       
   815         },
       
   816         unselectAll: function(_model) {
       
   817             _(this.representations).each(function(_repr) {
       
   818                 _repr.unselect();
       
   819             });
       
   820         },
       
   821         redraw: function() {
       
   822             _(this.representations).each(function(_representation) {
       
   823                 _representation.redraw(true);
       
   824             });
       
   825             if (this.minimap) {
       
   826                 this.redrawMiniframe();
       
   827             }
       
   828             paper.view.draw();
       
   829         },
       
   830         addTempEdge: function(_from, _point) {
       
   831             var _tmpEdge = this.addRepresentation("TempEdge",null);
       
   832             _tmpEdge.end_pos = _point;
       
   833             _tmpEdge.from_representation = _from;
       
   834             _tmpEdge.redraw();
       
   835             this.click_target = _tmpEdge;
       
   836         },
       
   837         findTarget: function(_hitResult) {
       
   838             if (_hitResult && typeof _hitResult.item.__representation !== "undefined") {
       
   839                 var _newTarget = _hitResult.item.__representation;
       
   840                 if (this.selected_target !== _hitResult.item.__representation) {
       
   841                     if (this.selected_target) {
       
   842                         this.selected_target.unselect(_newTarget);
       
   843                     }
       
   844                     _newTarget.select(this.selected_target);
       
   845                     this.selected_target = _newTarget;
       
   846                 }
       
   847             } else {
       
   848                 if (this.selected_target) {
       
   849                     this.selected_target.unselect();
       
   850                 }
       
   851                 this.selected_target = null;
       
   852             }
       
   853         },
       
   854         paperShift: function(_delta) {
       
   855             this.offset = this.offset.add(_delta);
       
   856             this.redraw();
       
   857         },
       
   858         onMouseMove: function(_event) {
       
   859             var _off = this.canvas_$.offset(),
       
   860             _point = new paper.Point([
       
   861                                       _event.pageX - _off.left,
       
   862                                       _event.pageY - _off.top
       
   863                                       ]),
       
   864                                       _delta = _point.subtract(this.last_point);
       
   865             this.last_point = _point;
       
   866             if (!this.is_dragging && this.mouse_down && _delta.length > Utils._MIN_DRAG_DISTANCE) {
       
   867                 this.is_dragging = true;
       
   868             }
       
   869             var _hitResult = paper.project.hitTest(_point);
       
   870             if (this.is_dragging) {
       
   871                 if (this.click_target && typeof this.click_target.paperShift === "function") {
       
   872                     this.click_target.paperShift(_delta);
       
   873                 } else {
       
   874                     this.paperShift(_delta);
       
   875                 }
       
   876             } else {
       
   877                 this.findTarget(_hitResult);
       
   878             }
       
   879             paper.view.draw();
       
   880         },
       
   881         onMouseDown: function(_event, _isTouch) {
       
   882             var _off = this.canvas_$.offset(),
       
   883             _point = new paper.Point([
       
   884                                       _event.pageX - _off.left,
       
   885                                       _event.pageY - _off.top
       
   886                                       ]);
       
   887             this.last_point = _point;
       
   888             this.mouse_down = true;
       
   889             if (!this.click_target || this.click_target.type !== "Temp-edge") {
       
   890                 this.removeRepresentationsOfType("editor");
       
   891                 this.is_dragging = false;
       
   892                 var _hitResult = paper.project.hitTest(_point);
       
   893                 if (_hitResult && typeof _hitResult.item.__representation !== "undefined") {
       
   894                     this.click_target = _hitResult.item.__representation;
       
   895                     this.click_target.mousedown(_event, _isTouch);
       
   896                 } else {
       
   897                     this.click_target = null;
       
   898                     if (this.isEditable() && this.click_mode === Utils._CLICKMODE_ADDNODE) {
       
   899                         var _coords = this.toModelCoords(_point),
       
   900                         _data = {
       
   901                             id: Utils.getUID('node'),
       
   902                             created_by: this.renkan.current_user,
       
   903                             position: {
       
   904                                 x: _coords.x,
       
   905                                 y: _coords.y
       
   906                             }
       
   907                         };
       
   908                         _node = this.renkan.project.addNode(_data);
       
   909                         this.getRepresentationByModel(_node).openEditor();
       
   910                     }
       
   911                 }
       
   912             }
       
   913             if (this.click_mode) {
       
   914                 if (this.isEditable() && this.click_mode === Utils._CLICKMODE_STARTEDGE && this.click_target && this.click_target.type === "Node") {
       
   915                     this.removeRepresentationsOfType("editor");
       
   916                     this.addTempEdge(this.click_target, _point);
       
   917                     this.click_mode = Utils._CLICKMODE_ENDEDGE;
       
   918                     this.notif_$.fadeOut(function() {
       
   919                         $(this).html(this.renkan.translate("Click on a second node to complete the edge")).fadeIn();
       
   920                     });
       
   921                 } else {
       
   922                     this.notif_$.hide();
       
   923                     this.click_mode = false;
       
   924                 }
       
   925             }
       
   926             paper.view.draw();
       
   927         },
       
   928         onMouseUp: function(_event, _isTouch) {
       
   929             this.mouse_down = false;
       
   930             if (this.click_target) {
       
   931                 var _off = this.canvas_$.offset();
       
   932                 this.click_target.mouseup(
       
   933                         {
       
   934                             point: new paper.Point([
       
   935                                                     _event.pageX - _off.left,
       
   936                                                     _event.pageY - _off.top
       
   937                                                     ])
       
   938                         },
       
   939                         _isTouch
       
   940                 );
       
   941             } else {
       
   942                 this.click_target = null;
       
   943                 this.is_dragging = false;
       
   944                 if (_isTouch) {
       
   945                     this.unselectAll();
       
   946                 }
       
   947             }
       
   948             paper.view.draw();
       
   949         },
       
   950         onScroll: function(_event, _scrolldelta) {
       
   951             this.totalScroll += _scrolldelta;
       
   952             if (Math.abs(this.totalScroll) >= 1) {
       
   953                 var _off = this.canvas_$.offset(),
       
   954                 _delta = new paper.Point([
       
   955                                           _event.pageX - _off.left,
       
   956                                           _event.pageY - _off.top
       
   957                                           ]).subtract(this.offset).multiply( Math.SQRT2 - 1 );
       
   958                 if (this.totalScroll > 0) {
       
   959                     this.setScale( this.scale * Math.SQRT2, this.offset.subtract(_delta) );
       
   960                 } else {
       
   961                     this.setScale( this.scale * Math.SQRT1_2, this.offset.add(_delta.divide(Math.SQRT2)));
       
   962                 }
       
   963                 this.totalScroll = 0;
       
   964             }
       
   965         },
       
   966         onDoubleClick: function(_event) {
       
   967             if (!this.isEditable()) {
       
   968                 return;
       
   969             }
       
   970             var _off = this.canvas_$.offset(),
       
   971             _point = new paper.Point([
       
   972                                       _event.pageX - _off.left,
       
   973                                       _event.pageY - _off.top
       
   974                                       ]);
       
   975             var _hitResult = paper.project.hitTest(_point);
       
   976             if (this.isEditable() && (!_hitResult || typeof _hitResult.item.__representation === "undefined")) {
       
   977                 var _coords = this.toModelCoords(_point),
       
   978                 _data = {
       
   979                     id: Utils.getUID('node'),
       
   980                     created_by: this.renkan.current_user,
       
   981                     position: {
       
   982                         x: _coords.x,
       
   983                         y: _coords.y
       
   984                     }
       
   985                 },
       
   986                 _node = this.renkan.project.addNode(_data);
       
   987                 this.getRepresentationByModel(_node).openEditor();
       
   988             }
       
   989             paper.view.draw();
       
   990         },
       
   991         defaultDropHandler: function(_data) {
       
   992             var newNode = {};
       
   993             switch(_data["text/x-iri-specific-site"]) {
       
   994             case "twitter":
       
   995                 var snippet = $('<div>').html(_data["text/x-iri-selected-html"]),
       
   996                 tweetdiv = snippet.find(".tweet");
       
   997                 newNode.title = this.renkan.translate("Tweet by ") + tweetdiv.attr("data-name");
       
   998                 newNode.uri = "http://twitter.com/" + tweetdiv.attr("data-screen-name") + "/status/" + tweetdiv.attr("data-tweet-id");
       
   999                 newNode.image = tweetdiv.find(".avatar").attr("src");
       
  1000                 newNode.description = tweetdiv.find(".js-tweet-text:first").text();
       
  1001                 break;
       
  1002             case "google":
       
  1003                 var snippet = $('<div>').html(_data["text/x-iri-selected-html"]);
       
  1004                 newNode.title = snippet.find("h3:first").text().trim();
       
  1005                 newNode.uri = snippet.find("h3 a").attr("href");
       
  1006                 newNode.description = snippet.find(".st:first").text().trim();
       
  1007                 break;
       
  1008             case undefined:
       
  1009             default:
       
  1010                 if (_data["text/x-iri-source-uri"]) {
       
  1011                     newNode.uri = _data["text/x-iri-source-uri"];
       
  1012                 }
       
  1013             if (_data["text/plain"] || _data["text/x-iri-selected-text"]) {
       
  1014                 newNode.description = (_data["text/plain"] || _data["text/x-iri-selected-text"]).replace(/[\s\n]+/gm,' ').trim();
       
  1015             }
       
  1016             if (_data["text/html"] || _data["text/x-iri-selected-html"]) {
       
  1017                 var snippet = $('<div>').html(_data["text/html"] || _data["text/x-iri-selected-html"]);
       
  1018                 var _svgimgs = snippet.find("image");
       
  1019                 if (_svgimgs.length) {
       
  1020                     newNode.image = _svgimgs.attr("xlink:href");
       
  1021                 }
       
  1022                 var _svgpaths = snippet.find("path");
       
  1023                 if (_svgpaths.length) {
       
  1024                     newNode.clipPath = _svgpaths.attr("d");
       
  1025                 }
       
  1026                 var _imgs = snippet.find("img");
       
  1027                 if (_imgs.length) {
       
  1028                     newNode.image = _imgs[0].src;
       
  1029                 }
       
  1030                 var _as = snippet.find("a");
       
  1031                 if (_as.length) {
       
  1032                     newNode.uri = _as[0].href;
       
  1033                 }
       
  1034                 newNode.title = snippet.find("[title]").attr("title") || newNode.title;
       
  1035                 newNode.description = snippet.text().replace(/[\s\n]+/gm,' ').trim();
       
  1036             }
       
  1037             if (_data["text/uri-list"]) {
       
  1038                 newNode.uri = _data["text/uri-list"];
       
  1039             }
       
  1040             if (_data["text/x-moz-url"] && !newNode.title) {
       
  1041                 newNode.title = (_data["text/x-moz-url"].split("\n")[1] || "").trim();
       
  1042                 if (newNode.title === newNode.uri) {
       
  1043                     newNode.title = false;
       
  1044                 }
       
  1045             }
       
  1046             if (_data["text/x-iri-source-title"] && !newNode.title) {
       
  1047                 newNode.title = _data["text/x-iri-source-title"];
       
  1048             }
       
  1049             if (_data["text/html"] || _data["text/x-iri-selected-html"]) {
       
  1050                 var snippet = $('<div>').html(_data["text/html"] || _data["text/x-iri-selected-html"]);
       
  1051                 newNode.image = snippet.find("[data-image]").attr("data-image") || newNode.image;
       
  1052                 newNode.uri = snippet.find("[data-uri]").attr("data-uri") || newNode.uri;
       
  1053                 newNode.title = snippet.find("[data-title]").attr("data-title") || newNode.title;
       
  1054                 newNode.description = snippet.find("[data-description]").attr("data-description") || newNode.description;
       
  1055                 newNode.clipPath = snippet.find("[data-clip-path]").attr("data-clip-path") || newNode.clipPath;
       
  1056             }
       
  1057             }
       
  1058 
       
  1059             if(typeof this.renkan.options.drop_enhancer === "function"){
       
  1060                 newNode = this.renkan.options.drop_enhancer(newNode, _data);
       
  1061             }
       
  1062             return newNode;
       
  1063 
       
  1064         },
       
  1065         dropData: function(_data, _event) {
       
  1066             if (!this.isEditable()) {
       
  1067                 return;
       
  1068             }
       
  1069             if (_data["text/json"] || _data["application/json"]) {
       
  1070                 try {
       
  1071                     var jsondata = JSON.parse(_data["text/json"] || _data["application/json"]);
       
  1072                     _(_data).extend(jsondata);
       
  1073                 }
       
  1074                 catch(e) {}
       
  1075             }
       
  1076 
       
  1077             var newNode = (typeof this.renkan.options.drop_handler === "undefined")?this.defaultDropHandler(_data):this.renkan.options.drop_handler(_data);
       
  1078 
       
  1079             if (!newNode.title) {
       
  1080                 newNode.title = this.renkan.translate("Dragged resource");
       
  1081             }
       
  1082             var fields = ["title", "description", "uri", "image"];
       
  1083             for (var i = 0; i < fields.length; i++) {
       
  1084                 var f = fields[i];
       
  1085                 if (_data["text/x-iri-" + f] || _data[f]) {
       
  1086                     newNode[f] = _data["text/x-iri-" + f] || _data[f];
       
  1087                 }
       
  1088                 if (newNode[f] === "none" || newNode[f] === "null") {
       
  1089                     newNode[f] = undefined;
       
  1090                 }
       
  1091             }
       
  1092             var _off = this.canvas_$.offset(),
       
  1093             _point = new paper.Point([
       
  1094                                       _event.pageX - _off.left,
       
  1095                                       _event.pageY - _off.top
       
  1096                                       ]),
       
  1097                                       _coords = this.toModelCoords(_point),
       
  1098                                       _nodedata = {
       
  1099                 id: Utils.getUID('node'),
       
  1100                 created_by: this.renkan.current_user,
       
  1101                 uri: newNode.uri || "",
       
  1102                 title: newNode.title || "",
       
  1103                 description: newNode.description || "",
       
  1104                 image: newNode.image || "",
       
  1105                 color: newNode.color || undefined,
       
  1106                 clip_path: newNode.clipPath || undefined,
       
  1107                 position: {
       
  1108                     x: _coords.x,
       
  1109                     y: _coords.y
       
  1110                 }
       
  1111             };
       
  1112             var _node = this.renkan.project.addNode(_nodedata),
       
  1113             _repr = this.getRepresentationByModel(_node);
       
  1114             if (_event.type === "drop") {
       
  1115                 _repr.openEditor();
       
  1116             }
       
  1117         },
       
  1118         fullScreen: function() {
       
  1119             var _isFull = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen,
       
  1120             _el = this.renkan.$[0],
       
  1121             _requestMethods = ["requestFullScreen","mozRequestFullScreen","webkitRequestFullScreen"],
       
  1122             _cancelMethods = ["cancelFullScreen","mozCancelFullScreen","webkitCancelFullScreen"];
       
  1123             if (_isFull) {
       
  1124                 for (var i = 0; i < _cancelMethods.length; i++) {
       
  1125                     if (typeof document[_cancelMethods[i]] === "function") {
       
  1126                         document[_cancelMethods[i]]();
       
  1127                         break;
       
  1128                     }
       
  1129                 }
       
  1130             } else {
       
  1131                 for (var i = 0; i < _requestMethods.length; i++) {
       
  1132                     if (typeof _el[_requestMethods[i]] === "function") {
       
  1133                         _el[_requestMethods[i]]();
       
  1134                         break;
       
  1135                     }
       
  1136                 }
       
  1137             }
       
  1138         },
       
  1139         zoomOut: function() {
       
  1140             var _newScale = this.scale * Math.SQRT1_2,
       
  1141             _offset = new paper.Point([
       
  1142                                        this.canvas_$.width(),
       
  1143                                        this.canvas_$.height()
       
  1144                                        ]).multiply( .5 * ( 1 - Math.SQRT1_2 ) ).add(this.offset.multiply( Math.SQRT1_2 ));
       
  1145             this.setScale( _newScale, _offset );
       
  1146         },
       
  1147         zoomIn: function() {
       
  1148             var _newScale = this.scale * Math.SQRT2,
       
  1149             _offset = new paper.Point([
       
  1150                                        this.canvas_$.width(),
       
  1151                                        this.canvas_$.height()
       
  1152                                        ]).multiply( .5 * ( 1 - Math.SQRT2 ) ).add(this.offset.multiply( Math.SQRT2 ));
       
  1153             this.setScale( _newScale, _offset );
       
  1154         },
       
  1155         addNodeBtn: function() {
       
  1156             if (this.click_mode === Utils._CLICKMODE_ADDNODE) {
       
  1157                 this.click_mode = false;
       
  1158                 this.notif_$.hide();
       
  1159             } else {
       
  1160                 this.click_mode = Utils._CLICKMODE_ADDNODE;
       
  1161                 this.notif_$.text(this.renkan.translate("Click on the background canvas to add a node")).fadeIn();
       
  1162             }
       
  1163             return false;
       
  1164         },
       
  1165         addEdgeBtn: function() {
       
  1166             if (this.click_mode === Utils._CLICKMODE_STARTEDGE || this.click_mode === Utils._CLICKMODE_ENDEDGE) {
       
  1167                 this.click_mode = false;
       
  1168                 this.notif_$.hide();
       
  1169             } else {
       
  1170                 this.click_mode = Utils._CLICKMODE_STARTEDGE;
       
  1171                 this.notif_$.text(this.renkan.translate("Click on a first node to start the edge")).fadeIn();
       
  1172             }
       
  1173             return false;
       
  1174         },
       
  1175         foldBins: function() {
       
  1176             var foldBinsButton = this.$.find(".Rk-Fold-Bins"),
       
  1177             bins = this.renkan.$.find(".Rk-Bins");
       
  1178             if (bins.offset().left < 0) {
       
  1179                 bins.animate({left: 0},250);
       
  1180                 var _this = this;
       
  1181                 this.$.animate({left: 300},250,function() {
       
  1182                     var w = _this.$.width();
       
  1183                     paper.view.viewSize = new paper.Size([w, _this.canvas_$.height()]);
       
  1184                 });
       
  1185                 foldBinsButton.html("&laquo;");
       
  1186             } else {
       
  1187                 bins.animate({left: -300},250);
       
  1188                 var _this = this;
       
  1189                 this.$.animate({left: 0},250,function() {
       
  1190                     var w = _this.$.width();
       
  1191                     paper.view.viewSize = new paper.Size([w, _this.canvas_$.height()]);
       
  1192                 });
       
  1193                 foldBinsButton.html("&raquo;");
       
  1194             }
       
  1195         },
       
  1196         save: function() { },
       
  1197         open: function() { }
       
  1198     });
       
  1199     
       
  1200     /* Scene End */
       
  1201     
       
  1202     return Scene;
       
  1203     
       
  1204 });