Built motion from commit bcd50b9.|0.0.29
[motion.git] / public / assets / plugins / square / js / Graph.js
1 /**
2  * $Id: Graph.js,v 1.14 2013-02-16 10:19:54 gaudenz Exp $
3  * Copyright (c) 2006-2012, JGraph Ltd
4  */
5 /**
6  * Constructs a new graph instance. Note that the constructor does not take a
7  * container because the graph instance is needed for creating the UI, which
8  * in turn will create the container for the graph. Hence, the container is
9  * assigned later in EditorUi.
10  */
11 Graph = function(container, model, renderHint, stylesheet)
12 {
13         mxGraph.call(this, container, model, renderHint, stylesheet);
14         
15         this.setConnectable(true);
16         this.setDropEnabled(false);
17         this.setPanning(true);
18         this.setTooltips(!mxClient.IS_TOUCH);
19         this.setAllowLoops(false);
20         this.setMultigraph(false);
21         this.allowAutoPanning = true;
22         this.resetEdgesOnConnect = false;
23         this.constrainChildren = false;
24         
25         // Centers the port icon on the target port
26     this.connectionHandler.targetConnectImage = true;
27
28     // Does not allow dangling edges
29     this.setAllowDanglingEdges(false);
30
31         // Enables cloning of connection sources
32         this.connectionHandler.setCreateTarget(false);
33         
34         // Disables built-in connection starts
35         this.connectionHandler.isValidSource = function()
36         {
37                 return mxConnectionHandler.prototype.isValidSource.apply(this, arguments) && urlParams['connect'] != '2';
38         };
39
40         // Sets the style to be used when an elbow edge is double clicked
41         this.alternateEdgeStyle = 'vertical';
42
43         if (stylesheet == null)
44         {
45                 this.loadStylesheet();
46         }
47         
48         // Creates rubberband selection
49     var rubberband = new mxRubberband(this);
50     
51     this.getRubberband = function()
52     {
53         return rubberband;
54     };
55     
56     // Shows hand cursor while panning
57         this.panningHandler.addListener(mxEvent.PAN_START, mxUtils.bind(this, function()
58         {
59                 this.container.style.cursor = 'pointer';
60         }));
61                         
62         this.panningHandler.addListener(mxEvent.PAN_END, mxUtils.bind(this, function()
63         {
64                 this.container.style.cursor = 'default';
65         }));
66
67     // Adds support for HTML labels via style. Note: Currently, only the Java
68     // backend supports HTML labels but CSS support is limited to the following:
69     // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
70         this.isHtmlLabel = function(cell)
71         {
72                 var state = this.view.getState(cell);
73                 var style = (state != null) ? state.style : this.getCellStyle(cell);
74                 
75                 return style['html'] == '1' || style['whiteSpace'] == 'wrap';
76         };
77         
78         // HTML entities are displayed as plain text in wrapped plain text labels
79         this.cellRenderer.getLabelValue = function(state)
80         {
81                 var result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments);
82                 
83                 if (state.style['whiteSpace'] == 'wrap' && state.style['html'] != 1)
84                 {
85                         result = mxUtils.htmlEntities(result, false);
86                 }
87                 
88                 return result;
89         };
90         
91         // Unlocks all cells
92         this.isCellLocked = function(cell)
93         {
94                 return false;
95         };
96
97         // Tap and hold brings up context menu.
98         // Tolerance slightly below graph tolerance is better.
99         this.connectionHandler.tapAndHoldTolerance = 16;
100         
101         //  Tap and hold on background starts rubberband on cell starts connecting
102         var connectionHandlerTapAndHold = this.connectionHandler.tapAndHold;
103         this.connectionHandler.tapAndHold = function(me, state)
104         {
105                 if (state == null)
106                 {
107                         if (!this.graph.panningHandler.active)
108                         {
109                                 rubberband.start(me.getGraphX(), me.getGraphY());
110                                 this.graph.panningHandler.panningTrigger = false;
111                         }
112                 }
113                 else if (tapAndHoldStartsConnection)
114                 {
115                         connectionHandlerTapAndHold.apply(this, arguments);     
116                 }
117                 else if (this.graph.isCellSelected(state.cell) && this.graph.getSelectionCount() > 1)
118                 {
119                         this.graph.removeSelectionCell(state.cell);
120                 }
121         };
122
123         if (touchStyle)
124         {
125                 this.initTouch();
126         }
127 };
128
129 // Graph inherits from mxGraph
130 mxUtils.extend(Graph, mxGraph);
131
132 /**
133  * Allows to all values in fit.
134  */
135 Graph.prototype.minFitScale = null;
136
137 /**
138  * Allows to all values in fit.
139  */
140 Graph.prototype.maxFitScale = null;
141
142 /**
143  * Loads the stylesheet for this graph.
144  */
145 Graph.prototype.loadStylesheet = function()
146 {
147     var node = mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement();
148         var dec = new mxCodec(node.ownerDocument);
149         dec.decode(node, this.getStylesheet());
150 };
151
152 /**
153  * Inverts the elbow edge style without removing existing styles.
154  */
155 Graph.prototype.flipEdge = function(edge)
156 {
157         if (edge != null)
158         {
159                 var state = this.view.getState(edge);
160                 var style = (state != null) ? state.style : this.getCellStyle(edge);
161                 
162                 if (style != null)
163                 {
164                         var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
165                                 mxConstants.ELBOW_HORIZONTAL);
166                         var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
167                                 mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
168                         this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
169                 }
170         }
171 };
172
173 /**
174  * Sets the default edge for future connections.
175  */
176 Graph.prototype.setDefaultEdge = function(cell)
177 {
178         if (cell != null && this.getModel().isEdge(cell))
179         {
180                 // Take a snapshot of the cell at the moment of calling
181                 var proto = this.getModel().cloneCells([cell])[0];
182                 
183                 // Delete existing points
184                 if (proto.geometry != null)
185                 {
186                         proto.geometry.points = null;
187                 }
188                 
189                 // Delete entry-/exitXY styles
190                 var style = proto.getStyle();
191                 style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_X, null);
192                 style = mxUtils.setStyle(style, mxConstants.STYLE_ENTRY_Y, null);
193                 style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_X, null);
194                 style = mxUtils.setStyle(style, mxConstants.STYLE_EXIT_Y, null);
195                 proto.setStyle(style);
196                 
197                 // Uses edge template for connect preview
198                 this.connectionHandler.createEdgeState = function(me)
199                 {
200                 return this.graph.view.createState(proto);
201             };
202
203             // Creates new connections from edge template
204             this.connectionHandler.factoryMethod = function()
205             {
206                 return this.graph.cloneCells([proto])[0];
207             };
208         }
209 };
210
211 /**
212  * Disables folding for non-swimlanes.
213  */
214 Graph.prototype.isCellFoldable = function(cell)
215 {
216         return this.foldingEnabled && this.isSwimlane(cell);
217 };
218
219 /**
220  * Disables drill-down for non-swimlanes.
221  */
222 Graph.prototype.isValidRoot = function(cell)
223 {
224         return this.isSwimlane(cell);
225 };
226
227 /**
228  * Overrides createGroupCell to set the group style for new groups to 'group'.
229  */
230 Graph.prototype.createGroupCell = function()
231 {
232         var group = mxGraph.prototype.createGroupCell.apply(this, arguments);
233         group.setStyle('group');
234         
235         return group;
236 };
237
238 /**
239  * Giuseppe Careri
240  * Overrides tooltips to show position and size
241  */
242 Graph.prototype.getTooltipForCell = function(cell)
243 {
244         var tip = '';
245         
246         if (this.getModel().isVertex(cell))
247         {
248                 // var geo = this.getCellGeometry(cell);
249                 
250                 // var f2 = function(x)
251                 // {
252                 //      return Math.round(parseFloat(x) * 100) / 100;
253                 // };
254                 
255                 // if (geo != null)
256                 // {
257                 //      if (tip == null)
258                 //      {
259                 //              tip = '';
260                 //      }
261                 //      else if (tip.length > 0)
262                 //      {
263                 //              tip += '\n';
264                 //      }
265                         
266                 //      tip += 'X: ' + f2(geo.x) + '\nY: ' + f2(geo.y) + '\nW: ' + f2(geo.width) + '\nH: ' + f2(geo.height);
267                 // }
268                 
269                 tip += mxResources.get('description_' + cell.value.nodeName);
270         }
271         else if (this.getModel().isEdge(cell))
272         {
273                 tip = mxGraph.prototype.getTooltipForCell.apply(this, arguments);
274         }
275         
276         return tip;
277 };
278
279 /**
280  * Giuseppe Careri
281  * Returns the label for the given cell.
282  */
283 Graph.prototype.convertValueToString = function(cell)
284 {
285         if (cell.value != null && typeof(cell.value) == 'object')
286         {
287                 return cell.value.getAttribute('label');
288         }
289         
290         return mxGraph.prototype.convertValueToString.apply(this, arguments);
291 };
292
293 /**
294  * Giuseppe Careri
295  * Handles label changes for XML user objects.
296  */
297 Graph.prototype.cellLabelChanged = function(cell, value, autoSize)
298 {
299         if (cell.value != null && typeof(cell.value) == 'object')
300         {
301                 var tmp = cell.value.cloneNode(true);
302                 tmp.setAttribute('label', value);
303                 value = tmp;
304         }
305         
306         mxGraph.prototype.cellLabelChanged.apply(this, arguments);
307 };
308
309 /**
310  * Sets the link for the given cell.
311  */
312 Graph.prototype.setLinkForCell = function(cell, link)
313 {
314         var value = null;
315         
316         if (cell.value != null && typeof(cell.value) == 'object')
317         {
318                 value = cell.value.cloneNode(true);
319         }
320         else
321         {
322                 var doc = mxUtils.createXmlDocument();
323                 
324                 value = doc.createElement('UserObject');
325                 value.setAttribute('label', cell.value);
326         }
327         
328         if (link != null && link.length > 0)
329         {
330                 value.setAttribute('link', link);
331         }
332         else
333         {
334                 value.removeAttribute('link');
335         }
336         
337         this.model.setValue(cell, value);
338 };
339
340 /**
341  * Returns the link for the given cell.
342  */
343 Graph.prototype.getLinkForCell = function(cell)
344 {
345         if (cell.value != null && typeof(cell.value) == 'object')
346         {
347                 return cell.value.getAttribute('link');
348         }
349         
350         return null;
351 };
352
353 /**
354  * Customized graph for touch devices.
355  */
356 Graph.prototype.initTouch = function()
357 {
358         // Disables new connections via "hotspot"
359         this.connectionHandler.marker.isEnabled = function()
360         {
361                 return this.graph.connectionHandler.first != null;
362         };
363
364         // Hides menu when editing starts
365         this.addListener(mxEvent.START_EDITING, function(sender, evt)
366         {
367                 this.panningHandler.hideMenu();
368         });
369
370         // Context menu for touchstyle
371         var showMenu = false;
372         var menuCell = null;
373
374         // Checks if native hit detection did not return anything and does custom
375         // hit detection for edges to take into account the tolerance
376         this.updateMouseEvent = function(me)
377         {
378                 mxGraph.prototype.updateMouseEvent.apply(this, arguments);
379
380                 if (me.getState() == null)
381                 {
382                         var cell = this.getCellAt(me.graphX, me.graphY);
383                         
384                         if (this.getModel().isEdge(cell))
385                         {
386                                 me.state = this.view.getState(cell);
387                                 
388                                 if (me.state != null && me.state.shape != null)
389                                 {
390                                         this.container.style.cursor = me.state.shape.node.style.cursor;
391                                 }
392                         }
393                 }
394                 
395                 if (me.getState() == null)
396                 {
397                         this.container.style.cursor = 'default';
398                 }
399         };
400         
401         // Handles popup menu on touch devices (tap selected cell)
402         this.fireMouseEvent = function(evtName, me, sender)
403         {
404                 if (evtName == mxEvent.MOUSE_DOWN)
405                 {
406                         if (!this.panningHandler.isMenuShowing())
407                         {
408                                 menuCell = me.getCell();
409                                 showMenu = (menuCell != null) ? this.isCellSelected(menuCell) : this.isSelectionEmpty();
410                         }
411                         else
412                         {
413                                 showMenu = false;
414                                 menuCell = null;
415                         }
416                 }
417                 else if (evtName == mxEvent.MOUSE_UP)
418                 {
419                         if (showMenu && !this.isEditing())
420                         {
421                                 if (!this.panningHandler.isMenuShowing())
422                                 {
423                                         var x = mxEvent.getClientX(me.getEvent());
424                                         var y = mxEvent.getClientY(me.getEvent());
425                                         
426                                         this.panningHandler.popup(x + 16, y, menuCell, me.getEvent());
427                                 }
428                                 
429                                 showMenu = false;
430                                 menuCell = null;
431                                 me.consume();
432                                 
433                                 return;
434                         }
435                         
436                         showMenu = false;
437                         menuCell = null;
438                 }
439
440                 mxGraph.prototype.fireMouseEvent.apply(this, arguments);
441
442                 if (evtName == mxEvent.MOUSE_MOVE && me.isConsumed())
443                 {
444                         showMenu = false;
445                         menuCell = null;
446                 }
447         };
448 };
449
450 /**
451  * Implements touch devices.
452  */
453 (function()
454 {
455         // Enables rotation handle
456         mxVertexHandler.prototype.rotationEnabled = true;
457         
458         // Matches label positions of mxGraph 1.x
459         mxText.prototype.baseSpacingTop = 5;
460         mxText.prototype.baseSpacingBottom = 1;
461
462         // Touch-specific static overrides
463         if (touchStyle)
464         {
465                 // Sets constants for touch style
466                 mxConstants.HANDLE_SIZE = 16;
467                 mxConstants.LABEL_HANDLE_SIZE = 7;
468                 
469                 // Larger tolerance and grid for real touch devices
470                 if (mxClient.IS_TOUCH)
471                 {
472                         mxVertexHandler.prototype.tolerance = 4;
473                         mxEdgeHandler.prototype.tolerance = 6;
474                         Graph.prototype.tolerance = 14;
475                         Graph.prototype.gridSize = 20;
476                         
477                         // One finger pans (no rubberband selection) must start regardless of mouse button
478                         mxPanningHandler.prototype.selectOnPopup = false;
479                         mxPanningHandler.prototype.useLeftButtonForPanning = true;
480                         mxPanningHandler.prototype.isPanningTrigger = function(me)
481                         {
482                                 var evt = me.getEvent();
483                                 
484                                 return (this.useLeftButtonForPanning && (this.ignoreCell || me.getState() == null)/* &&
485                                                 mxEvent.isLeftMouseButton(evt)*/) || (mxEvent.isControlDown(evt) &&
486                                                 mxEvent.isShiftDown(evt)) || (this.usePopupTrigger &&
487                                                 mxEvent.isPopupTrigger(evt));
488                         };
489                 }
490                 
491                 // Don't clear selection if multiple cells selected
492                 var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown;
493                 mxGraphHandler.prototype.mouseDown = function(sender, me)
494                 {
495                         graphHandlerMouseDown.apply(this, arguments);
496
497                         if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1)
498                         {
499                                 this.delayedSelection = false;
500                         }
501                 };
502
503                 // Changes order of panninghandler
504                 Graph.prototype.createHandlers = function(container)
505                 {
506                         this.tooltipHandler = new mxTooltipHandler(this);
507                         this.tooltipHandler.setEnabled(false);
508                         // Selection cells first
509                         this.selectionCellsHandler = new mxSelectionCellsHandler(this);
510                         this.panningHandler = new mxPanningHandler(this);
511                         this.panningHandler.panningEnabled = false;
512                         this.connectionHandler = new mxConnectionHandler(this);
513                         this.connectionHandler.setEnabled(false);
514                         this.graphHandler = new mxGraphHandler(this);
515                 };
516
517                 // On connect the target is selected and we clone the cell of the preview edge for insert
518                 mxConnectionHandler.prototype.selectCells = function(edge, target)
519                 {
520                         if (touchStyle && target != null)
521                         {
522                                 this.graph.setSelectionCell(target);
523                         }
524                         else
525                         {
526                                 this.graph.setSelectionCell(edge);
527                         }
528                 };
529
530                 // Overrides double click handling to use the tolerance
531                 // FIXME: Double click on edges in iPad needs focus on textarea
532                 var graphDblClick = mxGraph.prototype.dblClick;
533                 Graph.prototype.dblClick = function(evt, cell)
534                 {
535                         if (cell == null)
536                         {
537                                 var pt = mxUtils.convertPoint(this.container,
538                                         mxEvent.getClientX(evt), mxEvent.getClientY(evt));
539                                 cell = this.getCellAt(pt.x, pt.y);
540                         }
541
542                         graphDblClick.call(this, evt, cell);
543                 };
544
545                 // Rounded edge and vertex handles
546                 var touchHandle = new mxImage(IMAGE_PATH + '/touch-handle.png', 16, 16);
547                 mxVertexHandler.prototype.handleImage = touchHandle;
548                 mxEdgeHandler.prototype.handleImage = touchHandle;
549                 mxOutline.prototype.sizerImage = touchHandle;
550                 
551                 // Pre-fetches touch handle
552                 new Image().src = touchHandle.src;
553
554                 // Adds connect icon to selected vertices
555                 var connectorSrc = IMAGE_PATH + '/touch-connector.png';
556                 
557                 var vertexHandlerInit = mxVertexHandler.prototype.init;
558                 mxVertexHandler.prototype.init = function()
559                 {
560                         vertexHandlerInit.apply(this, arguments);
561
562                         // Only show connector image on one cell and do not show on containers
563                         if (showConnectorImg && this.graph.connectionHandler.isEnabled() &&
564                                 this.graph.isCellConnectable(this.state.cell) &&
565                                 !this.graph.isValidRoot(this.state.cell) &&
566                                 this.graph.getSelectionCount() == 1)
567                         {
568                                 this.connectorImg = mxUtils.createImage(connectorSrc);
569                                 this.connectorImg.style.cursor = 'pointer';
570                                 this.connectorImg.style.width = '29px';
571                                 this.connectorImg.style.height = '29px';
572                                 this.connectorImg.style.position = 'absolute';
573                                 
574                                 if (!mxClient.IS_TOUCH)
575                                 {
576                                         this.connectorImg.setAttribute('title', mxResources.get('connect'));
577                                         mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
578                                 }
579
580                                 // Adds 2px tolerance
581                                 this.connectorImg.style.padding = '2px';
582                                 
583                                 // Starts connecting on touch/mouse down
584                                 mxEvent.addGestureListeners(this.connectorImg,
585                                         mxUtils.bind(this, function(evt)
586                                         {
587                                                 this.graph.panningHandler.hideMenu();
588                                                 var pt = mxUtils.convertPoint(this.graph.container,
589                                                                 mxEvent.getClientX(evt), mxEvent.getClientY(evt));
590                                                 this.graph.connectionHandler.start(this.state, pt.x, pt.y);
591                                                 this.graph.isMouseDown = true;
592                                                 mxEvent.consume(evt);
593                                         })
594                                 );
595
596                                 this.graph.container.appendChild(this.connectorImg);
597                         }
598
599                         this.redrawTools();
600                 };
601                 
602                 var vertexHandlerRedraw = mxVertexHandler.prototype.redraw;
603                 mxVertexHandler.prototype.redraw = function()
604                 {
605                         vertexHandlerRedraw.apply(this);
606                         this.redrawTools();
607                 };
608                 
609                 mxVertexHandler.prototype.redrawTools = function()
610                 {
611                         if (this.state != null && this.connectorImg != null)
612                         {
613                                 // Top right for single-sizer
614                                 if (mxVertexHandler.prototype.singleSizer)
615                                 {
616                                         this.connectorImg.style.left = (this.state.x + this.state.width - this.connectorImg.offsetWidth / 2) + 'px';
617                                         this.connectorImg.style.top = (this.state.y - this.connectorImg.offsetHeight / 2) + 'px';
618                                 }
619                                 else
620                                 {
621                                         this.connectorImg.style.left = (this.state.x + this.state.width + mxConstants.HANDLE_SIZE / 2 + 4/* - 2 padding*/) + 'px';
622                                         this.connectorImg.style.top = (this.state.y + (this.state.height - this.connectorImg.offsetHeight) / 2) + 'px';
623                                 }
624                         }
625                 };
626                 
627                 var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
628                 mxVertexHandler.prototype.destroy = function(sender, me)
629                 {
630                         vertexHandlerDestroy.apply(this, arguments);
631
632                         if (this.connectorImg != null)
633                         {
634                                 this.connectorImg.parentNode.removeChild(this.connectorImg);
635                                 this.connectorImg = null;
636                         }
637                 };
638                 
639                 // Pre-fetches touch connector
640                 new Image().src = connectorSrc;
641         }
642         else
643         {
644                 var img = new mxImage(IMAGE_PATH + '/connector.png', 15, 15);
645                 mxConnectionHandler.prototype.connectImage = img;
646
647                 // Pre-fetches img
648                 new Image().src = img.src;
649                 
650                 if (urlParams['connect'] == '2') // not touchStyle
651                 {
652                         var img = new mxImage(IMAGE_PATH + '/connector.png', 15, 15);
653                                         
654                         var vertexHandlerInit = mxVertexHandler.prototype.init;
655                         mxVertexHandler.prototype.init = function()
656                         {
657                                 vertexHandlerInit.apply(this, arguments);
658         
659                                 // Only show connector image on one cell and do not show on containers
660                                 if (showConnectorImg && this.graph.connectionHandler.isEnabled() &&
661                                         this.graph.isCellConnectable(this.state.cell) &&
662                                         !this.graph.isValidRoot(this.state.cell) &&
663                                         this.graph.getSelectionCount() == 1)
664                                 {
665                                         // Workaround for event redirection via image tag in quirks and IE8
666                                         if (mxClient.IS_IE && !mxClient.IS_SVG)
667                                         {
668                                                 this.connectorImg = document.createElement('div');
669                                                 this.connectorImg.style.backgroundImage = 'url(' + img.src + ')';
670                                                 this.connectorImg.style.backgroundPosition = 'center';
671                                                 this.connectorImg.style.backgroundRepeat = 'no-repeat';
672                                                 this.connectorImg.style.width = (img.width + 4) + 'px';
673                                                 this.connectorImg.style.height = (img.height + 4) + 'px';
674                                                 this.connectorImg.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
675                                         }
676                                         else
677                                         {
678                                                 this.connectorImg = mxUtils.createImage(img.src);
679                                                 this.connectorImg.style.width = img.width + 'px';
680                                                 this.connectorImg.style.height = img.height + 'px';
681                                         }
682                                         
683                                         this.connectorImg.style.cursor = 'pointer';
684                                         this.connectorImg.style.position = 'absolute';
685                                         this.connectorImg.setAttribute('title', mxResources.get('connect'));
686                                         mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
687                                         
688                                         // Adds 2px tolerance
689                                         this.connectorImg.style.padding = '2px';
690                                         
691                                         // Starts connecting on touch/mouse down
692                                         mxEvent.addListener(this.connectorImg, 'mousedown',
693                                                 mxUtils.bind(this, function(evt)
694                                                 {
695                                                         this.graph.panningHandler.hideMenu();
696                                                         var pt = mxUtils.convertPoint(this.graph.container,
697                                                                         mxEvent.getClientX(evt), mxEvent.getClientY(evt));
698                                                         this.graph.connectionHandler.start(this.state, pt.x, pt.y);
699                                                         this.graph.isMouseDown = true;
700                                                         mxEvent.consume(evt);
701                                                 })
702                                         );
703         
704                                         this.graph.container.appendChild(this.connectorImg);
705                                 }
706         
707                                 this.redrawTools();
708                         };
709                         
710                         var vertexHandlerRedraw = mxVertexHandler.prototype.redraw;
711                         mxVertexHandler.prototype.redraw = function()
712                         {
713                                 vertexHandlerRedraw.apply(this);
714                                 this.redrawTools();
715                         };
716                         
717                         mxVertexHandler.prototype.redrawTools = function()
718                         {
719                                 if (this.state != null && this.connectorImg != null)
720                                 {
721                                         // Top right for single-sizer
722                                         if (mxVertexHandler.prototype.singleSizer)
723                                         {
724                                                 this.connectorImg.style.left = (this.state.x + this.state.width - this.connectorImg.offsetWidth / 2) + 'px';
725                                                 this.connectorImg.style.top = (this.state.y - this.connectorImg.offsetHeight / 2) + 'px';
726                                         }
727                                         else
728                                         {
729                                                 this.connectorImg.style.left = (this.state.x + this.state.width + mxConstants.HANDLE_SIZE / 2 + 2/* - 2 padding*/) + 'px';
730                                                 this.connectorImg.style.top = (this.state.y + (this.state.height - this.connectorImg.offsetHeight) / 2) + 'px';
731                                         }
732                                 }
733                         };
734                         
735                         var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
736                         mxVertexHandler.prototype.destroy = function(sender, me)
737                         {
738                                 vertexHandlerDestroy.apply(this, arguments);
739         
740                                 if (this.connectorImg != null)
741                                 {
742                                         this.connectorImg.parentNode.removeChild(this.connectorImg);
743                                         this.connectorImg = null;
744                                 }
745                         };
746                 }
747         }
748 })();