re base code
[sdc.git] / catalog-ui / src / third-party / cytoscape.js-edge-editation / CytoscapeEdgeEditation.js
1 (function (scope) {
2     var Class = function (param1, param2) {
3
4         var extend, mixins, definition;
5         if (param2) {     //two parameters passed, first is extends, second definition object
6             extend = Array.isArray(param1) ? param1[0] : param1;
7             mixins = Array.isArray(param1) ? param1.slice(1) : null;
8             definition = param2;
9         } else {      //only one parameter passed => no extend, only definition
10             extend = null;
11             definition = param1;
12         }
13
14
15         var Definition = definition.hasOwnProperty("constructor") ? definition.constructor : function () {
16         };
17
18         Definition.prototype = Object.create(extend ? extend.prototype : null);
19         var propertiesObject = definition.propertiesObject ? definition.propertiesObject : {};
20         if (mixins) {
21             var i, i2;
22             for (i in mixins) {
23                 for (i2 in mixins[i].prototype) {
24                     Definition.prototype[i2] = mixins[i].prototype[i2];
25                 }
26                 for (var i2 in mixins[i].prototype.propertiesObject) {
27                     propertiesObject[i2] = mixins[i].prototype.propertiesObject[i2];
28                 }
29             }
30         }
31
32         Definition.prototype.propertiesObject = propertiesObject;
33
34         Object.defineProperties(Definition.prototype, propertiesObject);
35
36         for (var key in definition) {
37             if (definition.hasOwnProperty(key)) {
38                 Definition.prototype[key] = definition[key];
39             }
40         }
41
42         Definition.prototype.constructor = Definition;
43
44         return Definition;
45     };
46
47
48     var Interface = function (properties) {
49         this.properties = properties;
50     };
51
52     var InterfaceException = function (message) {
53         this.name = "InterfaceException";
54         this.message = message || "";
55     };
56
57     InterfaceException.prototype = new Error();
58
59     Interface.prototype.implements = function (target) {
60         for (var i in this.properties) {
61             if (target[this.properties[i]] == undefined) {
62                 throw new InterfaceException("Missing property " + this.properties[i]);
63             }
64         }
65         return true;
66     };
67
68     Interface.prototype.doesImplement = function (target) {
69         for (var i in this.properties) {
70             if (target[this.properties[i]] === undefined) {
71                 return false;
72             }
73         }
74         return true;
75     };
76
77     var VectorMath = {
78         distance: function (vector1, vector2) {
79             return Math.sqrt(Math.pow(vector1.x - vector2.x, 2) + Math.pow(vector1.y - vector2.y, 2));
80         }
81     };
82
83     var EventDispatcher = Class({
84         constructor: function () {
85             this.events = {};
86         },
87         on: function (name, listener, context) {
88             this.events[name] = this.events[name] ? this.events[name] : [];
89             this.events[name].push({
90                 listener: listener,
91                 context: context
92             })
93         },
94         once: function (name, listener, context) {
95             this.off(name, listener, context);
96             this.on(name, listener, context);
97         },
98         off: function (name, listener, context) {
99             //no event with this name registered? => finish
100             if (!this.events[name]) {
101                 return;
102             }
103             if (listener) {             //searching only for certains listeners
104                 for (var i in this.events[name]) {
105                     if (this.events[name][i].listener === listener) {
106                         if (!context || this.events[name][i].context === context) {
107                             this.events[name].splice(i, 1);
108                         }
109                     }
110                 }
111             } else {
112                 delete this.events[name];
113             }
114         },
115         trigger: function (name) {
116             var listeners = this.events[name];
117
118             for (var i in listeners) {
119                 listeners[i].listener.apply(listeners[i].context, Array.prototype.slice.call(arguments, 1));
120             }
121         }
122     });
123
124     exports.CytoscapeEdgeEditation = Class({
125
126         init: function (cy) {
127             this.DOUBLE_CLICK_INTERVAL = 300;
128             this.HANDLE_SIZE = 18;
129             this.ARROW_END_ID = "ARROW_END_ID";
130
131             this._handles = {};
132             this._dragging = false;
133             this._hover = null;
134             this._tagMode = false;
135
136             this._cy = cy;
137             this._$container = $(cy.container());
138
139             this._$canvas = $('<canvas></canvas>');
140             this._$canvas.css("top", 0);
141
142             this._ctx = this._$canvas[0].getContext('2d');
143             this._$container.children("div").append(this._$canvas);
144
145             this._resizeCanvas();
146
147             this.initContainerEvents();
148
149         },
150         initContainerEvents: function () {
151             this._cy.on("resize", this._resizeCanvas.bind(this));
152             /*$(window).bind('resize', this._resizeCanvas.bind(this));
153              $(window).bind('resize', this._resizeCanvas.bind(this));*/
154
155              this._$container.bind('resize', function () {
156                 this._resizeCanvas();
157             }.bind(this));
158
159             this._cy.bind('zoom pan', this._redraw.bind(this));
160
161             this._cy.on('showhandle', function (cy, target, customHandle) {
162                 this.permanentHandle = true;
163                 this._showHandles(target, customHandle);
164             }.bind(this));
165
166             this._cy.on('hidehandles', this._hideHandles.bind(this));
167
168             this._$container.on('mouseout', function (e) {
169                 if (this.permanentHandle) {
170                     return;
171                 }
172
173                 this._clear();
174             }.bind(this));
175
176
177         },
178         initNodeEvents: function (){
179
180             this._$canvas.on("mousedown", this._mouseDown.bind(this));
181             this._$canvas.on("mousemove", this._mouseMove.bind(this));
182             this._$canvas.on('mouseup', this._mouseUp.bind(this));
183
184             this._cy.on('tapdragover', 'node', this._mouseOver.bind(this));
185             this._cy.on('tapdragout', 'node', this._mouseOut.bind(this));
186
187             
188
189             //this._cy.on("select", "node", this._redraw.bind(this))
190
191             this._cy.on("mousedown", "node", function () {
192                 if(!this._tagMode) {
193                     this._nodeClicked = true;
194                 }
195             }.bind(this));
196
197             this._cy.on("mouseup", "node", function () {
198                 this._nodeClicked = false;
199             }.bind(this));
200
201             this._cy.on("remove", "node", function () {
202                 this._hover = false;
203                 this._clear();
204             }.bind(this));
205
206             // this._$container.on('mouseover', function (e) {
207             //     if (this._hover) {
208             //         this._mouseOver({cyTarget: this._hover});
209             //     }
210             // }.bind(this));
211
212             this._cy.on('tagstart', function(){
213                 this._tagMode = true;
214             }.bind(this));
215
216             this._cy.on('tagend', function(){
217                 this._tagMode = false;
218             }.bind(this))
219
220         },
221         registerHandle: function (handle) {
222             
223             if (handle.imageUrl) {
224
225                 var base_image = new Image();
226                 base_image.src = handle.imageUrl;
227                 base_image.onload = function() {
228                     handle.image = base_image;
229                   };
230             }
231             
232             this._handles[handle.type] = this._handles[handle.type] || [];
233             this._handles[handle.type] = handle;
234
235
236         },
237         _showHandles: function (target, handleType) {
238
239             if(!handleType){
240                 handleType = 'add-edge'; //ie, CanvasHandleTypes.ADD_EDGE, which is the default
241             }
242             this._drawHandle(this._handles[handleType], target);
243
244         },
245         _clear: function () {
246
247             var w = this._$container.width();
248             var h = this._$container.height();
249             this._ctx.clearRect(0, 0, w, h);
250         },
251         _drawHandle: function (handle, target) {
252
253             target.data().handleType = handle.type;
254             var position = this._getHandlePosition(target);
255             var handleSize = this.HANDLE_SIZE * this._cy.zoom();
256             this._ctx.clearRect(position.x, position.y, handleSize, handleSize);
257             
258             if (handle.image) {
259                 this._ctx.drawImage(handle.image, position.x, position.y, handleSize, handleSize);
260             }
261         },
262         _drawArrow: function (fromNode, toPosition, handle) {
263             var toNode;
264             if (this._hover) {
265                 toNode = this._hover;
266             } else {
267                 if (!this._arrowEnd) {
268                     this._arrowEnd = this._cy.add({
269                         group: "nodes",
270                         data: {
271                             "id": this.ARROW_END_ID,
272                             "position": { x: 150, y: 150 }
273                         }
274                     });
275
276                     this._arrowEnd.css({
277                         "opacity": 0,
278                         'width': 0.0001,
279                         'height': 0.0001
280                     });                   
281                 }
282
283                 this._arrowEnd.renderedPosition(toPosition);
284                 toNode = this._arrowEnd;
285             }
286
287
288             if (this._edge) {
289                 this._edge.remove();
290             }
291
292             this._edge = this._cy.add({
293                 group: "edges",
294                 data: {
295                     id: "edge",
296                     source: fromNode.id(),
297                     target: toNode.id(),
298                     type: 'temporary-link'
299                 },
300                 css: $.extend(
301                     this._getEdgeCSSByHandle(handle),
302                     {opacity: 0.5}
303                 )
304             });
305
306         },
307         _clearArrow: function () {
308             if (this._edge) {
309                 this._edge.remove();
310                 this._edge = null;
311             }
312
313             if (this._arrowEnd) {
314                 this._arrowEnd.remove();
315                 this._arrowEnd = null;
316             }
317         },
318         _resizeCanvas: function () {
319             this._$canvas
320                 .attr('height', this._$container.height())
321                 .attr('width', this._$container.width())
322                 .css({
323                     'position': 'absolute',
324                     'z-index': '999'
325                 });
326         },
327         _mouseDown: function (e) {
328             if(this._tagMode){
329                 return;
330             }
331             //this._hit = this._hitTestHandles(e);
332
333             if (this._hit) {
334                 this._lastClick = Date.now();
335                 this._dragging = this._hover;
336                 this._hover = null;
337                 e.stopImmediatePropagation();
338             }
339
340         },
341         _hideHandles: function () {
342             this.permanentHandle = false;
343             this._clear();
344
345         },
346         _mouseUp: function (e) {
347             if (this._hover) {
348                 if(this._tagMode){
349                     if(this._hitTestHandles(e))
350                     this._cy.trigger('handletagclick', {
351                         nodeId: this._hover.data().id
352                     });
353                     //this._hover = null;
354                 } else if (this._hit && this._dragging) {
355                     //check if custom listener was passed, if so trigger it and do not add edge
356                     var listeners = this._cy._private.listeners;
357                     for (var i = 0; i < listeners.length; i++) {
358                         if (listeners[i].type === 'addedgemouseup') {
359                             this._cy.trigger('addedgemouseup', {
360                                 source: this._dragging,
361                                 target: this._hover,
362                                 edge: this._edge
363                             });
364                             var that = this;
365                             setTimeout(function () {
366                                 that._dragging = false;
367                                 that._clearArrow();
368                                 that._hit = null;
369                             }, 0);
370
371
372                             return;
373                         }
374                     }
375
376                     var edgeToRemove = this._checkSingleEdge(this._hit.handle, this._dragging);
377                     if (edgeToRemove) {
378                         this._cy.remove("#" + edgeToRemove.id());
379                     }
380                     var edge = this._cy.add({
381                         data: {
382                             source: this._dragging.id(),
383                             target: this._hover.id(),
384                             type: "default"
385                         }
386                     });
387                     this._initEdgeEvents(edge);
388                 }
389             }
390             this._cy.trigger('handlemouseout', {
391                 node: this._hover
392             });
393             $("body").css("cursor", "inherit");
394             this._dragging = false;
395             this._clearArrow();
396         },
397         _mouseMove: function (e) {
398             if (this._hover) {
399                 if (!this._dragging) {
400                     this._hit = this._hitTestHandles(e);
401                     if (this._hit) {
402                         this._cy.trigger('handlemouseover', {
403                             node: this._hover
404                         });
405                         $("body").css("cursor", "pointer");
406                     } else {
407                         this._cy.trigger('handlemouseout', {
408                             node: this._hover
409                         });
410                         if(!this._tagMode){
411                             this._showHandles(this._hover);
412                         }
413                         $("body").css("cursor", "inherit");
414                     }
415                 }
416             }
417
418             if (this._dragging && this._hit.handle) {
419                 this._drawArrow(this._dragging, this._getRelativePosition(e), this._hit.handle);
420             }
421
422             if (this._nodeClicked) {
423                 this._clear();
424             }
425         },
426         _mouseOver: function (e) {
427
428             if (this._dragging) {
429                 if ( (e.cyTarget.id() != this._dragging.id()) && e.cyTarget.data().allowConnection) {
430                     this._hover = e.cyTarget;
431                 }
432             } else {
433                 this._hover = e.cyTarget;
434                 if (!this._tagMode) {
435                     this._showHandles(this._hover);
436                 }
437             }
438         },
439         _mouseOut: function (e) {
440             if(!this._dragging) {
441                 if (!this.permanentHandle) {
442                     this._clear();
443                 }
444                 this._cy.trigger('handlemouseout', {
445                     node: this._hover
446                 });
447             }
448             this._hover = null;
449         },
450         _removeEdge: function (edge) {
451             edge.off("mousedown");
452             this._cy.remove("#" + edge.id());
453         },
454         _initEdgeEvents: function (edge) {
455             var self = this;
456             edge.on("mousedown", function () {
457                 if (self.__lastClick && Date.now() - self.__lastClick < self.DOUBLE_CLICK_INTERVAL) {
458                     self._removeEdge(this);
459                 }
460                 self.__lastClick = Date.now();
461             })
462         },
463         _hitTestHandles: function (e) {
464             var mousePoisition = this._getRelativePosition(e);
465
466             //if (this._hover) {
467                 var position = this._getHandlePosition(this._hover);
468                 var renderedHandleSize = this.HANDLE_SIZE * this._cy.zoom(); //actual number of pixels that handle uses.
469                 if (VectorMath.distance(position, mousePoisition) < renderedHandleSize) {
470                     var handleType = this._hover.data().handleType;
471                     return {
472                         handle: this._handles[handleType]
473                     };
474                 }
475             //}
476         },
477         _getHandlePosition: function (target) { //returns the upper left point at which to begin drawing the handle
478             var position = target.renderedPosition();
479             var width = target.renderedWidth();
480             var height = target.renderedHeight();
481             var renderedHandleSize = this.HANDLE_SIZE * this._cy.zoom(); //actual number of pixels that handle will use.
482             var xpos = position.x + width / 2 - renderedHandleSize;
483             var ypos = position.y - height / 2;
484
485             return {x: xpos, y: ypos};
486         },
487         _getEdgeCSSByHandle: function (handle) {
488             var color = handle.lineColor ? handle.lineColor : handle.color;
489             return {
490                 "line-color": color,
491                 "target-arrow-color": color,
492                 "line-style": handle.lineStyle? handle.lineStyle: 'solid',
493                 "width": handle.width? handle.width : 3
494             };
495         },
496         _getRelativePosition: function (e) {
497             var containerPosition = this._$container.offset();
498             return {
499                 x: e.pageX - containerPosition.left,
500                 y: e.pageY - containerPosition.top
501             }
502         },
503         _checkSingleEdge: function (handle, node) {
504
505             if (handle.noMultigraph) {
506                 var edges = this._cy.edges("[source='" + this._hover.id() + "'][target='" + node.id() + "'],[source='" + node.id() + "'][target='" + this._hover.id() + "']");
507
508                 for (var i = 0; i < edges.length; i++) {
509                     return edges[i];
510                 }
511             } else {
512
513                 if (handle.single == false) {
514                     return;
515                 }
516                 var edges = this._cy.edges("[source='" + node.id() + "']");
517
518                 for (var i = 0; i < edges.length; i++) {
519                     if (edges[i].data()["type"] == handle.type) {
520                         return edges[i];
521                     }
522                 }
523             }
524         },
525         _redraw: function () {
526             this._clear();
527             if(this._tagMode) {
528                 this._cy.trigger('canvasredraw');
529             }
530         }
531     });
532
533 })(this);
534