1/**
2 * Copyright (c) 2006-2012, JGraph Ltd
3 */
4// Workaround for allowing target="_blank" in HTML sanitizer
5// see https://code.google.com/p/google-caja/issues/detail?can=2&q=&colspec=ID%20Type%20Status%20Priority%20Owner%20Summary&groupby=&sort=&id=1296
6if (typeof html4 !== 'undefined')
7{
8	html4.ATTRIBS['a::target'] = 0;
9	html4.ATTRIBS['source::src'] = 0;
10	html4.ATTRIBS['video::src'] = 0;
11	// Would be nice for tooltips but probably a security risk...
12	//html4.ATTRIBS['video::autoplay'] = 0;
13	//html4.ATTRIBS['video::autobuffer'] = 0;
14}
15
16// Workaround for handling named HTML entities in mxUtils.parseXml
17// LATER: How to configure DOMParser to just ignore all entities?
18(function()
19{
20	var entities = [
21		['nbsp', '160'],
22		['shy', '173']
23    ];
24
25	var parseXml = mxUtils.parseXml;
26
27	mxUtils.parseXml = function(text)
28	{
29		for (var i = 0; i < entities.length; i++)
30	    {
31	        text = text.replace(new RegExp(
32	        	'&' + entities[i][0] + ';', 'g'),
33		        '&#' + entities[i][1] + ';');
34	    }
35
36		return parseXml(text);
37	};
38})();
39
40// Shim for missing toISOString in older versions of IE
41// See https://stackoverflow.com/questions/12907862
42if (!Date.prototype.toISOString)
43{
44    (function()
45    {
46        function pad(number)
47        {
48            var r = String(number);
49
50            if (r.length === 1)
51            {
52                r = '0' + r;
53            }
54
55            return r;
56        };
57
58        Date.prototype.toISOString = function()
59        {
60            return this.getUTCFullYear()
61                + '-' + pad( this.getUTCMonth() + 1 )
62                + '-' + pad( this.getUTCDate() )
63                + 'T' + pad( this.getUTCHours() )
64                + ':' + pad( this.getUTCMinutes() )
65                + ':' + pad( this.getUTCSeconds() )
66                + '.' + String( (this.getUTCMilliseconds()/1000).toFixed(3) ).slice( 2, 5 )
67                + 'Z';
68        };
69    }());
70}
71
72// Shim for Date.now()
73if (!Date.now)
74{
75	Date.now = function()
76	{
77		return new Date().getTime();
78	};
79}
80
81// Polyfill for Uint8Array.from in IE11 used in Graph.decompress
82// See https://stackoverflow.com/questions/36810940/alternative-or-polyfill-for-array-from-on-the-internet-explorer
83if (!Uint8Array.from) {
84  Uint8Array.from = (function () {
85    var toStr = Object.prototype.toString;
86    var isCallable = function (fn) {
87      return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
88    };
89    var toInteger = function (value) {
90      var number = Number(value);
91      if (isNaN(number)) { return 0; }
92      if (number === 0 || !isFinite(number)) { return number; }
93      return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
94    };
95    var maxSafeInteger = Math.pow(2, 53) - 1;
96    var toLength = function (value) {
97      var len = toInteger(value);
98      return Math.min(Math.max(len, 0), maxSafeInteger);
99    };
100
101    // The length property of the from method is 1.
102    return function from(arrayLike/*, mapFn, thisArg */) {
103      // 1. Let C be the this value.
104      var C = this;
105
106      // 2. Let items be ToObject(arrayLike).
107      var items = Object(arrayLike);
108
109      // 3. ReturnIfAbrupt(items).
110      if (arrayLike == null) {
111        throw new TypeError("Array.from requires an array-like object - not null or undefined");
112      }
113
114      // 4. If mapfn is undefined, then let mapping be false.
115      var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
116      var T;
117      if (typeof mapFn !== 'undefined') {
118        // 5. else
119        // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
120        if (!isCallable(mapFn)) {
121          throw new TypeError('Array.from: when provided, the second argument must be a function');
122        }
123
124        // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
125        if (arguments.length > 2) {
126          T = arguments[2];
127        }
128      }
129
130      // 10. Let lenValue be Get(items, "length").
131      // 11. Let len be ToLength(lenValue).
132      var len = toLength(items.length);
133
134      // 13. If IsConstructor(C) is true, then
135      // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
136      // 14. a. Else, Let A be ArrayCreate(len).
137      var A = isCallable(C) ? Object(new C(len)) : new Array(len);
138
139      // 16. Let k be 0.
140      var k = 0;
141      // 17. Repeat, while k < len… (also steps a - h)
142      var kValue;
143      while (k < len) {
144        kValue = items[k];
145        if (mapFn) {
146          A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
147        } else {
148          A[k] = kValue;
149        }
150        k += 1;
151      }
152      // 18. Let putStatus be Put(A, "length", len, true).
153      A.length = len;
154      // 20. Return A.
155      return A;
156    };
157  }());
158}
159
160// Changes default colors
161/**
162 * Measurements Units
163 */
164mxConstants.POINTS = 1;
165mxConstants.MILLIMETERS = 2;
166mxConstants.INCHES = 3;
167mxConstants.METERS = 4;
168/**
169 * This ratio is with page scale 1
170 */
171mxConstants.PIXELS_PER_MM = 3.937;
172mxConstants.PIXELS_PER_INCH = 100;
173
174mxConstants.SHADOW_OPACITY = 0.25;
175mxConstants.SHADOWCOLOR = '#000000';
176mxConstants.VML_SHADOWCOLOR = '#d0d0d0';
177mxGraph.prototype.pageBreakColor = '#c0c0c0';
178mxGraph.prototype.pageScale = 1;
179
180// Letter page format is default in US, Canada and Mexico
181(function()
182{
183	try
184	{
185		if (navigator != null && navigator.language != null)
186		{
187			var lang = navigator.language.toLowerCase();
188			mxGraph.prototype.pageFormat = (lang === 'en-us' || lang === 'en-ca' || lang === 'es-mx') ?
189				mxConstants.PAGE_FORMAT_LETTER_PORTRAIT : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
190		}
191	}
192	catch (e)
193	{
194		// ignore
195	}
196})();
197
198// Matches label positions of mxGraph 1.x
199mxText.prototype.baseSpacingTop = 5;
200mxText.prototype.baseSpacingBottom = 1;
201
202// Keeps edges between relative child cells inside parent
203mxGraphModel.prototype.ignoreRelativeEdgeParent = false;
204
205// Defines grid properties
206mxGraphView.prototype.gridImage = (mxClient.IS_SVG) ? 'data:image/gif;base64,R0lGODlhCgAKAJEAAAAAAP///8zMzP///yH5BAEAAAMALAAAAAAKAAoAAAIJ1I6py+0Po2wFADs=' :
207	IMAGE_PATH + '/grid.gif';
208mxGraphView.prototype.gridSteps = 4;
209mxGraphView.prototype.minGridSize = 4;
210
211// UrlParams is null in embed mode
212mxGraphView.prototype.defaultGridColor = '#d0d0d0';
213mxGraphView.prototype.defaultDarkGridColor = '#6e6e6e';
214mxGraphView.prototype.gridColor = mxGraphView.prototype.defaultGridColor;
215
216//Units
217mxGraphView.prototype.unit = mxConstants.POINTS;
218
219mxGraphView.prototype.setUnit = function(unit)
220{
221	if (this.unit != unit)
222	{
223	    this.unit = unit;
224
225	    this.fireEvent(new mxEventObject('unitChanged', 'unit', unit));
226	}
227};
228
229// Alternative text for unsupported foreignObjects
230mxSvgCanvas2D.prototype.foAltText = '[Not supported by viewer]';
231
232// Hook for custom constraints
233mxShape.prototype.getConstraints = function(style, w, h)
234{
235	return null;
236};
237
238// Override for clipSvg style.
239mxImageShape.prototype.getImageDataUri = function()
240{
241	var src = this.image;
242
243	if (src.substring(0, 26) == 'data:image/svg+xml;base64,' && this.style != null &&
244		mxUtils.getValue(this.style, 'clipSvg', '0') == '1')
245	{
246		if (this.clippedSvg == null || this.clippedImage != src)
247		{
248			this.clippedSvg = Graph.clipSvgDataUri(src);
249			this.clippedImage = src;
250		}
251
252		src = this.clippedSvg;
253	}
254
255	return src;
256};
257
258
259/**
260 * Constructs a new graph instance. Note that the constructor does not take a
261 * container because the graph instance is needed for creating the UI, which
262 * in turn will create the container for the graph. Hence, the container is
263 * assigned later in EditorUi.
264 */
265/**
266 * Defines graph class.
267 */
268Graph = function(container, model, renderHint, stylesheet, themes, standalone)
269{
270	mxGraph.call(this, container, model, renderHint, stylesheet);
271
272	this.themes = themes || this.defaultThemes;
273	this.currentEdgeStyle = mxUtils.clone(this.defaultEdgeStyle);
274	this.currentVertexStyle = mxUtils.clone(this.defaultVertexStyle);
275	this.standalone = (standalone != null) ? standalone : false;
276
277	// Sets the base domain URL and domain path URL for relative links.
278	var b = this.baseUrl;
279	var p = b.indexOf('//');
280	this.domainUrl = '';
281	this.domainPathUrl = '';
282
283	if (p > 0)
284	{
285		var d = b.indexOf('/', p + 2);
286
287		if (d > 0)
288		{
289			this.domainUrl = b.substring(0, d);
290		}
291
292		d = b.lastIndexOf('/');
293
294		if (d > 0)
295		{
296			this.domainPathUrl = b.substring(0, d + 1);
297		}
298	}
299
300    // Adds support for HTML labels via style. Note: Currently, only the Java
301    // backend supports HTML labels but CSS support is limited to the following:
302    // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
303	// TODO: Wrap should not affect isHtmlLabel output (should be handled later)
304	this.isHtmlLabel = function(cell)
305	{
306		var style = this.getCurrentCellStyle(cell);
307
308		return (style != null) ? (style['html'] == '1' || style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') : false;
309	};
310
311	// Implements a listener for hover and click handling on edges
312	if (this.edgeMode)
313	{
314		var start = {
315			point: null,
316			event: null,
317			state: null,
318			handle: null,
319			selected: false
320		};
321
322		// Uses this event to process mouseDown to check the selection state before it is changed
323		this.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt)
324		{
325			if (evt.getProperty('eventName') == 'mouseDown' && this.isEnabled())
326			{
327				var me = evt.getProperty('event');
328		    	var state = me.getState();
329				var s = this.view.scale;
330
331		    	if (!mxEvent.isAltDown(me.getEvent()) && state != null)
332		    	{
333		    		// Checks if state was removed in call to stopEditing above
334		    		if (this.model.isEdge(state.cell))
335		    		{
336		    			start.point = new mxPoint(me.getGraphX(), me.getGraphY());
337		    			start.selected = this.isCellSelected(state.cell);
338		    			start.state = state;
339		    			start.event = me;
340
341    					if (state.text != null && state.text.boundingBox != null &&
342    						mxUtils.contains(state.text.boundingBox, me.getGraphX(), me.getGraphY()))
343    					{
344    						start.handle = mxEvent.LABEL_HANDLE;
345    					}
346    					else
347    					{
348			    			var handler = this.selectionCellsHandler.getHandler(state.cell);
349
350			    			if (handler != null && handler.bends != null && handler.bends.length > 0)
351			    			{
352			    				start.handle = handler.getHandleForEvent(me);
353			    			}
354    					}
355		    		}
356		    		else if (!this.panningHandler.isActive() && !mxEvent.isControlDown(me.getEvent()))
357		    		{
358			   			var handler = this.selectionCellsHandler.getHandler(state.cell);
359
360			   			// Cell handles have precedence over row and col resize
361		    			if (handler == null || handler.getHandleForEvent(me) == null)
362		    			{
363				    		var box = new mxRectangle(me.getGraphX() - 1, me.getGraphY() - 1);
364			    			box.grow(mxEvent.isTouchEvent(me.getEvent()) ?
365			    				mxShape.prototype.svgStrokeTolerance - 1 :
366			    				(mxShape.prototype.svgStrokeTolerance + 1) / 2);
367
368			    			if (this.isTableCell(state.cell) && !this.isCellSelected(state.cell))
369			    			{
370			    				var row = this.model.getParent(state.cell);
371			    				var table = this.model.getParent(row);
372
373			    				if (!this.isCellSelected(table))
374			    				{
375									var geo = this.getCellGeometry(state.cell);
376									var g = (geo.alternateBounds != null) ? geo.alternateBounds : geo;
377
378				    				if ((mxUtils.intersects(box, new mxRectangle(state.x, state.y - 2, g.width * s, 3)) &&
379				    					this.model.getChildAt(table, 0) != row) || mxUtils.intersects(box, new mxRectangle(
380				    					state.x, state.y + g.height - 2, g.width, 3)) ||
381					    				(mxUtils.intersects(box, new mxRectangle(state.x - 2, state.y, 2, g.height * s)) &&
382					    				this.model.getChildAt(row, 0) != state.cell) || mxUtils.intersects(box, new mxRectangle(
383				    					state.x + g.width * s - 2, state.y, 2, g.height * s)))
384			    					{
385				    					var wasSelected = this.selectionCellsHandler.isHandled(table);
386				    					this.selectCellForEvent(table, me.getEvent());
387						    			handler = this.selectionCellsHandler.getHandler(table);
388
389						    			if (handler != null)
390						    			{
391						    				var handle = handler.getHandleForEvent(me);
392
393						    				if (handle != null)
394						    				{
395						    					handler.start(me.getGraphX(), me.getGraphY(), handle);
396						    					handler.blockDelayedSelection = !wasSelected;
397						    					me.consume();
398						    				}
399						    			}
400			    					}
401			    				}
402		    				}
403
404			    			// Hover for swimlane start sizes inside tables
405				    		var current = state;
406
407				    		while (!me.isConsumed() && current != null && (this.isTableCell(current.cell) ||
408				    			this.isTableRow(current.cell) || this.isTable(current.cell)))
409				    		{
410					    		if (this.isSwimlane(current.cell))
411					    		{
412					    			var offset = this.getActualStartSize(current.cell);
413
414		    						if (((offset.x > 0 || offset.width > 0) && mxUtils.intersects(box, new mxRectangle(
415		    							current.x + (offset.x - offset.width - 1) * s + ((offset.x == 0) ? current.width : 0),
416		    							current.y, 1, current.height))) || ((offset.y > 0 || offset.height > 0) &&
417		    							mxUtils.intersects(box, new mxRectangle(current.x, current.y + (offset.y -
418		    							offset.height - 1) * s + ((offset.y == 0) ? current.height : 0), current.width, 1))))
419		    						{
420		    							this.selectCellForEvent(current.cell, me.getEvent());
421						    			handler = this.selectionCellsHandler.getHandler(current.cell);
422
423						    			if (handler != null)
424						    			{
425						    				// Swimlane start size handle is last custom handle
426						    				var handle = mxEvent.CUSTOM_HANDLE - handler.customHandles.length + 1;
427					    					handler.start(me.getGraphX(), me.getGraphY(), handle);
428					    					me.consume();
429						    			}
430		    						}
431					    		}
432
433					    		current = this.view.getState(this.model.getParent(current.cell));
434				    		}
435		    			}
436		    		}
437		    	}
438			}
439		}));
440
441		var mouseDown = null;
442
443		this.addMouseListener(
444		{
445			mouseDown: function(sender, me) {},
446		    mouseMove: mxUtils.bind(this, function(sender, me)
447		    {
448		    	// Checks if any other handler is active
449		    	var handlerMap = this.selectionCellsHandler.handlers.map;
450
451		    	for (var key in handlerMap)
452		    	{
453		    		if (handlerMap[key].index != null)
454		    		{
455		    			return;
456		    		}
457		    	}
458
459		    	if (this.isEnabled() && !this.panningHandler.isActive() && !mxEvent.isAltDown(me.getEvent()))
460		    	{
461		    		var tol = this.tolerance;
462
463			    	if (start.point != null && start.state != null && start.event != null)
464			    	{
465			    		var state = start.state;
466
467			    		if (Math.abs(start.point.x - me.getGraphX()) > tol ||
468			    			Math.abs(start.point.y - me.getGraphY()) > tol)
469			    		{
470			    			var handler = this.selectionCellsHandler.getHandler(state.cell);
471
472			    			if (handler == null && this.model.isEdge(state.cell))
473			    			{
474			    				handler = this.createHandler(state);
475			    			}
476
477			    			if (handler != null && handler.bends != null && handler.bends.length > 0)
478			    			{
479			    				var handle = handler.getHandleForEvent(start.event);
480			    				var edgeStyle = this.view.getEdgeStyle(state);
481			    				var entity = edgeStyle == mxEdgeStyle.EntityRelation;
482
483			    				// Handles special case where label was clicked on unselected edge in which
484			    				// case the label will be moved regardless of the handle that is returned
485			    				if (!start.selected && start.handle == mxEvent.LABEL_HANDLE)
486			    				{
487			    					handle = start.handle;
488			    				}
489
490	    						if (!entity || handle == 0 || handle == handler.bends.length - 1 || handle == mxEvent.LABEL_HANDLE)
491	    						{
492				    				// Source or target handle or connected for direct handle access or orthogonal line
493				    				// with just two points where the central handle is moved regardless of mouse position
494				    				if (handle == mxEvent.LABEL_HANDLE || handle == 0 || state.visibleSourceState != null ||
495				    					handle == handler.bends.length - 1 || state.visibleTargetState != null)
496				    				{
497				    					if (!entity && handle != mxEvent.LABEL_HANDLE)
498				    					{
499					    					var pts = state.absolutePoints;
500
501					    					// Default case where handles are at corner points handles
502					    					// drag of corner as drag of existing point
503					    					if (pts != null && ((edgeStyle == null && handle == null) ||
504					    						edgeStyle == mxEdgeStyle.OrthConnector))
505					    					{
506					    						// Does not use handles if they were not initially visible
507					    						handle = start.handle;
508
509					    						if (handle == null)
510					    						{
511							    					var box = new mxRectangle(start.point.x, start.point.y);
512							    					box.grow(mxEdgeHandler.prototype.handleImage.width / 2);
513
514					    							if (mxUtils.contains(box, pts[0].x, pts[0].y))
515					    							{
516						    							// Moves source terminal handle
517					    								handle = 0;
518					    							}
519					    							else if (mxUtils.contains(box, pts[pts.length - 1].x, pts[pts.length - 1].y))
520					    							{
521					    								// Moves target terminal handle
522					    								handle = handler.bends.length - 1;
523					    							}
524					    							else
525					    							{
526							    						// Checks if edge has no bends
527							    						var nobends = edgeStyle != null && (pts.length == 2 || (pts.length == 3 &&
528						    								((Math.round(pts[0].x - pts[1].x) == 0 && Math.round(pts[1].x - pts[2].x) == 0) ||
529						    								(Math.round(pts[0].y - pts[1].y) == 0 && Math.round(pts[1].y - pts[2].y) == 0))));
530
531						    							if (nobends)
532								    					{
533									    					// Moves central handle for straight orthogonal edges
534								    						handle = 2;
535								    					}
536								    					else
537									    				{
538										    				// Finds and moves vertical or horizontal segment
539									    					handle = mxUtils.findNearestSegment(state, start.point.x, start.point.y);
540
541									    					// Converts segment to virtual handle index
542									    					if (edgeStyle == null)
543									    					{
544									    						handle = mxEvent.VIRTUAL_HANDLE - handle;
545									    					}
546									    					// Maps segment to handle
547									    					else
548									    					{
549									    						handle += 1;
550									    					}
551									    				}
552					    							}
553					    						}
554					    					}
555
556						    				// Creates a new waypoint and starts moving it
557						    				if (handle == null)
558						    				{
559						    					handle = mxEvent.VIRTUAL_HANDLE;
560						    				}
561				    					}
562
563				    					handler.start(me.getGraphX(), me.getGraphX(), handle);
564				    					me.consume();
565
566				    					// Removes preview rectangle in graph handler
567				    					this.graphHandler.reset();
568				    				}
569	    						}
570	    						else if (entity && (state.visibleSourceState != null || state.visibleTargetState != null))
571	    						{
572	    							// Disables moves on entity to make it consistent
573			    					this.graphHandler.reset();
574	    							me.consume();
575	    						}
576			    			}
577
578			    			if (handler != null)
579			    			{
580				    			// Lazy selection for edges inside groups
581			    				if (this.selectionCellsHandler.isHandlerActive(handler))
582			    				{
583					    			if (!this.isCellSelected(state.cell))
584					    			{
585					    				this.selectionCellsHandler.handlers.put(state.cell, handler);
586					    				this.selectCellForEvent(state.cell, me.getEvent());
587					    			}
588			    				}
589				    			else if (!this.isCellSelected(state.cell))
590				    			{
591				    				// Destroy temporary handler
592				    				handler.destroy();
593				    			}
594			    			}
595
596			    			// Reset start state
597		    				start.selected = false;
598		    				start.handle = null;
599	    					start.state = null;
600		    				start.event = null;
601		    				start.point = null;
602			    		}
603			    	}
604			    	else
605			    	{
606			    		// Updates cursor for unselected edges under the mouse
607				    	var state = me.getState();
608
609				    	if (state != null && this.isCellEditable(state.cell))
610				    	{
611				    		var cursor = null;
612
613				    		// Checks if state was removed in call to stopEditing above
614				    		if (this.model.isEdge(state.cell))
615				    		{
616				    			var box = new mxRectangle(me.getGraphX(), me.getGraphY());
617		    					box.grow(mxEdgeHandler.prototype.handleImage.width / 2);
618			    				var pts = state.absolutePoints;
619
620			    				if (pts != null)
621			    				{
622			    					if (state.text != null && state.text.boundingBox != null &&
623			    						mxUtils.contains(state.text.boundingBox, me.getGraphX(), me.getGraphY()))
624			    					{
625			    						cursor = 'move';
626			    					}
627			    					else if (mxUtils.contains(box, pts[0].x, pts[0].y) ||
628			    						mxUtils.contains(box, pts[pts.length - 1].x, pts[pts.length - 1].y))
629			    					{
630			    						cursor = 'pointer';
631			    					}
632			    					else if (state.visibleSourceState != null || state.visibleTargetState != null)
633			    					{
634		    							// Moving is not allowed for entity relation but still indicate hover state
635			    						var tmp = this.view.getEdgeStyle(state);
636			    						cursor = 'crosshair';
637
638			    						if (tmp != mxEdgeStyle.EntityRelation && this.isOrthogonal(state))
639						    			{
640						    				var idx = mxUtils.findNearestSegment(state, me.getGraphX(), me.getGraphY());
641
642						    				if (idx < pts.length - 1 && idx >= 0)
643						    				{
644					    						cursor = (Math.round(pts[idx].x - pts[idx + 1].x) == 0) ?
645					    							'col-resize' : 'row-resize';
646						    				}
647						    			}
648			    					}
649			    				}
650				    		}
651				    		else if (!mxEvent.isControlDown(me.getEvent()))
652				    		{
653				    			var box = new mxRectangle(me.getGraphX() - 1, me.getGraphY() - 1);
654			    				box.grow(mxShape.prototype.svgStrokeTolerance / 2);
655
656					    		if (this.isTableCell(state.cell))
657					    		{
658				    				var row = this.model.getParent(state.cell);
659			    					var table = this.model.getParent(row);
660
661			    					if (!this.isCellSelected(table))
662			    					{
663				    					if ((mxUtils.intersects(box, new mxRectangle(state.x - 2, state.y, 2, state.height)) &&
664						    				this.model.getChildAt(row, 0) != state.cell) || mxUtils.intersects(box,
665						    				new mxRectangle(state.x + state.width - 2, state.y, 2, state.height)))
666				    					{
667						    				cursor ='col-resize';
668				    					}
669				    					else if ((mxUtils.intersects(box, new mxRectangle(state.x, state.y - 2, state.width, 3)) &&
670					    					this.model.getChildAt(table, 0) != row) || mxUtils.intersects(box,
671					    					new mxRectangle(state.x, state.y + state.height - 2, state.width, 3)))
672				    					{
673						    				cursor ='row-resize';
674				    					}
675			    					}
676					    		}
677
678					    		// Hover for swimlane start sizes inside tables
679					    		var current = state;
680
681					    		while (cursor == null && current != null && (this.isTableCell(current.cell) ||
682					    			this.isTableRow(current.cell) || this.isTable(current.cell)))
683					    		{
684						    		if (this.isSwimlane(current.cell))
685						    		{
686						    			var offset = this.getActualStartSize(current.cell);
687						    			var s = this.view.scale;
688
689			    						if ((offset.x > 0 || offset.width > 0) && mxUtils.intersects(box, new mxRectangle(
690			    							current.x + (offset.x - offset.width - 1) * s + ((offset.x == 0) ? current.width * s : 0),
691			    							current.y, 1, current.height)))
692			    						{
693				    						cursor ='col-resize';
694			    						}
695			    						else if ((offset.y > 0 || offset.height > 0) && mxUtils.intersects(box, new mxRectangle(
696			    							current.x, current.y + (offset.y - offset.height - 1) * s + ((offset.y == 0) ? current.height : 0),
697			    							current.width, 1)))
698			    						{
699				    						cursor ='row-resize';
700			    						}
701						    		}
702
703						    		current = this.view.getState(this.model.getParent(current.cell));
704					    		}
705				    		}
706
707		    				if (cursor != null)
708		    				{
709		    					state.setCursor(cursor);
710		    				}
711				    	}
712			    	}
713		    	}
714		    }),
715		    mouseUp: mxUtils.bind(this, function(sender, me)
716		    {
717				start.state = null;
718				start.event = null;
719				start.point = null;
720				start.handle = null;
721		    })
722		});
723	}
724
725	this.cellRenderer.minSvgStrokeWidth = 0.1;
726
727	// HTML entities are displayed as plain text in wrapped plain text labels
728	this.cellRenderer.getLabelValue = function(state)
729	{
730		var result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments);
731
732		if (state.view.graph.isHtmlLabel(state.cell))
733		{
734			if (state.style['html'] != 1)
735			{
736				result = mxUtils.htmlEntities(result, false);
737			}
738			else
739			{
740				result = state.view.graph.sanitizeHtml(result);
741			}
742		}
743
744		return result;
745	};
746
747	// All code below not available and not needed in embed mode
748	if (typeof mxVertexHandler !== 'undefined')
749	{
750		this.setConnectable(true);
751		this.setDropEnabled(true);
752		this.setPanning(true);
753		this.setTooltips(true);
754		this.setAllowLoops(true);
755		this.allowAutoPanning = true;
756		this.resetEdgesOnConnect = false;
757		this.constrainChildren = false;
758		this.constrainRelativeChildren = true;
759
760		// Do not scroll after moving cells
761		this.graphHandler.scrollOnMove = false;
762		this.graphHandler.scaleGrid = true;
763
764		// Disables cloning of connection sources by default
765		this.connectionHandler.setCreateTarget(false);
766		this.connectionHandler.insertBeforeSource = true;
767
768		// Disables built-in connection starts
769		this.connectionHandler.isValidSource = function(cell, me)
770		{
771			return false;
772		};
773
774		// Sets the style to be used when an elbow edge is double clicked
775		this.alternateEdgeStyle = 'vertical';
776
777		if (stylesheet == null)
778		{
779			this.loadStylesheet();
780		}
781
782		// Adds page centers to the guides for moving cells
783		var graphHandlerGetGuideStates = this.graphHandler.getGuideStates;
784		this.graphHandler.getGuideStates = function()
785		{
786			var result = graphHandlerGetGuideStates.apply(this, arguments);
787
788			// Create virtual cell state for page centers
789			if (this.graph.pageVisible)
790			{
791				var guides = [];
792
793				var pf = this.graph.pageFormat;
794				var ps = this.graph.pageScale;
795				var pw = pf.width * ps;
796				var ph = pf.height * ps;
797				var t = this.graph.view.translate;
798				var s = this.graph.view.scale;
799
800				var layout = this.graph.getPageLayout();
801
802				for (var i = 0; i < layout.width; i++)
803				{
804					guides.push(new mxRectangle(((layout.x + i) * pw + t.x) * s,
805						(layout.y * ph + t.y) * s, pw * s, ph * s));
806				}
807
808				for (var j = 1; j < layout.height; j++)
809				{
810					guides.push(new mxRectangle((layout.x * pw + t.x) * s,
811						((layout.y + j) * ph + t.y) * s, pw * s, ph * s));
812				}
813
814				// Page center guides have precedence over normal guides
815				result = guides.concat(result);
816			}
817
818			return result;
819		};
820
821		// Overrides zIndex for dragElement
822		mxDragSource.prototype.dragElementZIndex = mxPopupMenu.prototype.zIndex;
823
824		// Overrides color for virtual guides for page centers
825		mxGuide.prototype.getGuideColor = function(state, horizontal)
826		{
827			return (state.cell == null) ? '#ffa500' /* orange */ : mxConstants.GUIDE_COLOR;
828		};
829
830		// Changes color of move preview for black backgrounds
831		this.graphHandler.createPreviewShape = function(bounds)
832		{
833			this.previewColor = (this.graph.background == '#000000') ? '#ffffff' : mxGraphHandler.prototype.previewColor;
834
835			return mxGraphHandler.prototype.createPreviewShape.apply(this, arguments);
836		};
837
838		// Handles parts of cells by checking if part=1 is in the style and returning the parent
839		// if the parent is not already in the list of cells. container style is used to disable
840		// step into swimlanes and dropTarget style is used to disable acting as a drop target.
841		// LATER: Handle recursive parts
842		var graphHandlerGetCells = this.graphHandler.getCells;
843
844		this.graphHandler.getCells = function(initialCell)
845		{
846		    var cells = graphHandlerGetCells.apply(this, arguments);
847		    var lookup = new mxDictionary();
848		    var newCells = [];
849
850		    for (var i = 0; i < cells.length; i++)
851		    {
852		    	// Propagates to composite parents or moves selected table rows
853		    	var cell = (this.graph.isTableCell(initialCell) &&
854		    		this.graph.isTableCell(cells[i]) &&
855		    		this.graph.isCellSelected(cells[i])) ?
856		    		this.graph.model.getParent(cells[i]) :
857		    		((this.graph.isTableRow(initialCell) &&
858		    		this.graph.isTableRow(cells[i]) &&
859		    		this.graph.isCellSelected(cells[i])) ?
860		    		cells[i] : this.graph.getCompositeParent(cells[i]));
861
862		    	if (cell != null && !lookup.get(cell))
863		    	{
864		    		lookup.put(cell, true);
865		            newCells.push(cell);
866		        }
867		    }
868
869		    return newCells;
870		};
871
872		// Handles parts and selected rows in tables of cells for drag and drop
873		var graphHandlerStart = this.graphHandler.start;
874
875		this.graphHandler.start = function(cell, x, y, cells)
876		{
877			// Propagates to selected table row to start move
878			var ignoreParent = false;
879
880		    if (this.graph.isTableCell(cell))
881		    {
882		    	if (!this.graph.isCellSelected(cell))
883		    	{
884		    		cell = this.graph.model.getParent(cell);
885		    	}
886		    	else
887		    	{
888		    		ignoreParent = true;
889		    	}
890		    }
891
892		    if (!ignoreParent && (!this.graph.isTableRow(cell) || !this.graph.isCellSelected(cell)))
893		    {
894		    	cell = this.graph.getCompositeParent(cell);
895		    }
896
897			graphHandlerStart.apply(this, arguments);
898		};
899
900		// Handles parts of cells when cloning the source for new connections
901		this.connectionHandler.createTargetVertex = function(evt, source)
902		{
903			source = this.graph.getCompositeParent(source);
904
905			return mxConnectionHandler.prototype.createTargetVertex.apply(this, arguments);
906		};
907
908	    var rubberband = new mxRubberband(this);
909
910	    this.getRubberband = function()
911	    {
912	    	return rubberband;
913	    };
914
915	    // Timer-based activation of outline connect in connection handler
916	    var startTime = new Date().getTime();
917	    var timeOnTarget = 0;
918
919	    var connectionHandlerMouseMove = this.connectionHandler.mouseMove;
920
921	    this.connectionHandler.mouseMove = function()
922	    {
923	    	var prev = this.currentState;
924	    	connectionHandlerMouseMove.apply(this, arguments);
925
926	    	if (prev != this.currentState)
927	    	{
928	    		startTime = new Date().getTime();
929	    		timeOnTarget = 0;
930	    	}
931	    	else
932	    	{
933		    	timeOnTarget = new Date().getTime() - startTime;
934	    	}
935	    };
936
937	    // Activates outline connect after 1500ms with touch event or if alt is pressed inside the shape
938	    // outlineConnect=0 is a custom style that means do not connect to strokes inside the shape,
939	    // or in other words, connect to the shape's perimeter if the highlight is under the mouse
940	    // (the name is because the highlight, including all strokes, is called outline in the code)
941	    var connectionHandleIsOutlineConnectEvent = this.connectionHandler.isOutlineConnectEvent;
942
943	    this.connectionHandler.isOutlineConnectEvent = function(me)
944	    {
945		    	return (this.currentState != null && me.getState() == this.currentState && timeOnTarget > 2000) ||
946		    		((this.currentState == null || mxUtils.getValue(this.currentState.style, 'outlineConnect', '1') != '0') &&
947		    		connectionHandleIsOutlineConnectEvent.apply(this, arguments));
948	    };
949
950	    // Adds shift+click to toggle selection state
951	    var isToggleEvent = this.isToggleEvent;
952	    this.isToggleEvent = function(evt)
953	    {
954	    	return isToggleEvent.apply(this, arguments) || (!mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt));
955	    };
956
957	    // Workaround for Firefox where first mouse down is received
958	    // after tap and hold if scrollbars are visible, which means
959	    // start rubberband immediately if no cell is under mouse.
960	    var isForceRubberBandEvent = rubberband.isForceRubberbandEvent;
961	    rubberband.isForceRubberbandEvent = function(me)
962	    {
963	    	return (isForceRubberBandEvent.apply(this, arguments) && !mxEvent.isShiftDown(me.getEvent()) &&
964	    		!mxEvent.isControlDown(me.getEvent())) || (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(me.getEvent())) ||
965	    		(mxUtils.hasScrollbars(this.graph.container) && mxClient.IS_FF &&
966	    		mxClient.IS_WIN && me.getState() == null && mxEvent.isTouchEvent(me.getEvent()));
967	    };
968
969	    // Shows hand cursor while panning
970	    var prevCursor = null;
971
972		this.panningHandler.addListener(mxEvent.PAN_START, mxUtils.bind(this, function()
973		{
974			if (this.isEnabled())
975			{
976				prevCursor = this.container.style.cursor;
977				this.container.style.cursor = 'move';
978			}
979		}));
980
981		this.panningHandler.addListener(mxEvent.PAN_END, mxUtils.bind(this, function()
982		{
983			if (this.isEnabled())
984			{
985				this.container.style.cursor = prevCursor;
986			}
987		}));
988
989		this.popupMenuHandler.autoExpand = true;
990
991		this.popupMenuHandler.isSelectOnPopup = function(me)
992		{
993			return mxEvent.isMouseEvent(me.getEvent());
994		};
995
996		// Handles links if graph is read-only or cell is locked
997		var click = this.click;
998		this.click = function(me)
999		{
1000			var locked = me.state == null && me.sourceState != null &&
1001				this.isCellLocked(me.sourceState.cell);
1002
1003			if ((!this.isEnabled() || locked) && !me.isConsumed())
1004			{
1005				var cell = (locked) ? me.sourceState.cell : me.getCell();
1006
1007				if (cell != null)
1008				{
1009					var link = this.getClickableLinkForCell(cell);
1010
1011					if (link != null)
1012					{
1013						if (this.isCustomLink(link))
1014						{
1015							this.customLinkClicked(link);
1016						}
1017						else
1018						{
1019							this.openLink(link);
1020						}
1021					}
1022				}
1023
1024				if (this.isEnabled() && locked)
1025				{
1026					this.clearSelection();
1027				}
1028			}
1029			else
1030			{
1031				return click.apply(this, arguments);
1032			}
1033		};
1034
1035		// Redirects tooltips for locked cells
1036		this.tooltipHandler.getStateForEvent = function(me)
1037		{
1038			return me.sourceState;
1039		};
1040
1041		// Opens links in tooltips in new windows
1042		var tooltipHandlerShow = this.tooltipHandler.show;
1043		this.tooltipHandler.show = function()
1044		{
1045			tooltipHandlerShow.apply(this, arguments);
1046
1047			if (this.div != null)
1048			{
1049				var links = this.div.getElementsByTagName('a');
1050
1051				for (var i = 0; i < links.length; i++)
1052				{
1053					if (links[i].getAttribute('href') != null &&
1054						links[i].getAttribute('target') == null)
1055					{
1056						links[i].setAttribute('target', '_blank');
1057					}
1058				}
1059			}
1060		};
1061
1062		// Redirects tooltips for locked cells
1063		this.tooltipHandler.getStateForEvent = function(me)
1064		{
1065			return me.sourceState;
1066		};
1067
1068		// Redirects cursor for locked cells
1069		var getCursorForMouseEvent = this.getCursorForMouseEvent;
1070		this.getCursorForMouseEvent = function(me)
1071		{
1072			var locked = me.state == null && me.sourceState != null && this.isCellLocked(me.sourceState.cell);
1073
1074			return this.getCursorForCell((locked) ? me.sourceState.cell : me.getCell());
1075		};
1076
1077		// Shows pointer cursor for clickable cells with links
1078		// ie. if the graph is disabled and cells cannot be selected
1079		var getCursorForCell = this.getCursorForCell;
1080		this.getCursorForCell = function(cell)
1081		{
1082			if (!this.isEnabled() || this.isCellLocked(cell))
1083			{
1084				var link = this.getClickableLinkForCell(cell);
1085
1086				if (link != null)
1087				{
1088					return 'pointer';
1089				}
1090				else if (this.isCellLocked(cell))
1091				{
1092					return 'default';
1093				}
1094			}
1095
1096			return getCursorForCell.apply(this, arguments);
1097		};
1098
1099		// Changes rubberband selection ignore locked cells
1100		this.selectRegion = function(rect, evt)
1101		{
1102			var isect = (mxEvent.isAltDown(evt)) ? rect : null;
1103
1104			var cells = this.getCells(rect.x, rect.y, rect.width, rect.height, null, null, isect, function(state)
1105			{
1106				return mxUtils.getValue(state.style, 'locked', '0') == '1';
1107			}, true);
1108
1109			if (this.isToggleEvent(evt))
1110			{
1111				for (var i = 0; i < cells.length; i++)
1112				{
1113					this.selectCellForEvent(cells[i], evt);
1114				}
1115			}
1116			else
1117			{
1118				this.selectCellsForEvent(cells, evt);
1119			}
1120
1121			return cells;
1122		};
1123
1124		// Never removes cells from parents that are being moved
1125		var graphHandlerShouldRemoveCellsFromParent = this.graphHandler.shouldRemoveCellsFromParent;
1126		this.graphHandler.shouldRemoveCellsFromParent = function(parent, cells, evt)
1127		{
1128			if (this.graph.isCellSelected(parent))
1129			{
1130				return false;
1131			}
1132
1133			return graphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
1134		};
1135
1136		// Unlocks all cells
1137		this.isCellLocked = function(cell)
1138		{
1139			while (cell != null)
1140			{
1141				if (mxUtils.getValue(this.getCurrentCellStyle(cell), 'locked', '0') == '1')
1142				{
1143					return true;
1144				}
1145
1146				cell = this.model.getParent(cell);
1147			}
1148
1149			return false;
1150		};
1151
1152		var tapAndHoldSelection = null;
1153
1154		// Uses this event to process mouseDown to check the selection state before it is changed
1155		this.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt)
1156		{
1157			if (evt.getProperty('eventName') == 'mouseDown')
1158			{
1159				var me = evt.getProperty('event');
1160				var state = me.getState();
1161
1162				if (state != null && !this.isSelectionEmpty() && !this.isCellSelected(state.cell))
1163				{
1164					tapAndHoldSelection = this.getSelectionCells();
1165				}
1166				else
1167				{
1168					tapAndHoldSelection = null;
1169				}
1170			}
1171		}));
1172
1173		// Tap and hold on background starts rubberband for multiple selected
1174		// cells the cell associated with the event is deselected
1175		this.addListener(mxEvent.TAP_AND_HOLD, mxUtils.bind(this, function(sender, evt)
1176		{
1177			if (!mxEvent.isMultiTouchEvent(evt))
1178			{
1179				var me = evt.getProperty('event');
1180				var cell = evt.getProperty('cell');
1181
1182				if (cell == null)
1183				{
1184					var pt = mxUtils.convertPoint(this.container,
1185							mxEvent.getClientX(me), mxEvent.getClientY(me));
1186					rubberband.start(pt.x, pt.y);
1187				}
1188				else if (tapAndHoldSelection != null)
1189				{
1190					this.addSelectionCells(tapAndHoldSelection);
1191				}
1192				else if (this.getSelectionCount() > 1 && this.isCellSelected(cell))
1193				{
1194					this.removeSelectionCell(cell);
1195				}
1196
1197				// Blocks further processing of the event
1198				tapAndHoldSelection = null;
1199				evt.consume();
1200			}
1201		}));
1202
1203		// On connect the target is selected and we clone the cell of the preview edge for insert
1204		this.connectionHandler.selectCells = function(edge, target)
1205		{
1206			this.graph.setSelectionCell(target || edge);
1207		};
1208
1209		// Shows connection points only if cell not selected and parent table not handled
1210		this.connectionHandler.constraintHandler.isStateIgnored = function(state, source)
1211		{
1212			var graph = state.view.graph;
1213
1214			return source && (graph.isCellSelected(state.cell) || (graph.isTableRow(state.cell) &&
1215				graph.selectionCellsHandler.isHandled(graph.model.getParent(state.cell))));
1216		};
1217
1218		// Updates constraint handler if the selection changes
1219		this.selectionModel.addListener(mxEvent.CHANGE, mxUtils.bind(this, function()
1220		{
1221			var ch = this.connectionHandler.constraintHandler;
1222
1223			if (ch.currentFocus != null && ch.isStateIgnored(ch.currentFocus, true))
1224			{
1225				ch.currentFocus = null;
1226				ch.constraints = null;
1227				ch.destroyIcons();
1228			}
1229
1230			ch.destroyFocusHighlight();
1231		}));
1232
1233		// Initializes touch interface
1234		if (Graph.touchStyle)
1235		{
1236			this.initTouch();
1237		}
1238
1239		/**
1240		 * Adds locking
1241		 */
1242		var graphUpdateMouseEvent = this.updateMouseEvent;
1243		this.updateMouseEvent = function(me)
1244		{
1245			me = graphUpdateMouseEvent.apply(this, arguments);
1246
1247			if (me.state != null && this.isCellLocked(me.getCell()))
1248			{
1249				me.state = null;
1250			}
1251
1252			return me;
1253		};
1254	}
1255
1256	//Create a unique offset object for each graph instance.
1257	this.currentTranslate = new mxPoint(0, 0);
1258};
1259
1260/**
1261 * Specifies if the touch UI should be used (cannot detect touch in FF so always on for Windows/Linux)
1262 */
1263Graph.touchStyle = mxClient.IS_TOUCH || (mxClient.IS_FF && mxClient.IS_WIN) || navigator.maxTouchPoints > 0 ||
1264	navigator.msMaxTouchPoints > 0 || window.urlParams == null || urlParams['touch'] == '1';
1265
1266/**
1267 * Shortcut for capability check.
1268 */
1269Graph.fileSupport = window.File != null && window.FileReader != null && window.FileList != null &&
1270	(window.urlParams == null || urlParams['filesupport'] != '0');
1271
1272/**
1273 * Shortcut for capability check.
1274 */
1275Graph.translateDiagram = urlParams['translate-diagram'] == '1';
1276
1277/**
1278 * Shortcut for capability check.
1279 */
1280Graph.diagramLanguage = (urlParams['diagram-language'] != null) ? urlParams['diagram-language'] : mxClient.language;
1281
1282/**
1283 * Default size for line jumps.
1284 */
1285Graph.lineJumpsEnabled = true;
1286
1287/**
1288 * Default size for line jumps.
1289 */
1290Graph.defaultJumpSize = 6;
1291
1292/**
1293 * Specifies if the mouse wheel is used for zoom without any modifiers.
1294 */
1295Graph.zoomWheel = false;
1296
1297/**
1298 * Minimum width for table columns.
1299 */
1300Graph.minTableColumnWidth = 20;
1301
1302/**
1303 * Minimum height for table rows.
1304 */
1305Graph.minTableRowHeight = 20;
1306
1307/**
1308 * Text for foreign object warning.
1309 */
1310Graph.foreignObjectWarningText = 'Viewer does not support full SVG 1.1';
1311
1312/**
1313 * Link for foreign object warning.
1314 */
1315Graph.foreignObjectWarningLink = 'https://www.diagrams.net/doc/faq/svg-export-text-problems';
1316
1317/**
1318 *
1319 */
1320Graph.xmlDeclaration = '<?xml version="1.0" encoding="UTF-8"?>';
1321
1322/**
1323 *
1324 */
1325Graph.svgDoctype = '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ' +
1326	'"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
1327
1328/**
1329 *
1330 */
1331Graph.svgFileComment = '<!-- Do not edit this file with editors other than diagrams.net -->'
1332
1333/**
1334 * Minimum height for table rows.
1335 */
1336Graph.pasteStyles = ['rounded', 'shadow', 'dashed', 'dashPattern', 'fontFamily', 'fontSource', 'fontSize', 'fontColor', 'fontStyle',
1337					'align', 'verticalAlign', 'strokeColor', 'strokeWidth', 'fillColor', 'gradientColor', 'swimlaneFillColor',
1338					'textOpacity', 'gradientDirection', 'glass', 'labelBackgroundColor', 'labelBorderColor', 'opacity',
1339					'spacing', 'spacingTop', 'spacingLeft', 'spacingBottom', 'spacingRight', 'endFill', 'endArrow',
1340					'endSize', 'targetPerimeterSpacing', 'startFill', 'startArrow', 'startSize', 'sourcePerimeterSpacing',
1341					'arcSize', 'comic', 'sketch', 'fillWeight', 'hachureGap', 'hachureAngle', 'jiggle', 'disableMultiStroke',
1342					'disableMultiStrokeFill', 'fillStyle', 'curveFitting', 'simplification', 'comicStyle'];
1343
1344/**
1345 * Creates a temporary graph instance for rendering off-screen content.
1346 */
1347Graph.createOffscreenGraph = function(stylesheet)
1348{
1349	var graph = new Graph(document.createElement('div'));
1350	graph.stylesheet.styles = mxUtils.clone(stylesheet.styles);
1351	graph.resetViewOnRootChange = false;
1352	graph.setConnectable(false);
1353	graph.gridEnabled = false;
1354	graph.autoScroll = false;
1355	graph.setTooltips(false);
1356	graph.setEnabled(false);
1357
1358	// Container must be in the DOM for correct HTML rendering
1359	graph.container.style.visibility = 'hidden';
1360	graph.container.style.position = 'absolute';
1361	graph.container.style.overflow = 'hidden';
1362	graph.container.style.height = '1px';
1363	graph.container.style.width = '1px';
1364
1365	return graph;
1366};
1367
1368/**
1369 * Helper function for creating SVG data URI.
1370 */
1371Graph.createSvgImage = function(w, h, data, coordWidth, coordHeight)
1372{
1373	var tmp = unescape(encodeURIComponent(Graph.svgDoctype +
1374        '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' + w + 'px" height="' + h + 'px" ' +
1375        ((coordWidth != null && coordHeight != null) ? 'viewBox="0 0 ' + coordWidth + ' ' + coordHeight + '" ' : '') +
1376        'version="1.1">' + data + '</svg>'));
1377
1378    return new mxImage('data:image/svg+xml;base64,' + ((window.btoa) ? btoa(tmp) : Base64.encode(tmp, true)), w, h)
1379};
1380
1381/**
1382 * Removes all illegal control characters with ASCII code <32 except TAB, LF
1383 * and CR.
1384 */
1385Graph.zapGremlins = function(text)
1386{
1387	var lastIndex = 0;
1388	var checked = [];
1389
1390	for (var i = 0; i < text.length; i++)
1391	{
1392		var code = text.charCodeAt(i);
1393
1394		// Removes all control chars except TAB, LF and CR
1395		if (!((code >= 32 || code == 9 || code == 10 || code == 13) &&
1396			code != 0xFFFF && code != 0xFFFE))
1397		{
1398			checked.push(text.substring(lastIndex, i));
1399			lastIndex = i + 1;
1400		}
1401	}
1402
1403	if (lastIndex > 0 && lastIndex < text.length)
1404	{
1405		checked.push(text.substring(lastIndex));
1406	}
1407
1408	return (checked.length == 0) ? text : checked.join('');
1409};
1410
1411/**
1412 * Turns the given string into an array.
1413 */
1414Graph.stringToBytes = function(str)
1415{
1416	var arr = new Array(str.length);
1417
1418    for (var i = 0; i < str.length; i++)
1419    {
1420        arr[i] = str.charCodeAt(i);
1421    }
1422
1423    return arr;
1424};
1425
1426/**
1427 * Turns the given array into a string.
1428 */
1429Graph.bytesToString = function(arr)
1430{
1431	var result = new Array(arr.length);
1432
1433    for (var i = 0; i < arr.length; i++)
1434    {
1435    	result[i] = String.fromCharCode(arr[i]);
1436    }
1437
1438    return result.join('');
1439};
1440
1441/**
1442 * Turns the given array into a string.
1443 */
1444Graph.base64EncodeUnicode = function(str)
1445{
1446    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
1447        return String.fromCharCode(parseInt(p1, 16))
1448    }));
1449};
1450
1451/**
1452 * Turns the given array into a string.
1453 */
1454Graph.base64DecodeUnicode = function(str)
1455{
1456    return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
1457        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
1458    }).join(''));
1459};
1460
1461/**
1462 * Returns a base64 encoded version of the compressed outer XML of the given node.
1463 */
1464Graph.compressNode = function(node, checked)
1465{
1466	var xml = mxUtils.getXml(node);
1467
1468	return Graph.compress((checked) ? xml : Graph.zapGremlins(xml));
1469};
1470
1471/**
1472 * Returns a string for the given array buffer.
1473 */
1474Graph.arrayBufferToString = function(buffer)
1475{
1476    var binary = '';
1477    var bytes = new Uint8Array(buffer);
1478    var len = bytes.byteLength;
1479
1480    for (var i = 0; i < len; i++)
1481	{
1482        binary += String.fromCharCode(bytes[i]);
1483    }
1484
1485    return binary;
1486};
1487
1488/**
1489 * Returns an array buffer for the given string.
1490 */
1491Graph.stringToArrayBuffer = function(data)
1492{
1493	return Uint8Array.from(data, function (c)
1494	{
1495	  return c.charCodeAt(0);
1496	});
1497};
1498
1499/**
1500 * Returns index of a string in an array buffer (UInt8Array)
1501 */
1502Graph.arrayBufferIndexOfString = function (uint8Array, str, start)
1503{
1504	var c0 = str.charCodeAt(0), j = 1, p = -1;
1505
1506	//Index of first char
1507	for (var i = start || 0; i < uint8Array.byteLength; i++)
1508	{
1509		if (uint8Array[i] == c0)
1510		{
1511			p = i;
1512			break;
1513		}
1514	}
1515
1516	for (var i = p + 1; p > -1 && i < uint8Array.byteLength && i < p + str.length - 1; i++)
1517	{
1518		if (uint8Array[i] != str.charCodeAt(j))
1519		{
1520			return Graph.arrayBufferIndexOfString(uint8Array, str, p + 1);
1521		}
1522
1523		j++;
1524	}
1525
1526	return j == str.length - 1? p : -1;
1527};
1528
1529/**
1530 * Returns a base64 encoded version of the compressed string.
1531 */
1532Graph.compress = function(data, deflate)
1533{
1534	if (data == null || data.length == 0 || typeof(pako) === 'undefined')
1535	{
1536		return data;
1537	}
1538	else
1539	{
1540   		var tmp = (deflate) ? pako.deflate(encodeURIComponent(data)) :
1541   			pako.deflateRaw(encodeURIComponent(data));
1542
1543   		return btoa(Graph.arrayBufferToString(new Uint8Array(tmp)));
1544	}
1545};
1546
1547/**
1548 * Returns a decompressed version of the base64 encoded string.
1549 */
1550Graph.decompress = function(data, inflate, checked)
1551{
1552   	if (data == null || data.length == 0 || typeof(pako) === 'undefined')
1553	{
1554		return data;
1555	}
1556	else
1557	{
1558		var tmp = Graph.stringToArrayBuffer(atob(data));
1559		var inflated = decodeURIComponent((inflate) ?
1560			pako.inflate(tmp, {to: 'string'}) :
1561			pako.inflateRaw(tmp, {to: 'string'}));
1562
1563		return (checked) ? inflated : Graph.zapGremlins(inflated);
1564	}
1565};
1566
1567/**
1568 * Fades the given nodes in or out.
1569 */
1570Graph.fadeNodes = function(nodes, start, end, done, delay)
1571{
1572	delay = (delay != null) ? delay : 1000;
1573	Graph.setTransitionForNodes(nodes, null);
1574	Graph.setOpacityForNodes(nodes, start);
1575
1576	window.setTimeout(function()
1577	{
1578		Graph.setTransitionForNodes(nodes,
1579			'all ' + delay + 'ms ease-in-out');
1580		Graph.setOpacityForNodes(nodes, end);
1581
1582		window.setTimeout(function()
1583		{
1584			Graph.setTransitionForNodes(nodes, null);
1585
1586			if (done != null)
1587			{
1588				done();
1589			}
1590		}, delay);
1591	}, 0);
1592};
1593
1594/**
1595 * Sets the transition for the given nodes.
1596 */
1597Graph.setTransitionForNodes = function(nodes, transition)
1598{
1599	 for (var i = 0; i < nodes.length; i++)
1600	 {
1601		mxUtils.setPrefixedStyle(nodes[i].style, 'transition', transition);
1602	 }
1603};
1604
1605/**
1606 * Sets the opacity for the given nodes.
1607 */
1608Graph.setOpacityForNodes = function(nodes, opacity)
1609{
1610	 for (var i = 0; i < nodes.length; i++)
1611	 {
1612		nodes[i].style.opacity = opacity;
1613	 }
1614};
1615
1616/**
1617 * Removes formatting from pasted HTML.
1618 */
1619Graph.removePasteFormatting = function(elt)
1620{
1621	while (elt != null)
1622	{
1623		if (elt.firstChild != null)
1624		{
1625			Graph.removePasteFormatting(elt.firstChild);
1626		}
1627
1628		if (elt.nodeType == mxConstants.NODETYPE_ELEMENT && elt.style != null)
1629		{
1630			elt.style.whiteSpace = '';
1631
1632			if (elt.style.color == '#000000')
1633			{
1634				elt.style.color = '';
1635			}
1636		}
1637
1638		elt = elt.nextSibling;
1639	}
1640};
1641
1642/**
1643 * Sanitizes the given HTML markup.
1644 */
1645Graph.sanitizeHtml = function(value, editing)
1646{
1647	// Uses https://code.google.com/p/google-caja/wiki/JsHtmlSanitizer
1648	// NOTE: Original minimized sanitizer was modified to support
1649	// data URIs for images, mailto and special data:-links.
1650	// LATER: Add MathML to whitelisted tags
1651	function urlX(link)
1652	{
1653		if (link != null && link.toString().toLowerCase().substring(0, 11) !== 'javascript:')
1654		{
1655			return link;
1656		}
1657
1658		return null;
1659	};
1660    function idX(id) { return id };
1661
1662	return html_sanitize(value, urlX, idX);
1663};
1664
1665/**
1666 * Removes all script tags and attributes starting with on.
1667 */
1668Graph.sanitizeSvg = function(div)
1669{
1670	// Removes all attributes starting with on
1671	var all = div.getElementsByTagName('*');
1672
1673	for (var i = 0; i < all.length; i++)
1674	{
1675		for (var j = 0; j < all[i].attributes.length; j++)
1676		{
1677			var attr = all[i].attributes[j];
1678
1679			if (attr.name.length > 2 && attr.name.toLowerCase().substring(0, 2) == 'on')
1680			{
1681				all[i].removeAttribute(attr.name);
1682			}
1683	    }
1684	}
1685
1686	function removeAllTags(tagName)
1687	{
1688		var nodes = div.getElementsByTagName(tagName);
1689
1690		while (nodes.length > 0)
1691		{
1692			nodes[0].parentNode.removeChild(nodes[0]);
1693		}
1694	};
1695
1696	removeAllTags('meta');
1697	removeAllTags('script');
1698	removeAllTags('metadata');
1699};
1700
1701/**
1702 * Updates the viewbox, width and height in the given SVG data URI
1703 * and returns the updated data URI with all script tags and event
1704 * handlers removed.
1705 */
1706Graph.clipSvgDataUri = function(dataUri)
1707{
1708	// LATER Add workaround for non-default NS declarations with empty URI not allowed in IE11
1709	if (!mxClient.IS_IE && !mxClient.IS_IE11 && dataUri != null &&
1710		dataUri.substring(0, 26) == 'data:image/svg+xml;base64,')
1711	{
1712		try
1713		{
1714			var div = document.createElement('div');
1715			div.style.position = 'absolute';
1716			div.style.visibility = 'hidden';
1717
1718			// Adds the text and inserts into DOM for updating of size
1719			var data = decodeURIComponent(escape(atob(dataUri.substring(26))));
1720			var idx = data.indexOf('<svg');
1721
1722			if (idx >= 0)
1723			{
1724				// Strips leading XML declaration and doctypes
1725				div.innerHTML = data.substring(idx);
1726
1727				// Removes all attributes starting with on
1728				Graph.sanitizeSvg(div);
1729
1730				// Gets the size and removes from DOM
1731				var svgs = div.getElementsByTagName('svg');
1732
1733				if (svgs.length > 0)
1734				{
1735					document.body.appendChild(div);
1736
1737					try
1738					{
1739						var fx = 1;
1740						var fy = 1;
1741						var w = svgs[0].getAttribute('width');
1742						var h = svgs[0].getAttribute('height');
1743
1744						if (w != null && w.charAt(w.length - 1) != '%')
1745						{
1746							w = parseFloat(w);
1747						}
1748						else
1749						{
1750							w = NaN;
1751						}
1752
1753						if (h != null && h.charAt(h.length - 1) != '%')
1754						{
1755							h = parseFloat(h);
1756						}
1757						else
1758						{
1759							h = NaN;
1760						}
1761
1762						var vb = svgs[0].getAttribute('viewBox');
1763
1764						if (vb != null && !isNaN(w) && !isNaN(h))
1765						{
1766							var tokens = vb.split(' ');
1767
1768							if (vb.length >= 4)
1769							{
1770								fx = parseFloat(tokens[2]) / w;
1771								fy = parseFloat(tokens[3]) / h;
1772							}
1773						}
1774
1775						var size = svgs[0].getBBox();
1776
1777						if (size.width > 0 && size.height > 0)
1778						{
1779							div.getElementsByTagName('svg')[0].setAttribute('viewBox', size.x +
1780								' ' + size.y + ' ' + size.width + ' ' + size.height);
1781							div.getElementsByTagName('svg')[0].setAttribute('width', size.width / fx);
1782							div.getElementsByTagName('svg')[0].setAttribute('height', size.height / fy);
1783						}
1784					}
1785					catch (e)
1786					{
1787						// ignore
1788					}
1789					finally
1790					{
1791						document.body.removeChild(div);
1792					}
1793
1794					dataUri = Editor.createSvgDataUri(mxUtils.getXml(svgs[0]));
1795				}
1796			}
1797		}
1798		catch (e)
1799		{
1800			// ignore
1801		}
1802	}
1803
1804	return dataUri;
1805};
1806
1807/**
1808 * Returns the CSS font family from the given computed style.
1809 */
1810Graph.stripQuotes = function(text)
1811{
1812	if (text != null)
1813	{
1814		if (text.charAt(0) == '\'')
1815		{
1816			text = text.substring(1);
1817		}
1818
1819		if (text.charAt(text.length - 1) == '\'')
1820		{
1821			text = text.substring(0, text.length - 1);
1822		}
1823
1824		if (text.charAt(0) == '"')
1825		{
1826			text = text.substring(1);
1827		}
1828
1829		if (text.charAt(text.length - 1) == '"')
1830		{
1831			text = text.substring(0, text.length - 1);
1832		}
1833	}
1834
1835	return text;
1836};
1837
1838/**
1839 * Create remove icon.
1840 */
1841Graph.createRemoveIcon = function(title, onclick)
1842{
1843	var removeLink = document.createElement('img');
1844	removeLink.setAttribute('src', Dialog.prototype.clearImage);
1845	removeLink.setAttribute('title', title);
1846	removeLink.setAttribute('width', '13');
1847	removeLink.setAttribute('height', '10');
1848	removeLink.style.marginLeft = '4px';
1849	removeLink.style.marginBottom = '-1px';
1850	removeLink.style.cursor = 'pointer';
1851
1852	mxEvent.addListener(removeLink, 'click', onclick);
1853
1854	return removeLink;
1855};
1856
1857/**
1858 * Returns true if the given string is a page link.
1859 */
1860Graph.isPageLink = function(text)
1861{
1862	 return text != null && text.substring(0, 13) == 'data:page/id,';
1863};
1864
1865/**
1866 * Returns true if the given string is a link.
1867 *
1868 * See https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
1869 */
1870Graph.isLink = function(text)
1871{
1872	return text != null && Graph.linkPattern.test(text);
1873};
1874
1875/**
1876 * Regular expression for links.
1877 */
1878Graph.linkPattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
1879	'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
1880	'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
1881	'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
1882	'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
1883	'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
1884
1885/**
1886 * Graph inherits from mxGraph.
1887 */
1888mxUtils.extend(Graph, mxGraph);
1889
1890/**
1891 * Allows all values in fit.
1892 */
1893Graph.prototype.minFitScale = null;
1894
1895/**
1896 * Allows all values in fit.
1897 */
1898Graph.prototype.maxFitScale = null;
1899
1900/**
1901 * Sets the policy for links. Possible values are "self" to replace any framesets,
1902 * "blank" to load the URL in <linkTarget> and "auto" (default).
1903 */
1904Graph.prototype.linkPolicy = (urlParams['target'] == 'frame') ? 'blank' : (urlParams['target'] || 'auto');
1905
1906/**
1907 * Target for links that open in a new window. Default is _blank.
1908 */
1909Graph.prototype.linkTarget = (urlParams['target'] == 'frame') ? '_self' : '_blank';
1910
1911/**
1912 * Value to the rel attribute of links. Default is 'nofollow noopener noreferrer'.
1913 * NOTE: There are security implications when this is changed and if noopener is removed,
1914 * then <openLink> must be overridden to allow for the opener to be set by default.
1915 */
1916Graph.prototype.linkRelation = 'nofollow noopener noreferrer';
1917
1918/**
1919 * Scrollbars are enabled on non-touch devices (not including Firefox because touch events
1920 * cannot be detected in Firefox, see above).
1921 */
1922Graph.prototype.defaultScrollbars = !mxClient.IS_IOS;
1923
1924/**
1925 * Specifies if the page should be visible for new files. Default is true.
1926 */
1927Graph.prototype.defaultPageVisible = true;
1928
1929/**
1930 * Specifies if the page should be visible for new files. Default is true.
1931 */
1932Graph.prototype.defaultGridEnabled = urlParams['grid'] != '0';
1933
1934/**
1935 * Specifies if the app should run in chromeless mode. Default is false.
1936 * This default is only used if the contructor argument is null.
1937 */
1938Graph.prototype.lightbox = false;
1939
1940/**
1941 *
1942 */
1943Graph.prototype.defaultPageBackgroundColor = '#ffffff';
1944
1945/**
1946 *
1947 */
1948Graph.prototype.defaultPageBorderColor = '#ffffff';
1949
1950/**
1951 *
1952 */
1953Graph.prototype.shapeForegroundColor = '#000000';
1954
1955/**
1956 *
1957 */
1958Graph.prototype.shapeBackgroundColor = '#ffffff';
1959
1960/**
1961 * Specifies the size of the size for "tiles" to be used for a graph with
1962 * scrollbars but no visible background page. A good value is large
1963 * enough to reduce the number of repaints that is caused for auto-
1964 * translation, which depends on this value, and small enough to give
1965 * a small empty buffer around the graph. Default is 400x400.
1966 */
1967Graph.prototype.scrollTileSize = new mxRectangle(0, 0, 400, 400);
1968
1969/**
1970 * Overrides the background color and paints a transparent background.
1971 */
1972Graph.prototype.transparentBackground = true;
1973
1974/**
1975 * Sets global constants.
1976 */
1977Graph.prototype.selectParentAfterDelete = false;
1978
1979/**
1980 * Sets the default target for all links in cells.
1981 */
1982Graph.prototype.defaultEdgeLength = 80;
1983
1984/**
1985 * Disables move of bends/segments without selecting.
1986 */
1987Graph.prototype.edgeMode = false;
1988
1989/**
1990 * Allows all values in fit.
1991 */
1992Graph.prototype.connectionArrowsEnabled = true;
1993
1994/**
1995 * Specifies the regular expression for matching placeholders.
1996 */
1997Graph.prototype.placeholderPattern = new RegExp('%(date\{.*\}|[^%^\{^\}^ ^"^ \'^=^;]+)%', 'g');
1998
1999/**
2000 * Specifies the regular expression for matching placeholders.
2001 */
2002Graph.prototype.absoluteUrlPattern = new RegExp('^(?:[a-z]+:)?//', 'i');
2003
2004/**
2005 * Specifies the default name for the theme. Default is 'default'.
2006 */
2007Graph.prototype.defaultThemeName = 'default';
2008
2009/**
2010 * Specifies the default name for the theme. Default is 'default'.
2011 */
2012Graph.prototype.defaultThemes = {};
2013
2014/**
2015 * Base URL for relative links.
2016 */
2017Graph.prototype.baseUrl = (urlParams['base'] != null) ?
2018	decodeURIComponent(urlParams['base']) :
2019	(((window != window.top) ? document.referrer :
2020	document.location.toString()).split('#')[0]);
2021
2022/**
2023 * Specifies if the label should be edited after an insert.
2024 */
2025Graph.prototype.editAfterInsert = false;
2026
2027/**
2028 * Defines the built-in properties to be ignored in tooltips.
2029 */
2030Graph.prototype.builtInProperties = ['label', 'tooltip', 'placeholders', 'placeholder'];
2031
2032/**
2033 * Defines if the graph is part of an EditorUi. If this is false the graph can
2034 * be used in an EditorUi instance but will not have a UI added, functions
2035 * overridden or event handlers added.
2036 */
2037Graph.prototype.standalone = false;
2038
2039/**
2040 * Enables move of bends/segments without selecting.
2041 */
2042Graph.prototype.enableFlowAnimation = false;
2043
2044/**
2045 * Installs child layout styles.
2046 */
2047Graph.prototype.init = function(container)
2048{
2049	mxGraph.prototype.init.apply(this, arguments);
2050
2051	// Intercepts links with no target attribute and opens in new window
2052	this.cellRenderer.initializeLabel = function(state, shape)
2053	{
2054		mxCellRenderer.prototype.initializeLabel.apply(this, arguments);
2055
2056		// Checks tolerance for clicks on links
2057		var tol = state.view.graph.tolerance;
2058		var handleClick = true;
2059		var first = null;
2060
2061		var down = mxUtils.bind(this, function(evt)
2062		{
2063			handleClick = true;
2064			first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
2065		});
2066
2067		var move = mxUtils.bind(this, function(evt)
2068		{
2069			handleClick = handleClick && first != null &&
2070				Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&
2071				Math.abs(first.y - mxEvent.getClientY(evt)) < tol;
2072		});
2073
2074		var up = mxUtils.bind(this, function(evt)
2075		{
2076			if (handleClick)
2077			{
2078				var elt = mxEvent.getSource(evt)
2079
2080				while (elt != null && elt != shape.node)
2081				{
2082					if (elt.nodeName.toLowerCase() == 'a')
2083					{
2084						state.view.graph.labelLinkClicked(state, elt, evt);
2085						break;
2086					}
2087
2088					elt = elt.parentNode;
2089				}
2090			}
2091		});
2092
2093		mxEvent.addGestureListeners(shape.node, down, move, up);
2094		mxEvent.addListener(shape.node, 'click', function(evt)
2095		{
2096			mxEvent.consume(evt);
2097		});
2098	};
2099
2100	// Handles custom links in tooltips
2101	if (this.tooltipHandler != null)
2102	{
2103		var tooltipHandlerInit = this.tooltipHandler.init;
2104
2105		this.tooltipHandler.init = function()
2106		{
2107			tooltipHandlerInit.apply(this, arguments);
2108
2109			if (this.div != null)
2110			{
2111				mxEvent.addListener(this.div, 'click', mxUtils.bind(this, function(evt)
2112				{
2113					var source = mxEvent.getSource(evt);
2114
2115					if (source.nodeName == 'A')
2116					{
2117						var href = source.getAttribute('href');
2118
2119						if (href != null && this.graph.isCustomLink(href) &&
2120							(mxEvent.isTouchEvent(evt) || !mxEvent.isPopupTrigger(evt)) &&
2121							this.graph.customLinkClicked(href))
2122						{
2123							mxEvent.consume(evt);
2124						}
2125					}
2126				}));
2127			}
2128		};
2129	}
2130
2131
2132	// Adds or updates CSS for flowAnimation style
2133	this.addListener(mxEvent.SIZE, mxUtils.bind(this, function(sender, evt)
2134	{
2135		if (this.container != null && this.flowAnimationStyle)
2136		{
2137			var id = this.flowAnimationStyle.getAttribute('id');
2138			this.flowAnimationStyle.innerHTML = this.getFlowAnimationStyleCss(id);
2139		}
2140	}));
2141
2142	this.initLayoutManager();
2143};
2144
2145/**
2146 * Implements zoom and offset via CSS transforms. This is currently only used
2147 * in read-only as there are fewer issues with the mxCellState not being scaled
2148 * and translated.
2149 *
2150 * KNOWN ISSUES TO FIX:
2151 * - Apply CSS transforms to HTML labels in IE11
2152 */
2153(function()
2154{
2155	/**
2156	 * Uses CSS transforms for scale and translate.
2157	 */
2158	Graph.prototype.useCssTransforms = false;
2159
2160	/**
2161	 * Contains the scale.
2162	 */
2163	Graph.prototype.currentScale = 1;
2164
2165	/**
2166	 * Contains the offset.
2167	 */
2168	Graph.prototype.currentTranslate = new mxPoint(0, 0);
2169
2170	/**
2171	 *
2172	 */
2173	Graph.prototype.getVerticesAndEdges = function(vertices, edges)
2174	{
2175		vertices = (vertices != null) ? vertices : true;
2176		edges = (edges != null) ? edges : true;
2177		var model = this.model;
2178
2179		return model.filterDescendants(function(cell)
2180		{
2181			return (vertices && model.isVertex(cell)) || (edges && model.isEdge(cell));
2182		}, model.getRoot());
2183	};
2184
2185	/**
2186	 * Returns information about the current selection.
2187	 */
2188	Graph.prototype.getCommonStyle = function(cells)
2189	{
2190		var style = {};
2191
2192		for (var i = 0; i < cells.length; i++)
2193		{
2194			var state = this.view.getState(cells[i]);
2195			this.mergeStyle(state.style, style, i == 0);
2196		}
2197
2198		return style;
2199	};
2200
2201	/**
2202	 * Returns information about the current selection.
2203	 */
2204	Graph.prototype.mergeStyle = function(style, into, initial)
2205	{
2206		if (style != null)
2207		{
2208			var keys = {};
2209
2210			for (var key in style)
2211			{
2212				var value = style[key];
2213
2214				if (value != null)
2215				{
2216					keys[key] = true;
2217
2218					if (into[key] == null && initial)
2219					{
2220						into[key] = value;
2221					}
2222					else if (into[key] != value)
2223					{
2224						delete into[key];
2225					}
2226				}
2227			}
2228
2229			for (var key in into)
2230			{
2231				if (!keys[key])
2232				{
2233					delete into[key];
2234				}
2235			}
2236		}
2237	};
2238
2239	/**
2240	 * Returns the cell for editing the given cell.
2241	 */
2242	Graph.prototype.getStartEditingCell = function(cell, trigger)
2243	{
2244		// Redirect editing for tables
2245		var style = this.getCellStyle(cell);
2246		var size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, 0));
2247
2248		if (this.isTable(cell) && (!this.isSwimlane(cell) ||
2249			size == 0) && this.getLabel(cell) == '' &&
2250			this.model.getChildCount(cell) > 0)
2251		{
2252			cell = this.model.getChildAt(cell, 0);
2253
2254			style = this.getCellStyle(cell);
2255			size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, 0));
2256		}
2257
2258		// Redirect editing for table rows
2259		if (this.isTableRow(cell) && (!this.isSwimlane(cell) ||
2260			size == 0) && this.getLabel(cell) == '' &&
2261			this.model.getChildCount(cell) > 0)
2262		{
2263			for (var i = 0; i < this.model.getChildCount(cell); i++)
2264			{
2265				var temp = this.model.getChildAt(cell, i);
2266
2267				if (this.isCellEditable(temp))
2268				{
2269					cell = temp;
2270					break;
2271				}
2272			}
2273		}
2274
2275		return cell;
2276	};
2277
2278	/**
2279	 * Returns true if fast zoom preview should be used.
2280	 */
2281	Graph.prototype.copyStyle = function(cell)
2282	{
2283		var style = null;
2284
2285		if (cell != null)
2286		{
2287			style = mxUtils.clone(this.getCurrentCellStyle(cell));
2288
2289			// Handles special case for value "none"
2290			var cellStyle = this.model.getStyle(cell);
2291			var tokens = (cellStyle != null) ? cellStyle.split(';') : [];
2292
2293			for (var j = 0; j < tokens.length; j++)
2294			{
2295				var tmp = tokens[j];
2296		 		var pos = tmp.indexOf('=');
2297
2298		 		if (pos >= 0)
2299		 		{
2300		 			var key = tmp.substring(0, pos);
2301		 			var value = tmp.substring(pos + 1);
2302
2303		 			if (style[key] == null && value == mxConstants.NONE)
2304		 			{
2305		 				style[key] = mxConstants.NONE;
2306		 			}
2307		 		}
2308			}
2309		}
2310
2311		return style;
2312	};
2313
2314	/**
2315	 * Returns true if fast zoom preview should be used.
2316	 */
2317	Graph.prototype.pasteStyle = function(style, cells, keys)
2318	{
2319		keys = (keys != null) ? keys : Graph.pasteStyles;
2320
2321		this.model.beginUpdate();
2322		try
2323		{
2324			for (var i = 0; i < cells.length; i++)
2325			{
2326				var temp = this.getCurrentCellStyle(cells[i]);
2327
2328				for (var j = 0; j < keys.length; j++)
2329				{
2330					var current = temp[keys[j]];
2331					var value = style[keys[j]];
2332
2333					if (current != value && (current != null || value != mxConstants.NONE))
2334					{
2335						this.setCellStyles(keys[j], value, [cells[i]]);
2336					}
2337				}
2338			}
2339		}
2340		finally
2341		{
2342			this.model.endUpdate();
2343		}
2344	};
2345
2346	/**
2347	 * Returns true if fast zoom preview should be used.
2348	 */
2349	Graph.prototype.isFastZoomEnabled = function()
2350	{
2351		return urlParams['zoom'] != 'nocss' && !mxClient.NO_FO && !mxClient.IS_EDGE &&
2352			!this.useCssTransforms && (this.isCssTransformsSupported() || mxClient.IS_IOS);
2353	};
2354
2355	/**
2356	 * Only foreignObject supported for now (no IE11). Safari disabled as it ignores
2357	 * overflow visible on foreignObject in negative space (lightbox and viewer).
2358	 * Check the following test case on page 1 before enabling this in production:
2359	 * https://devhost.jgraph.com/git/drawio/etc/embed/sf-math-fo-clipping.html?dev=1
2360	 */
2361	Graph.prototype.isCssTransformsSupported = function()
2362	{
2363		return this.dialect == mxConstants.DIALECT_SVG && !mxClient.NO_FO &&
2364			(!this.lightbox || !mxClient.IS_SF);
2365	};
2366
2367	/**
2368	 * Function: getCellAt
2369	 *
2370	 * Needs to modify original method for recursive call.
2371	 */
2372	Graph.prototype.getCellAt = function(x, y, parent, vertices, edges, ignoreFn)
2373	{
2374		if (this.useCssTransforms)
2375		{
2376			x = x / this.currentScale - this.currentTranslate.x;
2377			y = y / this.currentScale - this.currentTranslate.y;
2378		}
2379
2380		return this.getScaledCellAt.apply(this, arguments);
2381	};
2382
2383	/**
2384	 * Function: getScaledCellAt
2385	 *
2386	 * Overridden for recursion.
2387	 */
2388	Graph.prototype.getScaledCellAt = function(x, y, parent, vertices, edges, ignoreFn)
2389	{
2390		vertices = (vertices != null) ? vertices : true;
2391		edges = (edges != null) ? edges : true;
2392
2393		if (parent == null)
2394		{
2395			parent = this.getCurrentRoot();
2396
2397			if (parent == null)
2398			{
2399				parent = this.getModel().getRoot();
2400			}
2401		}
2402
2403		if (parent != null)
2404		{
2405			var childCount = this.model.getChildCount(parent);
2406
2407			for (var i = childCount - 1; i >= 0; i--)
2408			{
2409				var cell = this.model.getChildAt(parent, i);
2410				var result = this.getScaledCellAt(x, y, cell, vertices, edges, ignoreFn);
2411
2412				if (result != null)
2413				{
2414					return result;
2415				}
2416				else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) ||
2417					vertices && this.model.isVertex(cell)))
2418				{
2419					var state = this.view.getState(cell);
2420
2421					if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) &&
2422						this.intersects(state, x, y))
2423					{
2424						return cell;
2425					}
2426				}
2427			}
2428		}
2429
2430		return null;
2431	};
2432
2433	/**
2434	 * Returns if the child cells of the given vertex cell state should be resized.
2435	 */
2436	Graph.prototype.isRecursiveVertexResize = function(state)
2437	{
2438		return !this.isSwimlane(state.cell) && this.model.getChildCount(state.cell) > 0 &&
2439			!this.isCellCollapsed(state.cell) && mxUtils.getValue(state.style, 'recursiveResize', '1') == '1' &&
2440			mxUtils.getValue(state.style, 'childLayout', null) == null;
2441	}
2442
2443	/**
2444	 * Returns the first parent with an absolute or no geometry.
2445	 */
2446	Graph.prototype.getAbsoluteParent = function(cell)
2447	{
2448		var result = cell;
2449		var geo = this.getCellGeometry(result);
2450
2451		while (geo != null && geo.relative)
2452		{
2453			result = this.getModel().getParent(result);
2454			geo = this.getCellGeometry(result);
2455		}
2456
2457		return result;
2458	};
2459
2460	/**
2461	 * Returns the first parent that is not a part.
2462	 */
2463	Graph.prototype.isPart = function(cell)
2464	{
2465		return mxUtils.getValue(this.getCurrentCellStyle(cell), 'part', '0') == '1' ||
2466			this.isTableCell(cell) || this.isTableRow(cell);
2467	};
2468
2469	/**
2470	 * Returns the first parent that is not a part.
2471	 */
2472	Graph.prototype.getCompositeParent = function(cell)
2473	{
2474		while (this.isPart(cell))
2475		{
2476			var temp = this.model.getParent(cell);
2477
2478			if (!this.model.isVertex(temp))
2479			{
2480				break;
2481			}
2482
2483			cell = temp;
2484		}
2485
2486		return cell;
2487	};
2488
2489	/**
2490	 * Returns the selection cells where the given function returns false.
2491	 */
2492	Graph.prototype.filterSelectionCells = function(ignoreFn)
2493	{
2494		var cells = this.getSelectionCells();
2495
2496		if (ignoreFn != null)
2497		{
2498			var temp = [];
2499
2500			for (var i = 0; i < cells.length; i++)
2501			{
2502				if (!ignoreFn(cells[i]))
2503				{
2504					temp.push(cells[i]);
2505				}
2506			}
2507
2508			cells = temp;
2509		}
2510
2511		return cells;
2512	};
2513
2514	/**
2515	 * Overrides scrollRectToVisible to fix ignored transform.
2516	 */
2517	var graphScrollRectToVisible = mxGraph.prototype.scrollRectToVisible;
2518	Graph.prototype.scrollRectToVisible = function(r)
2519	{
2520		if (this.useCssTransforms)
2521		{
2522			var s = this.currentScale;
2523			var t = this.currentTranslate;
2524			r = new mxRectangle((r.x + 2 * t.x) * s - t.x,
2525				(r.y + 2 * t.y) * s - t.y,
2526				r.width * s, r.height * s);
2527		}
2528
2529		graphScrollRectToVisible.apply(this, arguments);
2530	};
2531
2532	/**
2533	 * Function: repaint
2534	 *
2535	 * Updates the highlight after a change of the model or view.
2536	 */
2537	mxCellHighlight.prototype.getStrokeWidth = function(state)
2538	{
2539		var s = this.strokeWidth;
2540
2541		if (this.graph.useCssTransforms)
2542		{
2543			s /= this.graph.currentScale;
2544		}
2545
2546		return s;
2547	};
2548
2549	/**
2550	 * Function: getGraphBounds
2551	 *
2552	 * Overrides getGraphBounds to use bounding box from SVG.
2553	 */
2554	mxGraphView.prototype.getGraphBounds = function()
2555	{
2556		var b = this.graphBounds;
2557
2558		if (this.graph.useCssTransforms)
2559		{
2560			var t = this.graph.currentTranslate;
2561			var s = this.graph.currentScale;
2562
2563			b = new mxRectangle(
2564				(b.x + t.x) * s, (b.y + t.y) * s,
2565				b.width * s, b.height * s);
2566		}
2567
2568		return b;
2569	};
2570
2571	/**
2572	 * Overrides to bypass full cell tree validation.
2573	 * TODO: Check if this improves performance
2574	 */
2575	mxGraphView.prototype.viewStateChanged = function()
2576	{
2577		if (this.graph.useCssTransforms)
2578		{
2579			this.validate();
2580			this.graph.sizeDidChange();
2581		}
2582		else
2583		{
2584			this.revalidate();
2585			this.graph.sizeDidChange();
2586		}
2587	};
2588
2589	/**
2590	 * Overrides validate to normalize validation view state and pass
2591	 * current state to CSS transform.
2592	 */
2593	var graphViewValidate = mxGraphView.prototype.validate;
2594	mxGraphView.prototype.validate = function(cell)
2595	{
2596		if (this.graph.useCssTransforms)
2597		{
2598			this.graph.currentScale = this.scale;
2599			this.graph.currentTranslate.x = this.translate.x;
2600			this.graph.currentTranslate.y = this.translate.y;
2601
2602			this.scale = 1;
2603			this.translate.x = 0;
2604			this.translate.y = 0;
2605		}
2606
2607		graphViewValidate.apply(this, arguments);
2608
2609		if (this.graph.useCssTransforms)
2610		{
2611			this.graph.updateCssTransform();
2612
2613			this.scale = this.graph.currentScale;
2614			this.translate.x = this.graph.currentTranslate.x;
2615			this.translate.y = this.graph.currentTranslate.y;
2616		}
2617	};
2618
2619	/**
2620	 * Overrides function to exclude table cells and rows from groups.
2621	 */
2622	var graphGetCellsForGroup = mxGraph.prototype.getCellsForGroup;
2623	Graph.prototype.getCellsForGroup = function(cells)
2624	{
2625		cells = graphGetCellsForGroup.apply(this, arguments);
2626		var result = [];
2627
2628		// Filters selection cells with the same parent
2629		for (var i = 0; i < cells.length; i++)
2630		{
2631			if (!this.isTableRow(cells[i]) &&
2632				!this.isTableCell(cells[i]))
2633			{
2634				result.push(cells[i]);
2635			}
2636		}
2637
2638		return result;
2639	};
2640
2641	/**
2642	 * Overrides function to exclude tables, rows and cells from ungrouping.
2643	 */
2644	var graphGetCellsForUngroup = mxGraph.prototype.getCellsForUngroup;
2645	Graph.prototype.getCellsForUngroup = function(cells)
2646	{
2647		cells = graphGetCellsForUngroup.apply(this, arguments);
2648		var result = [];
2649
2650		// Filters selection cells with the same parent
2651		for (var i = 0; i < cells.length; i++)
2652		{
2653			if (!this.isTable(cells[i]) &&
2654				!this.isTableRow(cells[i]) &&
2655				!this.isTableCell(cells[i]))
2656			{
2657				result.push(cells[i]);
2658			}
2659		}
2660
2661		return result;
2662	};
2663
2664	/**
2665	 * Function: updateCssTransform
2666	 *
2667	 * Zooms out of the graph by <zoomFactor>.
2668	 */
2669	Graph.prototype.updateCssTransform = function()
2670	{
2671		var temp = this.view.getDrawPane();
2672
2673		if (temp != null)
2674		{
2675			var g = temp.parentNode;
2676
2677			if (!this.useCssTransforms)
2678			{
2679				g.removeAttribute('transformOrigin');
2680				g.removeAttribute('transform');
2681			}
2682			else
2683			{
2684				var prev = g.getAttribute('transform');
2685				g.setAttribute('transformOrigin', '0 0');
2686				var s = Math.round(this.currentScale * 100) / 100;
2687				var dx = Math.round(this.currentTranslate.x * 100) / 100;
2688				var dy = Math.round(this.currentTranslate.y * 100) / 100;
2689				g.setAttribute('transform', 'scale(' + s + ',' + s + ')' +
2690					'translate(' + dx + ',' + dy + ')');
2691
2692				// Applies workarounds only if translate has changed
2693				if (prev != g.getAttribute('transform'))
2694				{
2695					this.fireEvent(new mxEventObject('cssTransformChanged'),
2696						'transform', g.getAttribute('transform'));
2697				}
2698			}
2699		}
2700	};
2701
2702	var graphViewValidateBackgroundPage = mxGraphView.prototype.validateBackgroundPage;
2703	mxGraphView.prototype.validateBackgroundPage = function()
2704	{
2705		var useCssTranforms = this.graph.useCssTransforms, scale = this.scale,
2706			translate = this.translate;
2707
2708		if (useCssTranforms)
2709		{
2710			this.scale = this.graph.currentScale;
2711			this.translate = this.graph.currentTranslate;
2712		}
2713
2714		graphViewValidateBackgroundPage.apply(this, arguments);
2715
2716		if (useCssTranforms)
2717		{
2718			this.scale = scale;
2719			this.translate = translate;
2720		}
2721	};
2722
2723	var graphUpdatePageBreaks = mxGraph.prototype.updatePageBreaks;
2724	mxGraph.prototype.updatePageBreaks = function(visible, width, height)
2725	{
2726		var useCssTranforms = this.useCssTransforms, scale = this.view.scale,
2727			translate = this.view.translate;
2728
2729		if (useCssTranforms)
2730		{
2731			this.view.scale = 1;
2732			this.view.translate = new mxPoint(0, 0);
2733			this.useCssTransforms = false;
2734		}
2735
2736		graphUpdatePageBreaks.apply(this, arguments);
2737
2738		if (useCssTranforms)
2739		{
2740			this.view.scale = scale;
2741			this.view.translate = translate;
2742			this.useCssTransforms = true;
2743		}
2744	};
2745})();
2746
2747/**
2748 * Sets the XML node for the current diagram.
2749 */
2750Graph.prototype.isLightboxView = function()
2751{
2752	return this.lightbox;
2753};
2754
2755/**
2756 * Sets the XML node for the current diagram.
2757 */
2758Graph.prototype.isViewer = function()
2759{
2760	return false;
2761};
2762
2763/**
2764 * Installs automatic layout via styles
2765 */
2766Graph.prototype.labelLinkClicked = function(state, elt, evt)
2767{
2768	var href = elt.getAttribute('href');
2769
2770	if (href != null && !this.isCustomLink(href) && ((mxEvent.isLeftMouseButton(evt) &&
2771		!mxEvent.isPopupTrigger(evt)) || mxEvent.isTouchEvent(evt)))
2772	{
2773		if (!this.isEnabled() || this.isCellLocked(state.cell))
2774		{
2775			var target = this.isBlankLink(href) ? this.linkTarget : '_top';
2776			this.openLink(this.getAbsoluteUrl(href), target);
2777		}
2778
2779		mxEvent.consume(evt);
2780	}
2781};
2782
2783/**
2784 * Returns the size of the page format scaled with the page size.
2785 */
2786Graph.prototype.openLink = function(href, target, allowOpener)
2787{
2788	var result = window;
2789
2790	try
2791	{
2792		// Workaround for blocking in same iframe
2793		if (target == '_self' && window != window.top)
2794		{
2795			window.location.href = href;
2796		}
2797		else
2798		{
2799			// Avoids page reload for anchors (workaround for IE but used everywhere)
2800			if (href.substring(0, this.baseUrl.length) == this.baseUrl &&
2801				href.charAt(this.baseUrl.length) == '#' &&
2802				target == '_top' && window == window.top)
2803			{
2804				var hash = href.split('#')[1];
2805
2806				// Forces navigation if on same hash
2807				if (window.location.hash == '#' + hash)
2808				{
2809					window.location.hash = '';
2810				}
2811
2812				window.location.hash = hash;
2813			}
2814			else
2815			{
2816				result = window.open(href, (target != null) ? target : '_blank');
2817
2818				if (result != null && !allowOpener)
2819				{
2820					result.opener = null;
2821				}
2822			}
2823		}
2824	}
2825	catch (e)
2826	{
2827		// ignores permission denied
2828	}
2829
2830	return result;
2831};
2832
2833/**
2834 * Adds support for page links.
2835 */
2836Graph.prototype.getLinkTitle = function(href)
2837{
2838	return href.substring(href.lastIndexOf('/') + 1);
2839};
2840
2841/**
2842 * Adds support for page links.
2843 */
2844Graph.prototype.isCustomLink = function(href)
2845{
2846	return href.substring(0, 5) == 'data:';
2847};
2848
2849/**
2850 * Adds support for page links.
2851 */
2852Graph.prototype.customLinkClicked = function(link)
2853{
2854	return false;
2855};
2856
2857/**
2858 * Returns true if the given href references an external protocol that
2859 * should never open in a new window. Default returns true for mailto.
2860 */
2861Graph.prototype.isExternalProtocol = function(href)
2862{
2863	return href.substring(0, 7) === 'mailto:';
2864};
2865
2866/**
2867 * Hook for links to open in same window. Default returns true for anchors,
2868 * links to same domain or if target == 'self' in the config.
2869 */
2870Graph.prototype.isBlankLink = function(href)
2871{
2872	return !this.isExternalProtocol(href) &&
2873		(this.linkPolicy === 'blank' ||
2874		(this.linkPolicy !== 'self' &&
2875		!this.isRelativeUrl(href) &&
2876		href.substring(0, this.domainUrl.length) !== this.domainUrl));
2877};
2878
2879/**
2880 *
2881 */
2882Graph.prototype.isRelativeUrl = function(url)
2883{
2884	return url != null && !this.absoluteUrlPattern.test(url) &&
2885		url.substring(0, 5) !== 'data:' &&
2886		!this.isExternalProtocol(url);
2887};
2888
2889/**
2890 *
2891 */
2892Graph.prototype.getAbsoluteUrl = function(url)
2893{
2894	if (url != null && this.isRelativeUrl(url))
2895	{
2896		if (url.charAt(0) == '#')
2897		{
2898			url = this.baseUrl + url;
2899		}
2900		else if (url.charAt(0) == '/')
2901		{
2902			url = this.domainUrl + url;
2903		}
2904		else
2905		{
2906			url = this.domainPathUrl + url;
2907		}
2908	}
2909
2910	return url;
2911};
2912
2913/**
2914 * Installs automatic layout via styles
2915 */
2916Graph.prototype.initLayoutManager = function()
2917{
2918	this.layoutManager = new mxLayoutManager(this);
2919
2920	this.layoutManager.hasLayout = function(cell, eventName)
2921	{
2922		return this.graph.getCellStyle(cell)['childLayout'] != null;
2923	};
2924
2925	this.layoutManager.getLayout = function(cell, eventName)
2926	{
2927		var parent = this.graph.model.getParent(cell);
2928
2929		// Executes layouts from top to bottom except for nested layouts where
2930		// child layouts are executed before and after the parent layout runs
2931		// in case the layout changes the size of the child cell
2932		if (eventName != mxEvent.BEGIN_UPDATE || this.hasLayout(parent, eventName))
2933		{
2934			var style = this.graph.getCellStyle(cell);
2935
2936			if (style['childLayout'] == 'stackLayout')
2937			{
2938				var stackLayout = new mxStackLayout(this.graph, true);
2939				stackLayout.resizeParentMax = mxUtils.getValue(style, 'resizeParentMax', '1') == '1';
2940				stackLayout.horizontal = mxUtils.getValue(style, 'horizontalStack', '1') == '1';
2941				stackLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1';
2942				stackLayout.resizeLast = mxUtils.getValue(style, 'resizeLast', '0') == '1';
2943				stackLayout.spacing = style['stackSpacing'] || stackLayout.spacing;
2944				stackLayout.border = style['stackBorder'] || stackLayout.border;
2945				stackLayout.marginLeft = style['marginLeft'] || 0;
2946				stackLayout.marginRight = style['marginRight'] || 0;
2947				stackLayout.marginTop = style['marginTop'] || 0;
2948				stackLayout.marginBottom = style['marginBottom'] || 0;
2949				stackLayout.allowGaps = style['allowGaps'] || 0;
2950				stackLayout.fill = true;
2951
2952				if (stackLayout.allowGaps)
2953				{
2954					stackLayout.gridSize = parseFloat(mxUtils.getValue(style, 'stackUnitSize', 20));
2955				}
2956
2957				return stackLayout;
2958			}
2959			else if (style['childLayout'] == 'treeLayout')
2960			{
2961				var treeLayout = new mxCompactTreeLayout(this.graph);
2962				treeLayout.horizontal = mxUtils.getValue(style, 'horizontalTree', '1') == '1';
2963				treeLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1';
2964				treeLayout.groupPadding = mxUtils.getValue(style, 'parentPadding', 20);
2965				treeLayout.levelDistance = mxUtils.getValue(style, 'treeLevelDistance', 30);
2966				treeLayout.maintainParentLocation = true;
2967				treeLayout.edgeRouting = false;
2968				treeLayout.resetEdges = false;
2969
2970				return treeLayout;
2971			}
2972			else if (style['childLayout'] == 'flowLayout')
2973			{
2974				var flowLayout = new mxHierarchicalLayout(this.graph, mxUtils.getValue(style,
2975					'flowOrientation', mxConstants.DIRECTION_EAST));
2976				flowLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1';
2977				flowLayout.parentBorder = mxUtils.getValue(style, 'parentPadding', 20);
2978				flowLayout.maintainParentLocation = true;
2979
2980				// Special undocumented styles for changing the hierarchical
2981				flowLayout.intraCellSpacing = mxUtils.getValue(style, 'intraCellSpacing',
2982					mxHierarchicalLayout.prototype.intraCellSpacing);
2983				flowLayout.interRankCellSpacing = mxUtils.getValue(style, 'interRankCellSpacing',
2984					mxHierarchicalLayout.prototype.interRankCellSpacing);
2985				flowLayout.interHierarchySpacing = mxUtils.getValue(style, 'interHierarchySpacing',
2986					mxHierarchicalLayout.prototype.interHierarchySpacing);
2987				flowLayout.parallelEdgeSpacing = mxUtils.getValue(style, 'parallelEdgeSpacing',
2988					mxHierarchicalLayout.prototype.parallelEdgeSpacing);
2989
2990				return flowLayout;
2991			}
2992			else if (style['childLayout'] == 'circleLayout')
2993			{
2994				return new mxCircleLayout(this.graph);
2995			}
2996			else if (style['childLayout'] == 'organicLayout')
2997			{
2998				return new mxFastOrganicLayout(this.graph);
2999			}
3000			else if (style['childLayout'] == 'tableLayout')
3001			{
3002				return new TableLayout(this.graph);
3003			}
3004		}
3005
3006		return null;
3007	};
3008};
3009
3010/**
3011 * Returns the metadata of the given cells as a JSON object.
3012 */
3013Graph.prototype.getDataForCells = function(cells)
3014{
3015	var result = [];
3016
3017	for (var i = 0; i < cells.length; i++)
3018	{
3019		var attrs = (cells[i].value != null) ? cells[i].value.attributes : null;
3020		var row = {};
3021		row.id = cells[i].id;
3022
3023		if (attrs != null)
3024		{
3025			for (var j = 0; j < attrs.length; j++)
3026			{
3027				row[attrs[j].nodeName] = attrs[j].nodeValue;
3028			}
3029		}
3030		else
3031		{
3032			row.label = this.convertValueToString(cells[i]);
3033		}
3034
3035		result.push(row);
3036	}
3037
3038	return result;
3039};
3040
3041/**
3042 * Returns the DOM nodes for the given cells.
3043 */
3044Graph.prototype.getNodesForCells = function(cells)
3045{
3046	var nodes = [];
3047
3048	for (var i = 0; i < cells.length; i++)
3049	{
3050		var state = this.view.getState(cells[i]);
3051
3052		if (state != null)
3053		{
3054			var shapes = this.cellRenderer.getShapesForState(state);
3055
3056			for (var j = 0; j < shapes.length; j++)
3057			{
3058				if (shapes[j] != null && shapes[j].node != null)
3059				{
3060					nodes.push(shapes[j].node);
3061				}
3062			}
3063
3064			// Adds folding icon
3065			if (state.control != null && state.control.node != null)
3066			{
3067				nodes.push(state.control.node);
3068			}
3069		}
3070	}
3071
3072	return nodes;
3073};
3074
3075/**
3076 * Creates animations for the given cells.
3077 */
3078 Graph.prototype.createWipeAnimations = function(cells, wipeIn)
3079 {
3080	var animations = [];
3081
3082	for (var i = 0; i < cells.length; i++)
3083	{
3084		var state = this.view.getState(cells[i]);
3085
3086		if (state != null && state.shape != null)
3087		{
3088			// TODO: include descendants
3089			if (this.model.isEdge(state.cell) &&
3090				state.absolutePoints != null &&
3091				state.absolutePoints.length > 1)
3092			{
3093				animations.push(this.createEdgeWipeAnimation(state, wipeIn));
3094			}
3095			else if (this.model.isVertex(state.cell) &&
3096				state.shape.bounds != null)
3097			{
3098				animations.push(this.createVertexWipeAnimation(state, wipeIn));
3099			}
3100		}
3101	}
3102
3103	return animations;
3104};
3105
3106/**
3107 * Creates an object to show the given edge cell state.
3108 */
3109Graph.prototype.createEdgeWipeAnimation = function(state, wipeIn)
3110{
3111	var pts = state.absolutePoints.slice();
3112	var segs = state.segments;
3113	var total = state.length;
3114	var n = pts.length;
3115
3116	return {
3117		execute: mxUtils.bind(this, function(step, steps)
3118		{
3119			if (state.shape != null)
3120			{
3121				var pts2 = [pts[0]];
3122				var f = step / steps;
3123
3124				if (!wipeIn)
3125				{
3126					f = 1 - f;
3127				}
3128
3129				var dist = total * f;
3130
3131				for (var i = 1; i < n; i++)
3132				{
3133					if (dist <= segs[i - 1])
3134					{
3135						pts2.push(new mxPoint(pts[i - 1].x + (pts[i].x - pts[i - 1].x) * dist / segs[i - 1],
3136							pts[i - 1].y + (pts[i].y - pts[i - 1].y) * dist / segs[i - 1]));
3137
3138						break;
3139					}
3140					else
3141					{
3142						dist -= segs[i - 1];
3143						pts2.push(pts[i]);
3144					}
3145				}
3146
3147				state.shape.points = pts2;
3148				state.shape.redraw();
3149
3150				if (step == 0)
3151				{
3152					Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), 1);
3153				}
3154
3155				if (state.text != null && state.text.node != null)
3156				{
3157					state.text.node.style.opacity = f;
3158				}
3159			}
3160		}),
3161		stop: mxUtils.bind(this, function()
3162		{
3163			if (state.shape != null)
3164			{
3165				state.shape.points = pts;
3166				state.shape.redraw();
3167
3168				if (state.text != null && state.text.node != null)
3169				{
3170					state.text.node.style.opacity = ''
3171				}
3172
3173				Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), (wipeIn) ? 1 : 0);
3174			}
3175		})
3176	};
3177};
3178
3179 /**
3180  * Creates an object to show the given vertex cell state.
3181  */
3182Graph.prototype.createVertexWipeAnimation = function(state, wipeIn)
3183{
3184	var bds = new mxRectangle.fromRectangle(state.shape.bounds);
3185
3186	return {
3187		execute: mxUtils.bind(this, function(step, steps)
3188		{
3189			if (state.shape != null)
3190			{
3191				var f = step / steps;
3192
3193				if (!wipeIn)
3194				{
3195					f = 1 - f;
3196				}
3197
3198				state.shape.bounds = new mxRectangle(bds.x, bds.y, bds.width * f, bds.height);
3199				state.shape.redraw();
3200
3201				if (step == 0)
3202				{
3203					Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), 1);
3204				}
3205
3206				if (state.text != null && state.text.node != null)
3207				{
3208					state.text.node.style.opacity = f;
3209				}
3210			}
3211		}),
3212		stop: mxUtils.bind(this, function()
3213		{
3214			if (state.shape != null)
3215			{
3216				state.shape.bounds = bds;
3217				state.shape.redraw();
3218
3219				if (state.text != null && state.text.node != null)
3220				{
3221					state.text.node.style.opacity = ''
3222				}
3223
3224				Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), (wipeIn) ? 1 : 0);
3225			}
3226		})
3227	};
3228};
3229
3230/**
3231 * Runs the animations for the given cells.
3232 */
3233 Graph.prototype.executeAnimations = function(animations, done, steps, delay)
3234 {
3235	steps = (steps != null) ? steps : 30;
3236	delay = (delay != null) ? delay : 30;
3237	var thread = null;
3238	var step = 0;
3239
3240	var animate = mxUtils.bind(this, function()
3241	{
3242		if (step == steps || this.stoppingCustomActions)
3243		{
3244			window.clearInterval(thread);
3245
3246			for (var i = 0; i < animations.length; i++)
3247			{
3248				animations[i].stop();
3249			}
3250
3251			if (done != null)
3252			{
3253				done();
3254			}
3255		}
3256		else
3257		{
3258			for (var i = 0; i < animations.length; i++)
3259			{
3260				animations[i].execute(step, steps);
3261			}
3262		}
3263
3264		step++;
3265	});
3266
3267	thread = window.setInterval(animate, delay);
3268	animate();
3269};
3270
3271/**
3272 * Returns the size of the page format scaled with the page size.
3273 */
3274Graph.prototype.getPageSize = function()
3275{
3276	return (this.pageVisible) ? new mxRectangle(0, 0, this.pageFormat.width * this.pageScale,
3277			this.pageFormat.height * this.pageScale) : this.scrollTileSize;
3278};
3279
3280/**
3281 * Returns a rectangle describing the position and count of the
3282 * background pages, where x and y are the position of the top,
3283 * left page and width and height are the vertical and horizontal
3284 * page count.
3285 */
3286Graph.prototype.getPageLayout = function()
3287{
3288	var size = this.getPageSize();
3289	var bounds = this.getGraphBounds();
3290
3291	if (bounds.width == 0 || bounds.height == 0)
3292	{
3293		return new mxRectangle(0, 0, 1, 1);
3294	}
3295	else
3296	{
3297		var x0 = Math.floor(Math.ceil(bounds.x / this.view.scale -
3298			this.view.translate.x) / size.width);
3299		var y0 = Math.floor(Math.ceil(bounds.y / this.view.scale -
3300			this.view.translate.y) / size.height);
3301		var w0 = Math.ceil((Math.floor((bounds.x + bounds.width) / this.view.scale) -
3302			this.view.translate.x) / size.width) - x0;
3303		var h0 = Math.ceil((Math.floor((bounds.y + bounds.height) / this.view.scale) -
3304			this.view.translate.y) / size.height) - y0;
3305
3306		return new mxRectangle(x0, y0, w0, h0);
3307	}
3308};
3309
3310/**
3311 * Sanitizes the given HTML markup.
3312 */
3313Graph.prototype.sanitizeHtml = function(value, editing)
3314{
3315	return Graph.sanitizeHtml(value, editing);
3316};
3317
3318/**
3319 * Revalidates all cells with placeholders in the current graph model.
3320 */
3321Graph.prototype.updatePlaceholders = function()
3322{
3323	var model = this.model;
3324	var validate = false;
3325
3326	for (var key in this.model.cells)
3327	{
3328		var cell = this.model.cells[key];
3329
3330		if (this.isReplacePlaceholders(cell))
3331		{
3332			this.view.invalidate(cell, false, false);
3333			validate = true;
3334		}
3335	}
3336
3337	if (validate)
3338	{
3339		this.view.validate();
3340	}
3341};
3342
3343/**
3344 * Adds support for placeholders in labels.
3345 */
3346Graph.prototype.isReplacePlaceholders = function(cell)
3347{
3348	return cell.value != null && typeof(cell.value) == 'object' &&
3349		cell.value.getAttribute('placeholders') == '1';
3350};
3351
3352/**
3353 * Returns true if the given mouse wheel event should be used for zooming. This
3354 * is invoked if no dialogs are showing and returns true with Alt or Control
3355 * (or cmd in macOS only) is pressed.
3356 */
3357Graph.prototype.isZoomWheelEvent = function(evt)
3358{
3359	return (Graph.zoomWheel && !mxEvent.isShiftDown(evt) && !mxEvent.isMetaDown(evt) &&
3360		!mxEvent.isAltDown(evt) && (!mxEvent.isControlDown(evt) || mxClient.IS_MAC)) ||
3361		(!Graph.zoomWheel && (mxEvent.isAltDown(evt) || mxEvent.isControlDown(evt)));
3362};
3363
3364/**
3365 * Returns true if the given scroll wheel event should be used for scrolling.
3366 */
3367Graph.prototype.isScrollWheelEvent = function(evt)
3368{
3369	return !this.isZoomWheelEvent(evt);
3370};
3371
3372/**
3373 * Adds Alt+click to select cells behind cells (Shift+Click on Chrome OS).
3374 */
3375Graph.prototype.isTransparentClickEvent = function(evt)
3376{
3377	return mxEvent.isAltDown(evt) || (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt));
3378};
3379
3380/**
3381 * Adds ctrl+shift+connect to disable connections.
3382 */
3383Graph.prototype.isIgnoreTerminalEvent = function(evt)
3384{
3385	return mxEvent.isAltDown(evt) && !mxEvent.isShiftDown(evt) &&
3386		!mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt);
3387};
3388
3389/**
3390 * Returns true if the given edge should be ignored.
3391 */
3392Graph.prototype.isEdgeIgnored = function(cell)
3393{
3394	var result = false;
3395
3396	if (cell != null)
3397	{
3398		var style = this.getCurrentCellStyle(cell);
3399
3400		result = mxUtils.getValue(style, 'ignoreEdge', '0') == '1';
3401	}
3402
3403	return result;
3404};
3405
3406/**
3407 * Adds support for placeholders in labels.
3408 */
3409Graph.prototype.isSplitTarget = function(target, cells, evt)
3410{
3411	return !this.model.isEdge(cells[0]) &&
3412		!mxEvent.isAltDown(evt) && !mxEvent.isShiftDown(evt) &&
3413		mxGraph.prototype.isSplitTarget.apply(this, arguments);
3414};
3415
3416/**
3417 * Adds support for placeholders in labels.
3418 */
3419Graph.prototype.getLabel = function(cell)
3420{
3421	var result = mxGraph.prototype.getLabel.apply(this, arguments);
3422
3423	if (result != null && this.isReplacePlaceholders(cell) && cell.getAttribute('placeholder') == null)
3424	{
3425		result = this.replacePlaceholders(cell, result);
3426	}
3427
3428	return result;
3429};
3430
3431/**
3432 * Adds labelMovable style.
3433 */
3434Graph.prototype.isLabelMovable = function(cell)
3435{
3436	var style = this.getCurrentCellStyle(cell);
3437
3438	return !this.isCellLocked(cell) &&
3439		((this.model.isEdge(cell) && this.edgeLabelsMovable) ||
3440		(this.model.isVertex(cell) && (this.vertexLabelsMovable ||
3441		mxUtils.getValue(style, 'labelMovable', '0') == '1')));
3442};
3443
3444/**
3445 * Adds event if grid size is changed.
3446 */
3447Graph.prototype.setGridSize = function(value)
3448{
3449	this.gridSize = value;
3450	this.fireEvent(new mxEventObject('gridSizeChanged'));
3451};
3452
3453/**
3454 * Adds event if default parent is changed.
3455 */
3456Graph.prototype.setDefaultParent = function(cell)
3457{
3458	this.defaultParent = cell;
3459	this.fireEvent(new mxEventObject('defaultParentChanged'));
3460};
3461
3462/**
3463 * Function: getClickableLinkForCell
3464 *
3465 * Returns the first non-null link for the cell or its ancestors.
3466 *
3467 * Parameters:
3468 *
3469 * cell - <mxCell> whose link should be returned.
3470 */
3471Graph.prototype.getClickableLinkForCell = function(cell)
3472{
3473	do
3474	{
3475		var link = this.getLinkForCell(cell);
3476
3477		if (link != null)
3478		{
3479			return link;
3480		}
3481
3482		cell = this.model.getParent(cell);
3483	} while (cell != null);
3484
3485	return null;
3486};
3487
3488/**
3489 * Private helper method.
3490 */
3491Graph.prototype.getGlobalVariable = function(name)
3492{
3493	var val = null;
3494
3495	if (name == 'date')
3496	{
3497		val = new Date().toLocaleDateString();
3498	}
3499	else if (name == 'time')
3500	{
3501		val = new Date().toLocaleTimeString();
3502	}
3503	else if (name == 'timestamp')
3504	{
3505		val = new Date().toLocaleString();
3506	}
3507	else if (name.substring(0, 5) == 'date{')
3508	{
3509		var fmt = name.substring(5, name.length - 1);
3510		val = this.formatDate(new Date(), fmt);
3511	}
3512
3513	return val;
3514};
3515
3516/**
3517 * Formats a date, see http://blog.stevenlevithan.com/archives/date-time-format
3518 */
3519Graph.prototype.formatDate = function(date, mask, utc)
3520{
3521	// LATER: Cache regexs
3522	if (this.dateFormatCache == null)
3523	{
3524		this.dateFormatCache = {
3525			i18n: {
3526			    dayNames: [
3527			        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
3528			        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
3529			    ],
3530			    monthNames: [
3531			        "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
3532			        "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
3533			    ]
3534			},
3535
3536			masks: {
3537			    "default":      "ddd mmm dd yyyy HH:MM:ss",
3538			    shortDate:      "m/d/yy",
3539			    mediumDate:     "mmm d, yyyy",
3540			    longDate:       "mmmm d, yyyy",
3541			    fullDate:       "dddd, mmmm d, yyyy",
3542			    shortTime:      "h:MM TT",
3543			    mediumTime:     "h:MM:ss TT",
3544			    longTime:       "h:MM:ss TT Z",
3545			    isoDate:        "yyyy-mm-dd",
3546			    isoTime:        "HH:MM:ss",
3547			    isoDateTime:    "yyyy-mm-dd'T'HH:MM:ss",
3548			    isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
3549			}
3550		};
3551	}
3552
3553    var dF = this.dateFormatCache;
3554	var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
3555    	timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
3556    	timezoneClip = /[^-+\dA-Z]/g,
3557    	pad = function (val, len) {
3558			val = String(val);
3559			len = len || 2;
3560			while (val.length < len) val = "0" + val;
3561			return val;
3562		};
3563
3564    // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
3565    if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
3566        mask = date;
3567        date = undefined;
3568    }
3569
3570    // Passing date through Date applies Date.parse, if necessary
3571    date = date ? new Date(date) : new Date;
3572    if (isNaN(date)) throw SyntaxError("invalid date");
3573
3574    mask = String(dF.masks[mask] || mask || dF.masks["default"]);
3575
3576    // Allow setting the utc argument via the mask
3577    if (mask.slice(0, 4) == "UTC:") {
3578        mask = mask.slice(4);
3579        utc = true;
3580    }
3581
3582    var _ = utc ? "getUTC" : "get",
3583        d = date[_ + "Date"](),
3584        D = date[_ + "Day"](),
3585        m = date[_ + "Month"](),
3586        y = date[_ + "FullYear"](),
3587        H = date[_ + "Hours"](),
3588        M = date[_ + "Minutes"](),
3589        s = date[_ + "Seconds"](),
3590        L = date[_ + "Milliseconds"](),
3591        o = utc ? 0 : date.getTimezoneOffset(),
3592        flags = {
3593            d:    d,
3594            dd:   pad(d),
3595            ddd:  dF.i18n.dayNames[D],
3596            dddd: dF.i18n.dayNames[D + 7],
3597            m:    m + 1,
3598            mm:   pad(m + 1),
3599            mmm:  dF.i18n.monthNames[m],
3600            mmmm: dF.i18n.monthNames[m + 12],
3601            yy:   String(y).slice(2),
3602            yyyy: y,
3603            h:    H % 12 || 12,
3604            hh:   pad(H % 12 || 12),
3605            H:    H,
3606            HH:   pad(H),
3607            M:    M,
3608            MM:   pad(M),
3609            s:    s,
3610            ss:   pad(s),
3611            l:    pad(L, 3),
3612            L:    pad(L > 99 ? Math.round(L / 10) : L),
3613            t:    H < 12 ? "a"  : "p",
3614            tt:   H < 12 ? "am" : "pm",
3615            T:    H < 12 ? "A"  : "P",
3616            TT:   H < 12 ? "AM" : "PM",
3617            Z:    utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
3618            o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
3619            S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
3620        };
3621
3622    return mask.replace(token, function ($0)
3623    {
3624        return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
3625    });
3626};
3627
3628/**
3629 *
3630 */
3631Graph.prototype.getLayerForCells = function(cells)
3632{
3633	var result = null;
3634
3635	if (cells.length > 0)
3636	{
3637		result = cells[0];
3638
3639		while (!this.model.isLayer(result))
3640		{
3641			result = this.model.getParent(result);
3642		}
3643
3644		for (var i = 1; i < cells.length; i++)
3645		{
3646			if (!this.model.isAncestor(result, cells[i]))
3647			{
3648				result = null;
3649				break;
3650			}
3651		}
3652	}
3653
3654	return result;
3655};
3656
3657/**
3658 *
3659 */
3660Graph.prototype.createLayersDialog = function(onchange, inverted)
3661{
3662	var div = document.createElement('div');
3663	div.style.position = 'absolute';
3664
3665	var model = this.getModel();
3666	var childCount = model.getChildCount(model.root);
3667
3668	for (var i = 0; i < childCount; i++)
3669	{
3670		(mxUtils.bind(this, function(layer)
3671		{
3672			var title = this.convertValueToString(layer) ||
3673				(mxResources.get('background') || 'Background');
3674
3675			var span = document.createElement('div');
3676			span.style.overflow = 'hidden';
3677			span.style.textOverflow = 'ellipsis';
3678			span.style.padding = '2px';
3679			span.style.whiteSpace = 'nowrap';
3680			span.style.cursor = 'pointer';
3681			span.setAttribute('title', mxResources.get(
3682				model.isVisible(layer) ?
3683				'hideIt' : 'show', [title]));
3684
3685			var inp = document.createElement('img');
3686			inp.setAttribute('draggable', 'false');
3687			inp.setAttribute('align', 'absmiddle');
3688			inp.setAttribute('border', '0');
3689			inp.style.position = 'relative';
3690			inp.style.width = '16px';
3691			inp.style.padding = '0px 6px 0 4px';
3692
3693			if (inverted)
3694			{
3695				inp.style.filter = 'invert(100%)';
3696				inp.style.top = '-2px';
3697			}
3698
3699			span.appendChild(inp);
3700
3701			mxUtils.write(span, title);
3702			div.appendChild(span);
3703
3704			function update()
3705			{
3706				if (model.isVisible(layer))
3707				{
3708					inp.setAttribute('src', Editor.visibleImage);
3709					mxUtils.setOpacity(span, 75);
3710				}
3711				else
3712				{
3713					inp.setAttribute('src', Editor.hiddenImage);
3714					mxUtils.setOpacity(span, 25);
3715				}
3716			};
3717
3718			mxEvent.addListener(span, 'click', function()
3719			{
3720				model.setVisible(layer, !model.isVisible(layer));
3721				update();
3722
3723				if (onchange != null)
3724				{
3725					onchange(layer);
3726				}
3727			});
3728
3729			update();
3730		})(model.getChildAt(model.root, i)));
3731	}
3732
3733	return div;
3734};
3735
3736/**
3737 * Private helper method.
3738 */
3739Graph.prototype.replacePlaceholders = function(cell, str, vars, translate)
3740{
3741	var result = [];
3742
3743	if (str != null)
3744	{
3745		var last = 0;
3746
3747		while (match = this.placeholderPattern.exec(str))
3748		{
3749			var val = match[0];
3750
3751			if (val.length > 2 && val != '%label%' && val != '%tooltip%')
3752			{
3753				var tmp = null;
3754
3755				if (match.index > last && str.charAt(match.index - 1) == '%')
3756				{
3757					tmp = val.substring(1);
3758				}
3759				else
3760				{
3761					var name = val.substring(1, val.length - 1);
3762
3763					// Workaround for invalid char for getting attribute in older versions of IE
3764					if (name == 'id')
3765					{
3766						tmp = cell.id;
3767					}
3768					else if (name.indexOf('{') < 0)
3769					{
3770						var current = cell;
3771
3772						while (tmp == null && current != null)
3773						{
3774							if (current.value != null && typeof(current.value) == 'object')
3775							{
3776								if (Graph.translateDiagram && Graph.diagramLanguage != null)
3777								{
3778									tmp = current.getAttribute(name + '_' + Graph.diagramLanguage);
3779								}
3780
3781								if (tmp == null)
3782								{
3783									tmp = (current.hasAttribute(name)) ? ((current.getAttribute(name) != null) ?
3784										current.getAttribute(name) : '') : null;
3785								}
3786							}
3787
3788							current = this.model.getParent(current);
3789						}
3790					}
3791
3792					if (tmp == null)
3793					{
3794						tmp = this.getGlobalVariable(name);
3795					}
3796
3797					if (tmp == null && vars != null)
3798					{
3799						tmp = vars[name];
3800					}
3801				}
3802
3803				result.push(str.substring(last, match.index) + ((tmp != null) ? tmp : val));
3804				last = match.index + val.length;
3805			}
3806		}
3807
3808		result.push(str.substring(last));
3809	}
3810
3811	return result.join('');
3812};
3813
3814/**
3815 * Resolves the given cells in the model and selects them.
3816 */
3817Graph.prototype.restoreSelection = function(cells)
3818{
3819	if (cells != null && cells.length > 0)
3820	{
3821		var temp = [];
3822
3823		for (var i = 0; i < cells.length; i++)
3824		{
3825			var newCell = this.model.getCell(cells[i].id);
3826
3827			if (newCell != null)
3828			{
3829				temp.push(newCell);
3830			}
3831		}
3832
3833		this.setSelectionCells(temp);
3834	}
3835	else
3836	{
3837		this.clearSelection();
3838	}
3839};
3840
3841/**
3842 * Selects cells for connect vertex return value.
3843 */
3844Graph.prototype.selectCellsForConnectVertex = function(cells, evt, hoverIcons)
3845{
3846	// Selects only target vertex if one exists
3847	if (cells.length == 2 && this.model.isVertex(cells[1]))
3848	{
3849		this.setSelectionCell(cells[1]);
3850		this.scrollCellToVisible(cells[1]);
3851
3852		if (hoverIcons != null)
3853		{
3854			// Adds hover icons for cloned vertex or hides icons
3855			if (mxEvent.isTouchEvent(evt))
3856			{
3857				hoverIcons.update(hoverIcons.getState(this.view.getState(cells[1])));
3858			}
3859			else
3860			{
3861				hoverIcons.reset();
3862			}
3863		}
3864	}
3865	else
3866	{
3867		this.setSelectionCells(cells);
3868	}
3869};
3870
3871/**
3872 * Never connects children in stack layouts or tables.
3873 */
3874Graph.prototype.isCloneConnectSource = function(source)
3875{
3876	var layout = null;
3877
3878	if (this.layoutManager != null)
3879	{
3880		layout = this.layoutManager.getLayout(this.model.getParent(source));
3881	}
3882
3883	return this.isTableRow(source) || this.isTableCell(source) ||
3884		(layout != null && layout.constructor == mxStackLayout);
3885};
3886
3887/**
3888 * Adds a connection to the given vertex or clones the vertex in special layout
3889 * containers without creating a connection.
3890 */
3891Graph.prototype.connectVertex = function(source, direction, length, evt, forceClone, ignoreCellAt, createTarget, done)
3892{
3893	ignoreCellAt = (ignoreCellAt) ? ignoreCellAt : false;
3894
3895	// Ignores relative edge labels
3896	if (source.geometry.relative && this.model.isEdge(source.parent))
3897	{
3898		return [];
3899	}
3900
3901	// Uses parent for relative child cells
3902	while (source.geometry.relative && this.model.isVertex(source.parent))
3903	{
3904		source = source.parent;
3905	}
3906
3907	// Handles clone connect sources
3908	var cloneSource = this.isCloneConnectSource(source);
3909	var composite = (cloneSource) ? source : this.getCompositeParent(source);
3910
3911	var pt = (source.geometry.relative && source.parent.geometry != null) ?
3912		new mxPoint(source.parent.geometry.width * source.geometry.x,
3913			source.parent.geometry.height * source.geometry.y) :
3914		new mxPoint(composite.geometry.x, composite.geometry.y);
3915
3916	if (direction == mxConstants.DIRECTION_NORTH)
3917	{
3918		pt.x += composite.geometry.width / 2;
3919		pt.y -= length ;
3920	}
3921	else if (direction == mxConstants.DIRECTION_SOUTH)
3922	{
3923		pt.x += composite.geometry.width / 2;
3924		pt.y += composite.geometry.height + length;
3925	}
3926	else if (direction == mxConstants.DIRECTION_WEST)
3927	{
3928		pt.x -= length;
3929		pt.y += composite.geometry.height / 2;
3930	}
3931	else
3932	{
3933		pt.x += composite.geometry.width + length;
3934		pt.y += composite.geometry.height / 2;
3935	}
3936
3937	var parentState = this.view.getState(this.model.getParent(source));
3938	var s = this.view.scale;
3939	var t = this.view.translate;
3940	var dx = t.x * s;
3941	var dy = t.y * s;
3942
3943	if (parentState != null && this.model.isVertex(parentState.cell))
3944	{
3945		dx = parentState.x;
3946		dy = parentState.y;
3947	}
3948
3949	// Workaround for relative child cells
3950	if (this.model.isVertex(source.parent) && source.geometry.relative)
3951	{
3952		pt.x += source.parent.geometry.x;
3953		pt.y += source.parent.geometry.y;
3954	}
3955
3956	// Checks end point for target cell and container
3957	var rect = (!ignoreCellAt) ? new mxRectangle(dx + pt.x * s, dy + pt.y * s).grow(40 * s) : null;
3958	var tempCells = (rect != null) ? this.getCells(0, 0, 0, 0, null, null, rect, null, true) : null;
3959	var sourceState = this.view.getState(source);
3960	var container = null;
3961	var target = null;
3962
3963	if (tempCells != null)
3964	{
3965		tempCells = tempCells.reverse();
3966
3967		for (var i = 0; i < tempCells.length; i++)
3968		{
3969			if (!this.isCellLocked(tempCells[i]) && !this.model.isEdge(tempCells[i]) && tempCells[i] != source)
3970			{
3971				// Direct parent overrides all possible containers
3972				if (!this.model.isAncestor(source, tempCells[i]) && this.isContainer(tempCells[i]) &&
3973					(container == null || tempCells[i] == this.model.getParent(source)))
3974				{
3975					container = tempCells[i];
3976				}
3977				// Containers are used as target cells but swimlanes are used as parents
3978				else if (target == null && this.isCellConnectable(tempCells[i]) &&
3979					!this.model.isAncestor(tempCells[i], source) &&
3980					!this.isSwimlane(tempCells[i]))
3981				{
3982					var targetState = this.view.getState(tempCells[i]);
3983
3984					if (sourceState != null && targetState != null && !mxUtils.intersects(sourceState, targetState))
3985					{
3986						target = tempCells[i];
3987					}
3988				}
3989			}
3990		}
3991	}
3992
3993	var duplicate = (!mxEvent.isShiftDown(evt) || mxEvent.isControlDown(evt)) || forceClone;
3994
3995	if (duplicate && (urlParams['sketch'] != '1' || forceClone))
3996	{
3997		if (direction == mxConstants.DIRECTION_NORTH)
3998		{
3999			pt.y -= source.geometry.height / 2;
4000		}
4001		else if (direction == mxConstants.DIRECTION_SOUTH)
4002		{
4003			pt.y += source.geometry.height / 2;
4004		}
4005		else if (direction == mxConstants.DIRECTION_WEST)
4006		{
4007			pt.x -= source.geometry.width / 2;
4008		}
4009		else
4010		{
4011			pt.x += source.geometry.width / 2;
4012		}
4013	}
4014
4015	var result = [];
4016	var realTarget = target;
4017	target = container;
4018
4019	var execute = mxUtils.bind(this, function(targetCell)
4020	{
4021		if (createTarget == null || targetCell != null || (target == null && cloneSource))
4022		{
4023			this.model.beginUpdate();
4024			try
4025			{
4026				if (realTarget == null && duplicate)
4027				{
4028					// Handles relative and composite cells
4029					var cellToClone = this.getAbsoluteParent((targetCell != null) ? targetCell : source);
4030					cellToClone =  (cloneSource) ? source : this.getCompositeParent(cellToClone);
4031					realTarget = (targetCell != null) ? targetCell : this.duplicateCells([cellToClone], false)[0];
4032
4033					if (targetCell != null)
4034					{
4035						this.addCells([realTarget], this.model.getParent(source), null, null, null, true);
4036					}
4037
4038					var geo = this.getCellGeometry(realTarget);
4039
4040					if (geo != null)
4041					{
4042						if (targetCell != null && urlParams['sketch'] == '1')
4043						{
4044							if (direction == mxConstants.DIRECTION_NORTH)
4045							{
4046								pt.y -= geo.height / 2;
4047							}
4048							else if (direction == mxConstants.DIRECTION_SOUTH)
4049							{
4050								pt.y += geo.height / 2;
4051							}
4052							else if (direction == mxConstants.DIRECTION_WEST)
4053							{
4054								pt.x -= geo.width / 2;
4055							}
4056							else
4057							{
4058								pt.x += geo.width / 2;
4059							}
4060						}
4061
4062						geo.x = pt.x - geo.width / 2;
4063						geo.y = pt.y - geo.height / 2;
4064					}
4065
4066					if (container != null)
4067					{
4068						this.addCells([realTarget], container, null, null, null, true);
4069						target = null;
4070					}
4071					else if (duplicate && !cloneSource)
4072					{
4073						this.addCells([realTarget], this.getDefaultParent(), null, null, null, true);
4074					}
4075				}
4076
4077				var edge = ((mxEvent.isControlDown(evt) && mxEvent.isShiftDown(evt) && duplicate) ||
4078					(target == null && cloneSource)) ? null : this.insertEdge(this.model.getParent(source),
4079						null, '', source, realTarget, this.createCurrentEdgeStyle());
4080
4081				// Inserts edge before source
4082				if (edge != null && this.connectionHandler.insertBeforeSource)
4083				{
4084					var index = null;
4085					var tmp = source;
4086
4087					while (tmp.parent != null && tmp.geometry != null &&
4088						tmp.geometry.relative && tmp.parent != edge.parent)
4089					{
4090						tmp = this.model.getParent(tmp);
4091					}
4092
4093					if (tmp != null && tmp.parent != null && tmp.parent == edge.parent)
4094					{
4095						var index = tmp.parent.getIndex(tmp);
4096						this.model.add(tmp.parent, edge, index);
4097					}
4098				}
4099
4100				// Special case: Click on west icon puts clone before cell
4101				if (target == null && realTarget != null && source.parent != null &&
4102					cloneSource && direction == mxConstants.DIRECTION_WEST)
4103				{
4104					var index = source.parent.getIndex(source);
4105					this.model.add(source.parent, realTarget, index);
4106				}
4107
4108				if (edge != null)
4109				{
4110					result.push(edge);
4111				}
4112
4113				if (target == null && realTarget != null)
4114				{
4115					result.push(realTarget);
4116				}
4117
4118				if (realTarget == null && edge != null)
4119				{
4120					edge.geometry.setTerminalPoint(pt, false);
4121				}
4122
4123				if (edge != null)
4124				{
4125					this.fireEvent(new mxEventObject('cellsInserted', 'cells', [edge]));
4126				}
4127			}
4128			finally
4129			{
4130				this.model.endUpdate();
4131			}
4132		}
4133
4134		if (done != null)
4135		{
4136			done(result);
4137		}
4138		else
4139		{
4140			return result;
4141		}
4142	});
4143
4144	if (createTarget != null && realTarget == null && duplicate &&
4145		(target != null || !cloneSource))
4146	{
4147		createTarget(dx + pt.x * s, dy + pt.y * s, execute);
4148	}
4149	else
4150	{
4151		return execute(realTarget);
4152	}
4153};
4154
4155/**
4156 * Returns all labels in the diagram as a string.
4157 */
4158Graph.prototype.getIndexableText = function()
4159{
4160	var tmp = document.createElement('div');
4161	var labels = [];
4162	var label = '';
4163
4164	for (var key in this.model.cells)
4165	{
4166		var cell = this.model.cells[key];
4167
4168		if (this.model.isVertex(cell) || this.model.isEdge(cell))
4169		{
4170			if (this.isHtmlLabel(cell))
4171			{
4172				tmp.innerHTML = this.sanitizeHtml(this.getLabel(cell));
4173				label = mxUtils.extractTextWithWhitespace([tmp]);
4174			}
4175			else
4176			{
4177				label = this.getLabel(cell);
4178			}
4179
4180			label = mxUtils.trim(label.replace(/[\x00-\x1F\x7F-\x9F]|\s+/g, ' '));
4181
4182			if (label.length > 0)
4183			{
4184				labels.push(label);
4185			}
4186		}
4187	}
4188
4189	return labels.join(' ');
4190};
4191
4192/**
4193 * Returns the label for the given cell.
4194 */
4195Graph.prototype.convertValueToString = function(cell)
4196{
4197	var value = this.model.getValue(cell);
4198
4199	if (value != null && typeof(value) == 'object')
4200	{
4201		var result = null;
4202
4203		if (this.isReplacePlaceholders(cell) && cell.getAttribute('placeholder') != null)
4204		{
4205			var name = cell.getAttribute('placeholder');
4206			var current = cell;
4207
4208			while (result == null && current != null)
4209			{
4210				if (current.value != null && typeof(current.value) == 'object')
4211				{
4212					result = (current.hasAttribute(name)) ? ((current.getAttribute(name) != null) ?
4213							current.getAttribute(name) : '') : null;
4214				}
4215
4216				current = this.model.getParent(current);
4217			}
4218		}
4219		else
4220		{
4221			var result = null;
4222
4223			if (Graph.translateDiagram && Graph.diagramLanguage != null)
4224			{
4225				result = value.getAttribute('label_' + Graph.diagramLanguage);
4226			}
4227
4228			if (result == null)
4229			{
4230				result = value.getAttribute('label') || '';
4231			}
4232		}
4233
4234		return result || '';
4235	}
4236
4237	return mxGraph.prototype.convertValueToString.apply(this, arguments);
4238};
4239
4240/**
4241 * Returns the link for the given cell.
4242 */
4243Graph.prototype.getLinksForState = function(state)
4244{
4245	if (state != null && state.text != null && state.text.node != null)
4246	{
4247		return state.text.node.getElementsByTagName('a');
4248	}
4249
4250	return null;
4251};
4252
4253/**
4254 * Returns the link for the given cell.
4255 */
4256Graph.prototype.getLinkForCell = function(cell)
4257{
4258	if (cell.value != null && typeof(cell.value) == 'object')
4259	{
4260		var link = cell.value.getAttribute('link');
4261
4262		// Removes links with leading javascript: protocol
4263		// TODO: Check more possible attack vectors
4264		if (link != null && link.toLowerCase().substring(0, 11) === 'javascript:')
4265		{
4266			link = link.substring(11);
4267		}
4268
4269		return link;
4270	}
4271
4272	return null;
4273};
4274
4275/**
4276 * Returns the link target for the given cell.
4277 */
4278Graph.prototype.getLinkTargetForCell = function(cell)
4279{
4280	if (cell.value != null && typeof(cell.value) == 'object')
4281	{
4282		return cell.value.getAttribute('linkTarget');
4283	}
4284
4285	return null;
4286};
4287
4288/**
4289 * Overrides label orientation for collapsed swimlanes inside stack and
4290 * for partial rectangles inside tables.
4291 */
4292Graph.prototype.getCellStyle = function(cell)
4293{
4294	var style = mxGraph.prototype.getCellStyle.apply(this, arguments);
4295
4296	if (cell != null && this.layoutManager != null)
4297	{
4298		var parent = this.model.getParent(cell);
4299
4300		if (this.model.isVertex(parent) && this.isCellCollapsed(cell))
4301		{
4302			var layout = this.layoutManager.getLayout(parent);
4303
4304			if (layout != null && layout.constructor == mxStackLayout)
4305			{
4306				style[mxConstants.STYLE_HORIZONTAL] = !layout.horizontal;
4307			}
4308		}
4309	}
4310
4311	return style;
4312};
4313
4314/**
4315 * Disables alternate width persistence for stack layout parents
4316 */
4317Graph.prototype.updateAlternateBounds = function(cell, geo, willCollapse)
4318{
4319	if (cell != null && geo != null && this.layoutManager != null && geo.alternateBounds != null)
4320	{
4321		var layout = this.layoutManager.getLayout(this.model.getParent(cell));
4322
4323		if (layout != null && layout.constructor == mxStackLayout)
4324		{
4325			if (layout.horizontal)
4326			{
4327				geo.alternateBounds.height = 0;
4328			}
4329			else
4330			{
4331				geo.alternateBounds.width = 0;
4332			}
4333		}
4334	}
4335
4336	mxGraph.prototype.updateAlternateBounds.apply(this, arguments);
4337};
4338
4339/**
4340 * Adds Shift+collapse/expand and size management for folding inside stack
4341 */
4342Graph.prototype.isMoveCellsEvent = function(evt, state)
4343{
4344	return mxEvent.isShiftDown(evt) || mxUtils.getValue(state.style, 'moveCells', '0') == '1';
4345};
4346
4347/**
4348 * Adds Shift+collapse/expand and size management for folding inside stack
4349 */
4350Graph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable, evt)
4351{
4352	recurse = (recurse != null) ? recurse : false;
4353
4354	if (cells == null)
4355	{
4356		cells = this.getFoldableCells(this.getSelectionCells(), collapse);
4357	}
4358
4359	if (cells != null)
4360	{
4361		this.model.beginUpdate();
4362
4363		try
4364		{
4365			mxGraph.prototype.foldCells.apply(this, arguments);
4366
4367			// Resizes all parent stacks if alt is not pressed
4368			if (this.layoutManager != null)
4369			{
4370				for (var i = 0; i < cells.length; i++)
4371				{
4372					var state = this.view.getState(cells[i]);
4373					var geo = this.getCellGeometry(cells[i]);
4374
4375					if (state != null && geo != null)
4376					{
4377						var dx = Math.round(geo.width - state.width / this.view.scale);
4378						var dy = Math.round(geo.height - state.height / this.view.scale);
4379
4380						if (dy != 0 || dx != 0)
4381						{
4382							var parent = this.model.getParent(cells[i]);
4383							var layout = this.layoutManager.getLayout(parent);
4384
4385							if (layout == null)
4386							{
4387								// Moves cells to the right and down after collapse/expand
4388								if (evt != null && this.isMoveCellsEvent(evt, state))
4389								{
4390									this.moveSiblings(state, parent, dx, dy);
4391								}
4392							}
4393							else if ((evt == null || !mxEvent.isAltDown(evt)) &&
4394								layout.constructor == mxStackLayout && !layout.resizeLast)
4395							{
4396								this.resizeParentStacks(parent, layout, dx, dy);
4397							}
4398						}
4399					}
4400				}
4401			}
4402		}
4403		finally
4404		{
4405			this.model.endUpdate();
4406		}
4407
4408		// Selects cells after folding
4409		if (this.isEnabled())
4410		{
4411			this.setSelectionCells(cells);
4412		}
4413	}
4414};
4415
4416/**
4417 * Overrides label orientation for collapsed swimlanes inside stack.
4418 */
4419Graph.prototype.moveSiblings = function(state, parent, dx, dy)
4420{
4421	this.model.beginUpdate();
4422	try
4423	{
4424		var cells = this.getCellsBeyond(state.x, state.y, parent, true, true);
4425
4426		for (var i = 0; i < cells.length; i++)
4427		{
4428			if (cells[i] != state.cell)
4429			{
4430				var tmp = this.view.getState(cells[i]);
4431				var geo = this.getCellGeometry(cells[i]);
4432
4433				if (tmp != null && geo != null)
4434				{
4435					geo = geo.clone();
4436					geo.translate(Math.round(dx * Math.max(0, Math.min(1, (tmp.x - state.x) / state.width))),
4437						Math.round(dy * Math.max(0, Math.min(1, (tmp.y - state.y) / state.height))));
4438					this.model.setGeometry(cells[i], geo);
4439				}
4440			}
4441		}
4442	}
4443	finally
4444	{
4445		this.model.endUpdate();
4446	}
4447};
4448
4449/**
4450 * Overrides label orientation for collapsed swimlanes inside stack.
4451 */
4452Graph.prototype.resizeParentStacks = function(parent, layout, dx, dy)
4453{
4454	if (this.layoutManager != null && layout != null && layout.constructor == mxStackLayout && !layout.resizeLast)
4455	{
4456		this.model.beginUpdate();
4457		try
4458		{
4459			var dir = layout.horizontal;
4460
4461			// Bubble resize up for all parent stack layouts with same orientation
4462			while (parent != null && layout != null && layout.constructor == mxStackLayout &&
4463				layout.horizontal == dir && !layout.resizeLast)
4464			{
4465				var pgeo = this.getCellGeometry(parent);
4466				var pstate = this.view.getState(parent);
4467
4468				if (pstate != null && pgeo != null)
4469				{
4470					pgeo = pgeo.clone();
4471
4472					if (layout.horizontal)
4473					{
4474						pgeo.width += dx + Math.min(0, pstate.width / this.view.scale - pgeo.width);
4475					}
4476					else
4477					{
4478						pgeo.height += dy + Math.min(0, pstate.height / this.view.scale - pgeo.height);
4479					}
4480
4481					this.model.setGeometry(parent, pgeo);
4482				}
4483
4484				parent = this.model.getParent(parent);
4485				layout = this.layoutManager.getLayout(parent);
4486			}
4487		}
4488		finally
4489		{
4490			this.model.endUpdate();
4491		}
4492	}
4493};
4494
4495/**
4496 * Disables drill-down for non-swimlanes.
4497 */
4498Graph.prototype.isContainer = function(cell)
4499{
4500	var style = this.getCurrentCellStyle(cell);
4501
4502	if (this.isSwimlane(cell))
4503	{
4504		return style['container'] != '0';
4505	}
4506	else
4507	{
4508		return style['container'] == '1';
4509	}
4510};
4511
4512/**
4513 * Adds a connectable style.
4514 */
4515Graph.prototype.isCellConnectable = function(cell)
4516{
4517	var style = this.getCurrentCellStyle(cell);
4518
4519	return (style['connectable'] != null) ? style['connectable'] != '0' :
4520		mxGraph.prototype.isCellConnectable.apply(this, arguments);
4521};
4522
4523/**
4524 * Adds labelMovable style.
4525 */
4526Graph.prototype.isLabelMovable = function(cell)
4527{
4528	var style = this.getCurrentCellStyle(cell);
4529
4530	return (style['movableLabel'] != null) ? style['movableLabel'] != '0' :
4531		mxGraph.prototype.isLabelMovable.apply(this, arguments);
4532};
4533
4534/**
4535 * Function: selectAll
4536 *
4537 * Selects all children of the given parent cell or the children of the
4538 * default parent if no parent is specified. To select leaf vertices and/or
4539 * edges use <selectCells>.
4540 *
4541 * Parameters:
4542 *
4543 * parent - Optional <mxCell> whose children should be selected.
4544 * Default is <defaultParent>.
4545 */
4546Graph.prototype.selectAll = function(parent)
4547{
4548	parent = parent || this.getDefaultParent();
4549
4550	if (!this.isCellLocked(parent))
4551	{
4552		mxGraph.prototype.selectAll.apply(this, arguments);
4553	}
4554};
4555
4556/**
4557 * Function: selectCells
4558 *
4559 * Selects all vertices and/or edges depending on the given boolean
4560 * arguments recursively, starting at the given parent or the default
4561 * parent if no parent is specified. Use <selectAll> to select all cells.
4562 * For vertices, only cells with no children are selected.
4563 *
4564 * Parameters:
4565 *
4566 * vertices - Boolean indicating if vertices should be selected.
4567 * edges - Boolean indicating if edges should be selected.
4568 * parent - Optional <mxCell> that acts as the root of the recursion.
4569 * Default is <defaultParent>.
4570 */
4571Graph.prototype.selectCells = function(vertices, edges, parent)
4572{
4573	parent = parent || this.getDefaultParent();
4574
4575	if (!this.isCellLocked(parent))
4576	{
4577		mxGraph.prototype.selectCells.apply(this, arguments);
4578	}
4579};
4580
4581/**
4582 * Function: getSwimlaneAt
4583 *
4584 * Returns the bottom-most swimlane that intersects the given point (x, y)
4585 * in the cell hierarchy that starts at the given parent.
4586 *
4587 * Parameters:
4588 *
4589 * x - X-coordinate of the location to be checked.
4590 * y - Y-coordinate of the location to be checked.
4591 * parent - <mxCell> that should be used as the root of the recursion.
4592 * Default is <defaultParent>.
4593 */
4594Graph.prototype.getSwimlaneAt = function (x, y, parent)
4595{
4596	var result = mxGraph.prototype.getSwimlaneAt.apply(this, arguments);
4597
4598	if (this.isCellLocked(result))
4599	{
4600		result = null;
4601	}
4602
4603	return result;
4604};
4605
4606/**
4607 * Disables folding for non-swimlanes.
4608 */
4609Graph.prototype.isCellFoldable = function(cell)
4610{
4611	var style = this.getCurrentCellStyle(cell);
4612
4613	return this.foldingEnabled && mxUtils.getValue(style,
4614		mxConstants.STYLE_RESIZABLE, '1') != '0' &&
4615		(style['treeFolding'] == '1' ||
4616		(!this.isCellLocked(cell) &&
4617		((this.isContainer(cell) && style['collapsible'] != '0') ||
4618		(!this.isContainer(cell) && style['collapsible'] == '1'))));
4619};
4620
4621/**
4622 * Stops all interactions and clears the selection.
4623 */
4624Graph.prototype.reset = function()
4625{
4626	if (this.isEditing())
4627	{
4628		this.stopEditing(true);
4629	}
4630
4631	this.escape();
4632
4633	if (!this.isSelectionEmpty())
4634	{
4635		this.clearSelection();
4636	}
4637};
4638
4639/**
4640 * Overridden to limit zoom to 1% - 16.000%.
4641 */
4642Graph.prototype.zoom = function(factor, center)
4643{
4644	factor = Math.max(0.01, Math.min(this.view.scale * factor, 160)) / this.view.scale;
4645
4646	mxGraph.prototype.zoom.apply(this, arguments);
4647};
4648
4649/**
4650 * Function: zoomIn
4651 *
4652 * Zooms into the graph by <zoomFactor>.
4653 */
4654Graph.prototype.zoomIn = function()
4655{
4656	// Switches to 1% zoom steps below 15%
4657	if (this.view.scale < 0.15)
4658	{
4659		this.zoom((this.view.scale + 0.01) / this.view.scale);
4660	}
4661	else
4662	{
4663		// Uses to 5% zoom steps for better grid rendering in webkit
4664		// and to avoid rounding errors for zoom steps
4665		this.zoom((Math.round(this.view.scale * this.zoomFactor * 20) / 20) / this.view.scale);
4666	}
4667};
4668
4669/**
4670 * Function: zoomOut
4671 *
4672 * Zooms out of the graph by <zoomFactor>.
4673 */
4674Graph.prototype.zoomOut = function()
4675{
4676	// Switches to 1% zoom steps below 15%
4677	if (this.view.scale <= 0.15)
4678	{
4679		this.zoom((this.view.scale - 0.01) / this.view.scale);
4680	}
4681	else
4682	{
4683		// Uses to 5% zoom steps for better grid rendering in webkit
4684		// and to avoid rounding errors for zoom steps
4685		this.zoom((Math.round(this.view.scale * (1 / this.zoomFactor) * 20) / 20) / this.view.scale);
4686	}
4687};
4688
4689/**
4690 * Function: fitWindow
4691 *
4692 * Sets the current visible rectangle of the window in graph coordinates.
4693 */
4694Graph.prototype.fitWindow = function(bounds, border)
4695{
4696	border = (border != null) ? border : 10;
4697
4698	var cw = this.container.clientWidth - border;
4699	var ch = this.container.clientHeight - border;
4700	var scale = Math.floor(20 * Math.min(cw / bounds.width, ch / bounds.height)) / 20;
4701	this.zoomTo(scale);
4702
4703	if (mxUtils.hasScrollbars(this.container))
4704	{
4705		var t = this.view.translate;
4706		this.container.scrollTop = (bounds.y + t.y) * scale -
4707			Math.max((ch - bounds.height * scale) / 2 + border / 2, 0);
4708		this.container.scrollLeft = (bounds.x + t.x) * scale -
4709			Math.max((cw - bounds.width * scale) / 2 + border / 2, 0);
4710	}
4711};
4712
4713/**
4714 * Overrides tooltips to show custom tooltip or metadata.
4715 */
4716Graph.prototype.getTooltipForCell = function(cell)
4717{
4718	var tip = '';
4719
4720	if (mxUtils.isNode(cell.value))
4721	{
4722		var tmp = null;
4723
4724		if (Graph.translateDiagram && Graph.diagramLanguage != null)
4725		{
4726			tmp = cell.value.getAttribute('tooltip_' + Graph.diagramLanguage);
4727		}
4728
4729		if (tmp == null)
4730		{
4731			tmp = cell.value.getAttribute('tooltip');
4732		}
4733
4734		if (tmp != null)
4735		{
4736			if (tmp != null && this.isReplacePlaceholders(cell))
4737			{
4738				tmp = this.replacePlaceholders(cell, tmp);
4739			}
4740
4741			tip = this.sanitizeHtml(tmp);
4742		}
4743		else
4744		{
4745			var ignored = this.builtInProperties;
4746			var attrs = cell.value.attributes;
4747			var temp = [];
4748
4749			// Hides links in edit mode
4750			if (this.isEnabled())
4751			{
4752				ignored.push('linkTarget');
4753				ignored.push('link');
4754			}
4755
4756			for (var i = 0; i < attrs.length; i++)
4757			{
4758				if (mxUtils.indexOf(ignored, attrs[i].nodeName) < 0 && attrs[i].nodeValue.length > 0)
4759				{
4760					temp.push({name: attrs[i].nodeName, value: attrs[i].nodeValue});
4761				}
4762			}
4763
4764			// Sorts by name
4765			temp.sort(function(a, b)
4766			{
4767				if (a.name < b.name)
4768				{
4769					return -1;
4770				}
4771				else if (a.name > b.name)
4772				{
4773					return 1;
4774				}
4775				else
4776				{
4777					return 0;
4778				}
4779			});
4780
4781			for (var i = 0; i < temp.length; i++)
4782			{
4783				if (temp[i].name != 'link' || !this.isCustomLink(temp[i].value))
4784				{
4785					tip += ((temp[i].name != 'link') ? '<b>' + temp[i].name + ':</b> ' : '') +
4786						mxUtils.htmlEntities(temp[i].value) + '\n';
4787				}
4788			}
4789
4790			if (tip.length > 0)
4791			{
4792				tip = tip.substring(0, tip.length - 1);
4793
4794				if (mxClient.IS_SVG)
4795				{
4796					tip = '<div style="max-width:360px;text-overflow:ellipsis;overflow:hidden;">' +
4797						tip + '</div>';
4798				}
4799			}
4800		}
4801	}
4802
4803	return tip;
4804};
4805
4806/**
4807 * Adds rack child layout style.
4808 */
4809Graph.prototype.getFlowAnimationStyle = function()
4810{
4811	var head = document.getElementsByTagName('head')[0];
4812
4813	if (head != null && this.flowAnimationStyle == null)
4814	{
4815		this.flowAnimationStyle = document.createElement('style')
4816		this.flowAnimationStyle.setAttribute('id',
4817			'geEditorFlowAnimation-' + Editor.guid());
4818		this.flowAnimationStyle.type = 'text/css';
4819		var id = this.flowAnimationStyle.getAttribute('id');
4820		this.flowAnimationStyle.innerHTML = this.getFlowAnimationStyleCss(id);
4821
4822		head.appendChild(this.flowAnimationStyle);
4823	}
4824
4825	return this.flowAnimationStyle;
4826};
4827
4828/**
4829 * Adds rack child layout style.
4830 */
4831Graph.prototype.getFlowAnimationStyleCss = function(id)
4832{
4833	return '.' + id + ' {\n' +
4834	  'animation: ' + id + ' 0.5s linear;\n' +
4835	  'animation-iteration-count: infinite;\n' +
4836	'}\n' +
4837	'@keyframes ' + id + ' {\n' +
4838	  'to {\n' +
4839	    'stroke-dashoffset: ' + (this.view.scale * -16) + ';\n' +
4840	  '}\n' +
4841	'}';
4842};
4843
4844/**
4845 * Turns the given string into an array.
4846 */
4847Graph.prototype.stringToBytes = function(str)
4848{
4849	return Graph.stringToBytes(str);
4850};
4851
4852/**
4853 * Turns the given array into a string.
4854 */
4855Graph.prototype.bytesToString = function(arr)
4856{
4857	return Graph.bytesToString(arr);
4858};
4859
4860/**
4861 * Returns a base64 encoded version of the compressed outer XML of the given node.
4862 */
4863Graph.prototype.compressNode = function(node)
4864{
4865	return Graph.compressNode(node);
4866};
4867
4868/**
4869 * Returns a base64 encoded version of the compressed string.
4870 */
4871Graph.prototype.compress = function(data, deflate)
4872{
4873	return Graph.compress(data, deflate);
4874};
4875
4876/**
4877 * Returns a decompressed version of the base64 encoded string.
4878 */
4879Graph.prototype.decompress = function(data, inflate)
4880{
4881	return Graph.decompress(data, inflate);
4882};
4883
4884/**
4885 * Redirects to Graph.zapGremlins.
4886 */
4887Graph.prototype.zapGremlins = function(text)
4888{
4889	return Graph.zapGremlins(text);
4890};
4891
4892/**
4893 * Hover icons are used for hover, vertex handler and drag from sidebar.
4894 */
4895HoverIcons = function(graph)
4896{
4897	mxEventSource.call(this);
4898	this.graph = graph;
4899	this.init();
4900};
4901
4902// Extends mxEventSource
4903mxUtils.extend(HoverIcons, mxEventSource);
4904
4905/**
4906 * Up arrow.
4907 */
4908HoverIcons.prototype.arrowSpacing = 2;
4909
4910/**
4911 * Delay to switch to another state for overlapping bbox. Default is 500ms.
4912 */
4913HoverIcons.prototype.updateDelay = 500;
4914
4915/**
4916 * Delay to switch between states. Default is 140ms.
4917 */
4918HoverIcons.prototype.activationDelay = 140;
4919
4920/**
4921 * Up arrow.
4922 */
4923HoverIcons.prototype.currentState = null;
4924
4925/**
4926 * Up arrow.
4927 */
4928HoverIcons.prototype.activeArrow = null;
4929
4930/**
4931 * Up arrow.
4932 */
4933HoverIcons.prototype.inactiveOpacity = 15;
4934
4935/**
4936 * Up arrow.
4937 */
4938HoverIcons.prototype.cssCursor = 'copy';
4939
4940/**
4941 * Whether to hide arrows that collide with vertices.
4942 * LATER: Add keyboard override, touch support.
4943 */
4944HoverIcons.prototype.checkCollisions = true;
4945
4946/**
4947 * Up arrow.
4948 */
4949HoverIcons.prototype.arrowFill = '#29b6f2';
4950
4951/**
4952 * Up arrow.
4953 */
4954HoverIcons.prototype.triangleUp = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-up.png', 26, 14) :
4955	Graph.createSvgImage(18, 28, '<path d="m 6 26 L 12 26 L 12 12 L 18 12 L 9 1 L 1 12 L 6 12 z" ' +
4956	'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>');
4957
4958/**
4959 * Right arrow.
4960 */
4961HoverIcons.prototype.triangleRight = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-right.png', 14, 26) :
4962	Graph.createSvgImage(26, 18, '<path d="m 1 6 L 14 6 L 14 1 L 26 9 L 14 18 L 14 12 L 1 12 z" ' +
4963	'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>');
4964
4965/**
4966 * Down arrow.
4967 */
4968HoverIcons.prototype.triangleDown = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-down.png', 26, 14) :
4969	Graph.createSvgImage(18, 26, '<path d="m 6 1 L 6 14 L 1 14 L 9 26 L 18 14 L 12 14 L 12 1 z" ' +
4970	'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>');
4971
4972/**
4973 * Left arrow.
4974 */
4975HoverIcons.prototype.triangleLeft = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-left.png', 14, 26) :
4976	Graph.createSvgImage(28, 18, '<path d="m 1 9 L 12 1 L 12 6 L 26 6 L 26 12 L 12 12 L 12 18 z" ' +
4977	'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>');
4978
4979/**
4980 * Round target.
4981 */
4982HoverIcons.prototype.roundDrop = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/round-drop.png', 26, 26) :
4983	Graph.createSvgImage(26, 26, '<circle cx="13" cy="13" r="12" ' +
4984	'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>');
4985
4986/**
4987 * Refresh target.
4988 */
4989HoverIcons.prototype.refreshTarget = new mxImage((mxClient.IS_SVG) ? 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjM2cHgiIGhlaWdodD0iMzZweCI+PGVsbGlwc2UgZmlsbD0iIzI5YjZmMiIgY3g9IjEyIiBjeT0iMTIiIHJ4PSIxMiIgcnk9IjEyIi8+PHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjgpIHRyYW5zbGF0ZSgyLjQsIDIuNCkiIHN0cm9rZT0iI2ZmZiIgZmlsbD0iI2ZmZiIgZD0iTTEyIDZ2M2w0LTQtNC00djNjLTQuNDIgMC04IDMuNTgtOCA4IDAgMS41Ny40NiAzLjAzIDEuMjQgNC4yNkw2LjcgMTQuOGMtLjQ1LS44My0uNy0xLjc5LS43LTIuOCAwLTMuMzEgMi42OS02IDYtNnptNi43NiAxLjc0TDE3LjMgOS4yYy40NC44NC43IDEuNzkuNyAyLjggMCAzLjMxLTIuNjkgNi02IDZ2LTNsLTQgNCA0IDR2LTNjNC40MiAwIDgtMy41OCA4LTggMC0xLjU3LS40Ni0zLjAzLTEuMjQtNC4yNnoiLz48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+Cg==' :
4990	IMAGE_PATH + '/refresh.png', 38, 38);
4991
4992/**
4993 * Tolerance for hover icon clicks.
4994 */
4995HoverIcons.prototype.tolerance = (mxClient.IS_TOUCH) ? 6 : 0;
4996
4997/**
4998 *
4999 */
5000HoverIcons.prototype.init = function()
5001{
5002	this.arrowUp = this.createArrow(this.triangleUp, mxResources.get('plusTooltip'), mxConstants.DIRECTION_NORTH);
5003	this.arrowRight = this.createArrow(this.triangleRight, mxResources.get('plusTooltip'), mxConstants.DIRECTION_EAST);
5004	this.arrowDown = this.createArrow(this.triangleDown, mxResources.get('plusTooltip'), mxConstants.DIRECTION_SOUTH);
5005	this.arrowLeft = this.createArrow(this.triangleLeft, mxResources.get('plusTooltip'), mxConstants.DIRECTION_WEST);
5006
5007	this.elts = [this.arrowUp, this.arrowRight, this.arrowDown, this.arrowLeft];
5008
5009	this.resetHandler = mxUtils.bind(this, function()
5010	{
5011		this.reset();
5012	});
5013
5014	this.repaintHandler = mxUtils.bind(this, function()
5015	{
5016		this.repaint();
5017	});
5018
5019	this.graph.selectionModel.addListener(mxEvent.CHANGE, this.resetHandler);
5020	this.graph.model.addListener(mxEvent.CHANGE, this.repaintHandler);
5021	this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler);
5022	this.graph.view.addListener(mxEvent.TRANSLATE, this.repaintHandler);
5023	this.graph.view.addListener(mxEvent.SCALE, this.repaintHandler);
5024	this.graph.view.addListener(mxEvent.DOWN, this.repaintHandler);
5025	this.graph.view.addListener(mxEvent.UP, this.repaintHandler);
5026	this.graph.addListener(mxEvent.ROOT, this.repaintHandler);
5027	this.graph.addListener(mxEvent.ESCAPE, this.resetHandler);
5028	mxEvent.addListener(this.graph.container, 'scroll', this.resetHandler);
5029
5030	// Resets the mouse point on escape
5031	this.graph.addListener(mxEvent.ESCAPE, mxUtils.bind(this, function()
5032	{
5033		this.mouseDownPoint = null;
5034	}));
5035
5036	// Removes hover icons if mouse leaves the container
5037	mxEvent.addListener(this.graph.container, 'mouseleave',  mxUtils.bind(this, function(evt)
5038	{
5039		// Workaround for IE11 firing mouseleave for touch in diagram
5040		if (evt.relatedTarget != null && mxEvent.getSource(evt) == this.graph.container)
5041		{
5042			this.setDisplay('none');
5043		}
5044	}));
5045
5046	// Resets current state when in-place editor starts
5047	this.graph.addListener(mxEvent.START_EDITING, mxUtils.bind(this, function(evt)
5048	{
5049		this.reset();
5050	}));
5051
5052	// Resets current state after update of selection state for touch events
5053	var graphClick = this.graph.click;
5054	this.graph.click = mxUtils.bind(this, function(me)
5055	{
5056		graphClick.apply(this.graph, arguments);
5057
5058		if (this.currentState != null && !this.graph.isCellSelected(this.currentState.cell) &&
5059			mxEvent.isTouchEvent(me.getEvent()) && !this.graph.model.isVertex(me.getCell()))
5060		{
5061			this.reset();
5062		}
5063	});
5064
5065	// Checks if connection handler was active in mouse move
5066	// as workaround for possible double connection inserted
5067	var connectionHandlerActive = false;
5068
5069	// Implements a listener for hover and click handling
5070	this.graph.addMouseListener(
5071	{
5072	    mouseDown: mxUtils.bind(this, function(sender, me)
5073	    {
5074	    	connectionHandlerActive = false;
5075	    	var evt = me.getEvent();
5076
5077	    	if (this.isResetEvent(evt))
5078	    	{
5079	    		this.reset();
5080	    	}
5081	    	else if (!this.isActive())
5082	    	{
5083	    		var state = this.getState(me.getState());
5084
5085	    		if (state != null || !mxEvent.isTouchEvent(evt))
5086	    		{
5087	    			this.update(state);
5088	    		}
5089	    	}
5090
5091	    	this.setDisplay('none');
5092	    }),
5093	    mouseMove: mxUtils.bind(this, function(sender, me)
5094	    {
5095	    	var evt = me.getEvent();
5096
5097	    	if (this.isResetEvent(evt))
5098	    	{
5099	    		this.reset();
5100	    	}
5101	    	else if (!this.graph.isMouseDown && !mxEvent.isTouchEvent(evt))
5102	    	{
5103	    		this.update(this.getState(me.getState()),
5104	    			me.getGraphX(), me.getGraphY());
5105	    	}
5106
5107	    	if (this.graph.connectionHandler != null &&
5108	    		this.graph.connectionHandler.shape != null)
5109	    	{
5110	    		connectionHandlerActive = true;
5111	    	}
5112	    }),
5113	    mouseUp: mxUtils.bind(this, function(sender, me)
5114	    {
5115	    	var evt = me.getEvent();
5116	    	var pt = mxUtils.convertPoint(this.graph.container,
5117				mxEvent.getClientX(evt), mxEvent.getClientY(evt))
5118
5119	    	if (this.isResetEvent(evt))
5120	    	{
5121	    		this.reset();
5122	    	}
5123	    	else if (this.isActive() && !connectionHandlerActive &&
5124	    		this.mouseDownPoint != null)
5125	    	{
5126    			this.click(this.currentState, this.getDirection(), me);
5127	    	}
5128	    	else if (this.isActive())
5129	    	{
5130	    		// Selects target vertex after drag and clone if not only new edge was inserted
5131	    		if (this.graph.getSelectionCount() != 1 || !this.graph.model.isEdge(
5132	    			this.graph.getSelectionCell()))
5133	    		{
5134	    			this.update(this.getState(this.graph.view.getState(
5135	    				this.graph.getCellAt(me.getGraphX(), me.getGraphY()))));
5136	    		}
5137	    		else
5138	    		{
5139	    			this.reset();
5140	    		}
5141	    	}
5142	    	else if (mxEvent.isTouchEvent(evt) || (this.bbox != null &&
5143	    		mxUtils.contains(this.bbox, me.getGraphX(), me.getGraphY())))
5144	    	{
5145	    		// Shows existing hover icons if inside bounding box
5146	    		this.setDisplay('');
5147	    		this.repaint();
5148	    	}
5149	    	else if (!mxEvent.isTouchEvent(evt))
5150	    	{
5151	    		this.reset();
5152	    	}
5153
5154	    	connectionHandlerActive = false;
5155	    	this.resetActiveArrow();
5156	    })
5157	});
5158};
5159
5160/**
5161 *
5162 */
5163HoverIcons.prototype.isResetEvent = function(evt, allowShift)
5164{
5165	return mxEvent.isAltDown(evt) || (this.activeArrow == null && mxEvent.isShiftDown(evt)) ||
5166		(mxEvent.isPopupTrigger(evt) && !this.graph.isCloneEvent(evt));
5167};
5168
5169/**
5170 *
5171 */
5172HoverIcons.prototype.createArrow = function(img, tooltip, direction)
5173{
5174	var arrow = null;
5175	arrow = mxUtils.createImage(img.src);
5176	arrow.style.width = img.width + 'px';
5177	arrow.style.height = img.height + 'px';
5178	arrow.style.padding = this.tolerance + 'px';
5179
5180	if (tooltip != null)
5181	{
5182		arrow.setAttribute('title', tooltip);
5183	}
5184
5185	arrow.style.position = 'absolute';
5186	arrow.style.cursor = this.cssCursor;
5187
5188	mxEvent.addGestureListeners(arrow, mxUtils.bind(this, function(evt)
5189	{
5190		if (this.currentState != null && !this.isResetEvent(evt))
5191		{
5192			this.mouseDownPoint = mxUtils.convertPoint(this.graph.container,
5193					mxEvent.getClientX(evt), mxEvent.getClientY(evt));
5194			this.drag(evt, this.mouseDownPoint.x, this.mouseDownPoint.y);
5195			this.activeArrow = arrow;
5196			this.setDisplay('none');
5197			mxEvent.consume(evt);
5198		}
5199	}));
5200
5201	// Captures mouse events as events on graph
5202	mxEvent.redirectMouseEvents(arrow, this.graph, this.currentState);
5203
5204	mxEvent.addListener(arrow, 'mouseenter', mxUtils.bind(this, function(evt)
5205	{
5206		// Workaround for Firefox firing mouseenter on touchend
5207		if (mxEvent.isMouseEvent(evt))
5208		{
5209	    	if (this.activeArrow != null && this.activeArrow != arrow)
5210	    	{
5211	    		mxUtils.setOpacity(this.activeArrow, this.inactiveOpacity);
5212	    	}
5213
5214			this.graph.connectionHandler.constraintHandler.reset();
5215			mxUtils.setOpacity(arrow, 100);
5216			this.activeArrow = arrow;
5217
5218			this.fireEvent(new mxEventObject('focus', 'arrow', arrow,
5219				'direction', direction, 'event', evt));
5220		}
5221	}));
5222
5223	mxEvent.addListener(arrow, 'mouseleave', mxUtils.bind(this, function(evt)
5224	{
5225		if (mxEvent.isMouseEvent(evt))
5226		{
5227			this.fireEvent(new mxEventObject('blur', 'arrow', arrow,
5228				'direction', direction, 'event', evt));
5229		}
5230
5231		// Workaround for IE11 firing this event on touch
5232		if (!this.graph.isMouseDown)
5233		{
5234			this.resetActiveArrow();
5235		}
5236	}));
5237
5238	return arrow;
5239};
5240
5241/**
5242 *
5243 */
5244HoverIcons.prototype.resetActiveArrow = function()
5245{
5246	if (this.activeArrow != null)
5247	{
5248		mxUtils.setOpacity(this.activeArrow, this.inactiveOpacity);
5249		this.activeArrow = null;
5250	}
5251};
5252
5253/**
5254 *
5255 */
5256HoverIcons.prototype.getDirection = function()
5257{
5258	var dir = mxConstants.DIRECTION_EAST;
5259
5260	if (this.activeArrow == this.arrowUp)
5261	{
5262		dir = mxConstants.DIRECTION_NORTH;
5263	}
5264	else if (this.activeArrow == this.arrowDown)
5265	{
5266		dir = mxConstants.DIRECTION_SOUTH;
5267	}
5268	else if (this.activeArrow == this.arrowLeft)
5269	{
5270		dir = mxConstants.DIRECTION_WEST;
5271	}
5272
5273	return dir;
5274};
5275
5276/**
5277 *
5278 */
5279HoverIcons.prototype.visitNodes = function(visitor)
5280{
5281	for (var i = 0; i < this.elts.length; i++)
5282	{
5283		if (this.elts[i] != null)
5284		{
5285			visitor(this.elts[i]);
5286		}
5287	}
5288};
5289
5290/**
5291 *
5292 */
5293HoverIcons.prototype.removeNodes = function()
5294{
5295	this.visitNodes(function(elt)
5296	{
5297		if (elt.parentNode != null)
5298		{
5299			elt.parentNode.removeChild(elt);
5300		}
5301	});
5302};
5303
5304/**
5305 *
5306 */
5307HoverIcons.prototype.setDisplay = function(display)
5308{
5309	this.visitNodes(function(elt)
5310	{
5311		elt.style.display = display;
5312	});
5313};
5314
5315/**
5316 *
5317 */
5318HoverIcons.prototype.isActive = function()
5319{
5320	return this.activeArrow != null && this.currentState != null;
5321};
5322
5323/**
5324 *
5325 */
5326HoverIcons.prototype.drag = function(evt, x, y)
5327{
5328	this.graph.popupMenuHandler.hideMenu();
5329	this.graph.stopEditing(false);
5330
5331	// Checks if state was removed in call to stopEditing above
5332	if (this.currentState != null)
5333	{
5334		this.graph.connectionHandler.start(this.currentState, x, y);
5335		this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
5336		this.graph.isMouseDown = true;
5337
5338		// Hides handles for selection cell
5339		var handler = this.graph.selectionCellsHandler.getHandler(this.currentState.cell);
5340
5341		if (handler != null)
5342		{
5343			handler.setHandlesVisible(false);
5344		}
5345
5346		// Ctrl+shift drag sets source constraint
5347		var es = this.graph.connectionHandler.edgeState;
5348
5349		if (evt != null && mxEvent.isShiftDown(evt) && mxEvent.isControlDown(evt) && es != null &&
5350			mxUtils.getValue(es.style, mxConstants.STYLE_EDGE, null) === 'orthogonalEdgeStyle')
5351		{
5352			var direction = this.getDirection();
5353			es.cell.style = mxUtils.setStyle(es.cell.style, 'sourcePortConstraint', direction);
5354			es.style['sourcePortConstraint'] = direction;
5355		}
5356	}
5357};
5358
5359/**
5360 *
5361 */
5362HoverIcons.prototype.getStateAt = function(state, x, y)
5363{
5364	return this.graph.view.getState(this.graph.getCellAt(x, y));
5365};
5366
5367/**
5368 *
5369 */
5370HoverIcons.prototype.click = function(state, dir, me)
5371{
5372	var evt = me.getEvent();
5373	var x = me.getGraphX();
5374	var y = me.getGraphY();
5375
5376	var tmp = this.getStateAt(state, x, y);
5377
5378	if (tmp != null && this.graph.model.isEdge(tmp.cell) && !this.graph.isCloneEvent(evt) &&
5379		(tmp.getVisibleTerminalState(true) == state || tmp.getVisibleTerminalState(false) == state))
5380	{
5381		this.graph.setSelectionCell(tmp.cell);
5382		this.reset();
5383	}
5384	else if (state != null)
5385	{
5386		this.execute(state, dir, me);
5387	}
5388
5389	me.consume();
5390};
5391
5392/**
5393 *
5394 */
5395HoverIcons.prototype.execute = function(state, dir, me)
5396{
5397	var evt = me.getEvent();
5398
5399	this.graph.selectCellsForConnectVertex(this.graph.connectVertex(
5400		state.cell, dir, this.graph.defaultEdgeLength, evt, this.graph.isCloneEvent(evt),
5401		this.graph.isCloneEvent(evt)), evt, this);
5402};
5403
5404/**
5405 *
5406 */
5407HoverIcons.prototype.reset = function(clearTimeout)
5408{
5409	clearTimeout = (clearTimeout == null) ? true : clearTimeout;
5410
5411	if (clearTimeout && this.updateThread != null)
5412	{
5413		window.clearTimeout(this.updateThread);
5414	}
5415
5416	this.mouseDownPoint = null;
5417	this.currentState = null;
5418	this.activeArrow = null;
5419	this.removeNodes();
5420	this.bbox = null;
5421
5422	this.fireEvent(new mxEventObject('reset'));
5423};
5424
5425/**
5426 *
5427 */
5428HoverIcons.prototype.repaint = function()
5429{
5430	this.bbox = null;
5431
5432	if (this.currentState != null)
5433	{
5434		// Checks if cell was deleted
5435		this.currentState = this.getState(this.currentState);
5436
5437		// Cell was deleted
5438		if (this.currentState != null &&
5439			this.graph.model.isVertex(this.currentState.cell) &&
5440			this.graph.isCellConnectable(this.currentState.cell))
5441		{
5442			var bds = mxRectangle.fromRectangle(this.currentState);
5443
5444			// Uses outer bounding box to take rotation into account
5445			if (this.currentState.shape != null && this.currentState.shape.boundingBox != null)
5446			{
5447				bds = mxRectangle.fromRectangle(this.currentState.shape.boundingBox);
5448			}
5449
5450			bds.grow(this.graph.tolerance);
5451			bds.grow(this.arrowSpacing);
5452
5453			var handler = this.graph.selectionCellsHandler.getHandler(this.currentState.cell);
5454
5455			if (this.graph.isTableRow(this.currentState.cell))
5456			{
5457				handler = this.graph.selectionCellsHandler.getHandler(
5458					this.graph.model.getParent(this.currentState.cell));
5459			}
5460
5461			var rotationBbox = null;
5462
5463			if (handler != null)
5464			{
5465				bds.x -= handler.horizontalOffset / 2;
5466				bds.y -= handler.verticalOffset / 2;
5467				bds.width += handler.horizontalOffset;
5468				bds.height += handler.verticalOffset;
5469
5470				// Adds bounding box of rotation handle to avoid overlap
5471				if (handler.rotationShape != null && handler.rotationShape.node != null &&
5472					handler.rotationShape.node.style.visibility != 'hidden' &&
5473					handler.rotationShape.node.style.display != 'none' &&
5474					handler.rotationShape.boundingBox != null)
5475				{
5476					rotationBbox = handler.rotationShape.boundingBox;
5477				}
5478			}
5479
5480			// Positions arrows avoid collisions with rotation handle
5481			var positionArrow = mxUtils.bind(this, function(arrow, x, y)
5482			{
5483				if (rotationBbox != null)
5484				{
5485					var bbox = new mxRectangle(x, y, arrow.clientWidth, arrow.clientHeight);
5486
5487					if (mxUtils.intersects(bbox, rotationBbox))
5488					{
5489						if (arrow == this.arrowUp)
5490						{
5491							y -= bbox.y + bbox.height - rotationBbox.y;
5492						}
5493						else if (arrow == this.arrowRight)
5494						{
5495							x += rotationBbox.x + rotationBbox.width - bbox.x;
5496						}
5497						else if (arrow == this.arrowDown)
5498						{
5499							y += rotationBbox.y + rotationBbox.height - bbox.y;
5500						}
5501						else if (arrow == this.arrowLeft)
5502						{
5503							x -= bbox.x + bbox.width - rotationBbox.x;
5504						}
5505					}
5506				}
5507
5508				arrow.style.left = x + 'px';
5509				arrow.style.top = y + 'px';
5510				mxUtils.setOpacity(arrow, this.inactiveOpacity);
5511			});
5512
5513			positionArrow(this.arrowUp,
5514				Math.round(this.currentState.getCenterX() - this.triangleUp.width / 2 - this.tolerance),
5515				Math.round(bds.y - this.triangleUp.height - this.tolerance));
5516
5517			positionArrow(this.arrowRight, Math.round(bds.x + bds.width - this.tolerance),
5518				Math.round(this.currentState.getCenterY() - this.triangleRight.height / 2 - this.tolerance));
5519
5520			positionArrow(this.arrowDown, parseInt(this.arrowUp.style.left),
5521				Math.round(bds.y + bds.height - this.tolerance));
5522
5523			positionArrow(this.arrowLeft, Math.round(bds.x - this.triangleLeft.width - this.tolerance),
5524				parseInt(this.arrowRight.style.top));
5525
5526			if (this.checkCollisions)
5527			{
5528				var right = this.graph.getCellAt(bds.x + bds.width +
5529						this.triangleRight.width / 2, this.currentState.getCenterY());
5530				var left = this.graph.getCellAt(bds.x - this.triangleLeft.width / 2, this.currentState.getCenterY());
5531				var top = this.graph.getCellAt(this.currentState.getCenterX(), bds.y - this.triangleUp.height / 2);
5532				var bottom = this.graph.getCellAt(this.currentState.getCenterX(), bds.y + bds.height + this.triangleDown.height / 2);
5533
5534				// Shows hover icons large cell is behind all directions of current cell
5535				if (right != null && right == left && left == top && top == bottom)
5536				{
5537					right = null;
5538					left = null;
5539					top = null;
5540					bottom = null;
5541				}
5542
5543				var currentGeo = this.graph.getCellGeometry(this.currentState.cell);
5544
5545				var checkCollision = mxUtils.bind(this, function(cell, arrow)
5546				{
5547					var geo = this.graph.model.isVertex(cell) && this.graph.getCellGeometry(cell);
5548
5549					// Ignores collision if vertex is more than 3 times the size of this vertex
5550					if (cell != null && !this.graph.model.isAncestor(cell, this.currentState.cell) &&
5551						!this.graph.isSwimlane(cell) && (geo == null || currentGeo == null ||
5552						(geo.height < 3 * currentGeo.height && geo.width < 3 * currentGeo.width)))
5553					{
5554						arrow.style.visibility = 'hidden';
5555					}
5556					else
5557					{
5558						arrow.style.visibility = 'visible';
5559					}
5560				});
5561
5562				checkCollision(right, this.arrowRight);
5563				checkCollision(left, this.arrowLeft);
5564				checkCollision(top, this.arrowUp);
5565				checkCollision(bottom, this.arrowDown);
5566			}
5567			else
5568			{
5569				this.arrowLeft.style.visibility = 'visible';
5570				this.arrowRight.style.visibility = 'visible';
5571				this.arrowUp.style.visibility = 'visible';
5572				this.arrowDown.style.visibility = 'visible';
5573			}
5574
5575			if (this.graph.tooltipHandler.isEnabled())
5576			{
5577				this.arrowLeft.setAttribute('title', mxResources.get('plusTooltip'));
5578				this.arrowRight.setAttribute('title', mxResources.get('plusTooltip'));
5579				this.arrowUp.setAttribute('title', mxResources.get('plusTooltip'));
5580				this.arrowDown.setAttribute('title', mxResources.get('plusTooltip'));
5581			}
5582			else
5583			{
5584				this.arrowLeft.removeAttribute('title');
5585				this.arrowRight.removeAttribute('title');
5586				this.arrowUp.removeAttribute('title');
5587				this.arrowDown.removeAttribute('title');
5588			}
5589		}
5590		else
5591		{
5592			this.reset();
5593		}
5594
5595		// Updates bounding box
5596		if (this.currentState != null)
5597		{
5598			this.bbox = this.computeBoundingBox();
5599
5600			// Adds tolerance for hover
5601			if (this.bbox != null)
5602			{
5603				this.bbox.grow(10);
5604			}
5605		}
5606	}
5607};
5608
5609/**
5610 *
5611 */
5612HoverIcons.prototype.computeBoundingBox = function()
5613{
5614	var bbox = (!this.graph.model.isEdge(this.currentState.cell)) ? mxRectangle.fromRectangle(this.currentState) : null;
5615
5616	this.visitNodes(function(elt)
5617	{
5618		if (elt.parentNode != null)
5619		{
5620			var tmp = new mxRectangle(elt.offsetLeft, elt.offsetTop, elt.offsetWidth, elt.offsetHeight);
5621
5622			if (bbox == null)
5623			{
5624				bbox = tmp;
5625			}
5626			else
5627			{
5628				bbox.add(tmp);
5629			}
5630		}
5631	});
5632
5633	return bbox;
5634};
5635
5636/**
5637 *
5638 */
5639HoverIcons.prototype.getState = function(state)
5640{
5641	if (state != null)
5642	{
5643		var cell = state.cell;
5644
5645		if (!this.graph.getModel().contains(cell))
5646		{
5647			state = null;
5648		}
5649		else
5650		{
5651			// Uses connectable parent vertex if child is not connectable
5652			if (this.graph.getModel().isVertex(cell) && !this.graph.isCellConnectable(cell))
5653			{
5654				var parent = this.graph.getModel().getParent(cell);
5655
5656				if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
5657				{
5658					cell = parent;
5659				}
5660			}
5661
5662			// Ignores locked cells and edges
5663			if (this.graph.isCellLocked(cell) || this.graph.model.isEdge(cell))
5664			{
5665				cell = null;
5666			}
5667
5668			state = this.graph.view.getState(cell);
5669
5670			if (state != null && state.style == null)
5671			{
5672				state = null;
5673			}
5674		}
5675	}
5676
5677	return state;
5678};
5679
5680/**
5681 *
5682 */
5683HoverIcons.prototype.update = function(state, x, y)
5684{
5685	if (!this.graph.connectionArrowsEnabled || (state != null &&
5686		mxUtils.getValue(state.style, 'allowArrows', '1') == '0'))
5687	{
5688		this.reset();
5689	}
5690	else
5691	{
5692		if (state != null && state.cell.geometry != null && state.cell.geometry.relative &&
5693			this.graph.model.isEdge(state.cell.parent))
5694		{
5695			state = null;
5696		}
5697
5698		var timeOnTarget = null;
5699
5700		// Time on target
5701		if (this.prev != state || this.isActive())
5702		{
5703			this.startTime = new Date().getTime();
5704			this.prev = state;
5705			timeOnTarget = 0;
5706
5707			if (this.updateThread != null)
5708			{
5709				window.clearTimeout(this.updateThread);
5710			}
5711
5712			if (state != null)
5713			{
5714				// Starts timer to update current state with no mouse events
5715				this.updateThread = window.setTimeout(mxUtils.bind(this, function()
5716				{
5717					if (!this.isActive() && !this.graph.isMouseDown &&
5718						!this.graph.panningHandler.isActive())
5719					{
5720						this.prev = state;
5721						this.update(state, x, y);
5722					}
5723				}), this.updateDelay + 10);
5724			}
5725		}
5726		else if (this.startTime != null)
5727		{
5728			timeOnTarget = new Date().getTime() - this.startTime;
5729		}
5730
5731		this.setDisplay('');
5732
5733		if (this.currentState != null && this.currentState != state && timeOnTarget < this.activationDelay &&
5734			this.bbox != null && !mxUtils.contains(this.bbox, x, y))
5735		{
5736			this.reset(false);
5737		}
5738		else if (this.currentState != null || timeOnTarget > this.activationDelay)
5739		{
5740			if (this.currentState != state && ((timeOnTarget > this.updateDelay && state != null) ||
5741				this.bbox == null || x == null || y == null || !mxUtils.contains(this.bbox, x, y)))
5742			{
5743				if (state != null && this.graph.isEnabled())
5744				{
5745					this.removeNodes();
5746					this.setCurrentState(state);
5747					this.repaint();
5748
5749					// Resets connection points on other focused cells
5750					if (this.graph.connectionHandler.constraintHandler.currentFocus != state)
5751					{
5752						this.graph.connectionHandler.constraintHandler.reset();
5753					}
5754				}
5755				else
5756				{
5757					this.reset();
5758				}
5759			}
5760		}
5761	}
5762};
5763
5764/**
5765 *
5766 */
5767HoverIcons.prototype.setCurrentState = function(state)
5768{
5769	if (state.style['portConstraint'] != 'eastwest')
5770	{
5771		this.graph.container.appendChild(this.arrowUp);
5772		this.graph.container.appendChild(this.arrowDown);
5773	}
5774
5775	this.graph.container.appendChild(this.arrowRight);
5776	this.graph.container.appendChild(this.arrowLeft);
5777	this.currentState = state;
5778};
5779
5780/**
5781 * Returns true if the given cell is a table.
5782 */
5783Graph.prototype.createParent = function(parent, child, childCount, dx, dy)
5784{
5785	parent = this.cloneCell(parent);
5786
5787	for (var i = 0; i < childCount; i++)
5788    {
5789		var clone = this.cloneCell(child);
5790		var geo = this.getCellGeometry(clone)
5791
5792		if (geo != null)
5793		{
5794			geo.x += i * dx;
5795			geo.y += i * dy;
5796		}
5797
5798		parent.insert(clone);
5799    }
5800
5801	return parent;
5802};
5803
5804/**
5805 * Returns true if the given cell is a table.
5806 */
5807Graph.prototype.createTable = function(rowCount, colCount, w, h, title, startSize, tableStyle, rowStyle, cellStyle)
5808{
5809	w = (w != null) ? w : 60;
5810	h = (h != null) ? h : 40;
5811	startSize = (startSize != null) ? startSize : 30;
5812	tableStyle = (tableStyle != null) ? tableStyle : 'shape=table;startSize=' +
5813		((title != null) ? startSize : '0') + ';container=1;collapsible=0;childLayout=tableLayout;';
5814	rowStyle = (rowStyle != null) ? rowStyle : 'shape=partialRectangle;collapsible=0;dropTarget=0;' +
5815    	'pointerEvents=0;fillColor=none;top=0;left=0;bottom=0;right=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;';
5816	cellStyle = (cellStyle != null) ? cellStyle : 'shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;' +
5817		'overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;';
5818
5819	return this.createParent(this.createVertex(null, null, (title != null) ? title : '',
5820		0, 0, colCount * w, rowCount * h + ((title != null) ? startSize : 0), tableStyle),
5821		this.createParent(this.createVertex(null, null, '', 0, 0, colCount * w, h, rowStyle),
5822			this.createVertex(null, null, '', 0, 0, w, h, cellStyle),
5823				colCount, w, 0), rowCount, 0, h);
5824};
5825
5826/**
5827 * Sets the values for the cells and rows in the given table and returns the table.
5828 */
5829Graph.prototype.setTableValues = function(table, values, rowValues)
5830{
5831	var rows = this.model.getChildCells(table, true);
5832
5833	for (var i = 0; i < rows.length; i++)
5834	{
5835		if (rowValues != null)
5836		{
5837			rows[i].value = rowValues[i];
5838		}
5839
5840		if (values != null)
5841		{
5842			var cells = this.model.getChildCells(rows[i], true);
5843
5844			for (var j = 0; j < cells.length; j++)
5845			{
5846				if (values[i][j] != null)
5847				{
5848					cells[j].value = values[i][j];
5849				}
5850			}
5851		}
5852	}
5853
5854	return table;
5855};
5856
5857/**
5858 *
5859 */
5860Graph.prototype.createCrossFunctionalSwimlane = function(rowCount, colCount, w, h, title, tableStyle, rowStyle, firstCellStyle, cellStyle)
5861{
5862	w = (w != null) ? w : 120;
5863	h = (h != null) ? h : 120;
5864	var startSize = (title == null) ? 0 : 40;
5865
5866	var s = 'collapsible=0;recursiveResize=0;expand=0;pointerEvents=0;';
5867	tableStyle = (tableStyle != null) ? tableStyle : 'shape=table;childLayout=tableLayout;' +
5868		'rowLines=0;columnLines=0;startSize=' + startSize + ';' +
5869		((title == null) ? 'fillColor=none;' : '') + s;
5870
5871	rowStyle = (rowStyle != null) ? rowStyle : 'swimlane;horizontal=0;points=[[0,0.5],[1,0.5]];' +
5872		'portConstraint=eastwest;startSize=' + startSize + ';' + s;
5873	firstCellStyle = (firstCellStyle != null) ? firstCellStyle : 'swimlane;connectable=0;startSize=40;' + s;
5874	cellStyle = (cellStyle != null) ? cellStyle : 'swimlane;connectable=0;startSize=' +
5875		((title == null) ? '40' : '0') + ';' + s;
5876
5877	var table = this.createVertex(null, null, (title != null) ? title : '', 0, 0,
5878		colCount * w, rowCount * h, tableStyle);
5879	var t = mxUtils.getValue(this.getCellStyle(table), mxConstants.STYLE_STARTSIZE,
5880		mxConstants.DEFAULT_STARTSIZE);
5881	table.geometry.width += t;
5882	table.geometry.height += t;
5883
5884	var row = this.createVertex(null, null, '', 0, t, colCount * w + t, h, rowStyle);
5885	table.insert(this.createParent(row, this.createVertex(null, null,
5886		'', t, 0, w, h, firstCellStyle), colCount, w, 0));
5887
5888	if (rowCount > 1)
5889	{
5890		row.geometry.y = h + t;
5891
5892		return this.createParent(table, this.createParent(row,
5893			this.createVertex(null, null,  '', t, 0, w, h, cellStyle),
5894			colCount, w, 0), rowCount - 1, 0, h);
5895	}
5896	else
5897	{
5898		return table;
5899	}
5900};
5901
5902/**
5903 * Returns true if the given cell is a table cell.
5904 */
5905Graph.prototype.isTableCell = function(cell)
5906{
5907	return this.model.isVertex(cell) && this.isTableRow(this.model.getParent(cell));
5908};
5909
5910/**
5911 * Returns true if the given cell is a table row.
5912 */
5913Graph.prototype.isTableRow = function(cell)
5914{
5915	return this.model.isVertex(cell) && this.isTable(this.model.getParent(cell));
5916};
5917
5918/**
5919 * Returns true if the given cell is a table.
5920 */
5921Graph.prototype.isTable = function(cell)
5922{
5923	var style = this.getCellStyle(cell);
5924
5925	return style != null && style['childLayout'] == 'tableLayout';
5926};
5927
5928/**
5929 * Returns true if the given cell is a table.
5930 */
5931Graph.prototype.isStack = function(cell)
5932{
5933	var style = this.getCellStyle(cell);
5934
5935	return style != null && style['childLayout'] == 'stackLayout';
5936};
5937
5938/**
5939 * Returns true if the given cell is a table row.
5940 */
5941Graph.prototype.isStackChild = function(cell)
5942{
5943	return this.model.isVertex(cell) && this.isStack(this.model.getParent(cell));
5944};
5945
5946/**
5947 * Updates the row and table heights.
5948 */
5949Graph.prototype.setTableRowHeight = function(row, dy, extend)
5950{
5951	extend = (extend != null) ? extend : true;
5952	var model = this.getModel();
5953
5954	model.beginUpdate();
5955	try
5956	{
5957		var rgeo = this.getCellGeometry(row);
5958
5959		// Sets height of row
5960		if (rgeo != null)
5961		{
5962			rgeo = rgeo.clone();
5963			rgeo.height += dy;
5964			model.setGeometry(row, rgeo);
5965
5966			var table = model.getParent(row);
5967			var rows = model.getChildCells(table, true);
5968
5969			// Shifts and resizes neighbor row
5970			if (!extend)
5971			{
5972				var index = mxUtils.indexOf(rows, row);
5973
5974				if (index < rows.length - 1)
5975				{
5976					var nextRow = rows[index + 1];
5977					var geo = this.getCellGeometry(nextRow);
5978
5979					if (geo != null)
5980					{
5981						geo = geo.clone();
5982						geo.y += dy;
5983						geo.height -= dy;
5984
5985						model.setGeometry(nextRow, geo);
5986					}
5987				}
5988			}
5989
5990			// Updates height of table
5991			var tgeo = this.getCellGeometry(table);
5992
5993			if (tgeo != null)
5994			{
5995				// Always extends for last row
5996				if (!extend)
5997				{
5998					extend = row == rows[rows.length - 1];
5999				}
6000
6001				if (extend)
6002				{
6003					tgeo = tgeo.clone();
6004					tgeo.height += dy;
6005					model.setGeometry(table, tgeo);
6006				}
6007			}
6008
6009			if (this.layoutManager != null)
6010			{
6011				this.layoutManager.executeLayout(table, true);
6012			}
6013		}
6014	}
6015	finally
6016	{
6017		model.endUpdate();
6018	}
6019};
6020
6021/**
6022 * Updates column width and row height.
6023 */
6024Graph.prototype.setTableColumnWidth = function(col, dx, extend)
6025{
6026	extend = (extend != null) ? extend : false;
6027
6028	var model = this.getModel();
6029	var row = model.getParent(col);
6030	var table = model.getParent(row);
6031	var cells = model.getChildCells(row, true);
6032	var index = mxUtils.indexOf(cells, col);
6033	var lastColumn = index == cells.length - 1;
6034
6035	model.beginUpdate();
6036	try
6037	{
6038		// Sets width of child cell
6039		var rows = model.getChildCells(table, true);
6040
6041		for (var i = 0; i < rows.length; i++)
6042		{
6043			row = rows[i];
6044			cells = model.getChildCells(row, true);
6045			var cell = cells[index];
6046			var geo = this.getCellGeometry(cell);
6047
6048			if (geo != null)
6049			{
6050				geo = geo.clone();
6051				geo.width += dx;
6052
6053				if (geo.alternateBounds != null)
6054				{
6055					geo.alternateBounds.width += dx;
6056				}
6057
6058				model.setGeometry(cell, geo);
6059			}
6060
6061			// Shifts and resizes neighbor column
6062			if (index < cells.length - 1)
6063			{
6064				cell = cells[index + 1];
6065				var geo = this.getCellGeometry(cell);
6066
6067				if (geo != null)
6068				{
6069					geo = geo.clone();
6070					geo.x += dx;
6071
6072					if (!extend)
6073					{
6074						geo.width -= dx;
6075
6076						if (geo.alternateBounds != null)
6077						{
6078							geo.alternateBounds.width -= dx;
6079						}
6080					}
6081
6082					model.setGeometry(cell, geo);
6083				}
6084			}
6085		}
6086
6087		if (lastColumn || extend)
6088		{
6089			// Updates width of table
6090			var tgeo = this.getCellGeometry(table);
6091
6092			if (tgeo != null)
6093			{
6094				tgeo = tgeo.clone();
6095				tgeo.width += dx;
6096				model.setGeometry(table, tgeo);
6097			}
6098		}
6099
6100		if (this.layoutManager != null)
6101		{
6102			this.layoutManager.executeLayout(table, true);
6103		}
6104	}
6105	finally
6106	{
6107		model.endUpdate();
6108	}
6109};
6110
6111/**
6112 * Special Layout for tables.
6113 */
6114function TableLayout(graph)
6115{
6116	mxGraphLayout.call(this, graph);
6117};
6118
6119/**
6120 * Extends mxGraphLayout.
6121 */
6122TableLayout.prototype = new mxStackLayout();
6123TableLayout.prototype.constructor = TableLayout;
6124
6125/**
6126 * Function: isHorizontal
6127 *
6128 * Overrides stack layout to handle row reorder.
6129 */
6130TableLayout.prototype.isHorizontal = function()
6131{
6132	return false;
6133};
6134
6135/**
6136 * Function: isVertexIgnored
6137 *
6138 * Overrides to allow for table rows and cells.
6139 */
6140TableLayout.prototype.isVertexIgnored = function(vertex)
6141{
6142	return !this.graph.getModel().isVertex(vertex) ||
6143		!this.graph.isCellVisible(vertex);
6144};
6145
6146/**
6147 * Function: getSize
6148 *
6149 * Returns the total vertical or horizontal size of the given cells.
6150 */
6151TableLayout.prototype.getSize = function(cells, horizontal)
6152{
6153	var total = 0;
6154
6155	for (var i = 0; i < cells.length; i++)
6156	{
6157		if (!this.isVertexIgnored(cells[i]))
6158		{
6159			var geo = this.graph.getCellGeometry(cells[i]);
6160
6161			if (geo != null)
6162			{
6163				total += (horizontal) ? geo.width : geo.height;
6164			}
6165		}
6166	}
6167
6168	return total;
6169};
6170
6171/**
6172 * Function: getRowLayout
6173 *
6174 * Returns the column positions for the given row and table width.
6175 */
6176TableLayout.prototype.getRowLayout = function(row, width)
6177{
6178	var cells = this.graph.model.getChildCells(row, true);
6179	var off = this.graph.getActualStartSize(row, true);
6180	var sw = this.getSize(cells, true);
6181	var rw = width - off.x - off.width;
6182	var result = [];
6183	var x = off.x;
6184
6185	for (var i = 0; i < cells.length; i++)
6186	{
6187		var cell = this.graph.getCellGeometry(cells[i]);
6188
6189		if (cell != null)
6190		{
6191			x += (cell.alternateBounds != null ?
6192				cell.alternateBounds.width :
6193				cell.width) * rw / sw;
6194			result.push(Math.round(x));
6195		}
6196	}
6197
6198	return result;
6199};
6200
6201/**
6202 * Function: layoutRow
6203 *
6204 * Places the cells at the given positions in the given row.
6205 */
6206TableLayout.prototype.layoutRow = function(row, positions, height, tw, lastCells)
6207{
6208	var model = this.graph.getModel();
6209	var cells = model.getChildCells(row, true);
6210	var off = this.graph.getActualStartSize(row, true);
6211	var last = null;
6212	var x = off.x;
6213	var sw = 0;
6214
6215	if (positions != null)
6216	{
6217		positions = positions.slice();
6218		positions.splice(0, 0, off.x);
6219	}
6220
6221	for (var i = 0; i < cells.length; i++)
6222	{
6223		var cell = this.graph.getCellGeometry(cells[i]);
6224
6225		if (cell != null)
6226		{
6227			cell = cell.clone();
6228
6229			cell.y = off.y;
6230			cell.height = height - off.y - off.height;
6231
6232			if (positions != null)
6233			{
6234				cell.x = positions[i];
6235				cell.width = positions[i + 1] - cell.x;
6236
6237				// Fills with last cell if not enough cells
6238				if (i == cells.length - 1 && i < positions.length - 2)
6239				{
6240					cell.width = tw - cell.x - off.x - off.width;
6241				}
6242			}
6243			else
6244			{
6245				cell.x = x;
6246				x += cell.width;
6247
6248				if (i == cells.length - 1)
6249				{
6250					cell.width = tw - off.x - off.width - sw;
6251				}
6252				else
6253				{
6254					sw += cell.width;
6255				}
6256			}
6257
6258			cell.alternateBounds = new mxRectangle(0, 0, cell.width, cell.height);
6259			model.setGeometry(cells[i], cell);
6260		}
6261
6262		var visible = true;
6263
6264		// Handles rowspan
6265		var upper = lastCells[i];
6266
6267		if (upper != null && upper.geo != null &&
6268			upper.rowspan != null && upper.rowspan > 1)
6269		{
6270			upper.geo.height += (cell.alternateBounds != null) ?
6271				cell.alternateBounds.height : cell.height;
6272			visible = false;
6273			upper.rowspan--;
6274		}
6275
6276		// Handles colspan
6277		if (last != null && last.geo != null &&
6278			last.colspan != null && last.colspan > 1)
6279		{
6280			last.geo.width += (cell.alternateBounds != null) ?
6281				cell.alternateBounds.width : cell.width;
6282			visible = false;
6283			last.colspan--;
6284		}
6285
6286		model.setVisible(cells[i], visible);
6287
6288		var style = this.graph.getCurrentCellStyle(cells[i], true);
6289		var temp = {style: style, cell: cells[i], geo: cell};
6290
6291		if (style != null)
6292		{
6293			if (last == null || last.colspan < 1)
6294			{
6295				temp.colspan = parseInt(style['colspan'] || 0);
6296				last = temp;
6297			}
6298
6299			if (upper == null || upper.rowspan < 1)
6300			{
6301				temp.rowspan = parseInt(style['rowspan'] || 0);
6302				lastCells[i] = temp;
6303			}
6304			else if (upper != null)
6305			{
6306				temp.colspan = parseInt(upper.style['colspan'] || 0);
6307			}
6308		}
6309	}
6310
6311	return sw;
6312};
6313
6314/**
6315 * Function: execute
6316 *
6317 * Implements <mxGraphLayout.execute>.
6318 */
6319TableLayout.prototype.execute = function(parent)
6320{
6321	if (parent != null)
6322	{
6323		var offset = this.graph.getActualStartSize(parent, true);
6324		var table = this.graph.getCellGeometry(parent);
6325		var style = this.graph.getCellStyle(parent);
6326		var resizeLastRow = mxUtils.getValue(style,
6327			'resizeLastRow', '0') == '1';
6328		var resizeLast = mxUtils.getValue(style,
6329			'resizeLast', '0') == '1';
6330		var fixedRows = mxUtils.getValue(style,
6331			'fixedRows', '0') == '1';
6332		var model = this.graph.getModel();
6333		var sw = 0;
6334
6335		model.beginUpdate();
6336		try
6337		{
6338			var th = table.height - offset.y - offset.height;
6339			var tw = table.width - offset.x - offset.width;
6340			var rows = model.getChildCells(parent, true);
6341			var sh = this.getSize(rows, false);
6342
6343			if (th > 0 && tw > 0 && rows.length > 0 && sh > 0)
6344			{
6345				if (resizeLastRow)
6346				{
6347					var row = this.graph.getCellGeometry(rows[rows.length - 1]);
6348
6349					if (row != null)
6350					{
6351						row = row.clone();
6352						row.height = th - sh + row.height;
6353						model.setGeometry(rows[rows.length - 1], row);
6354					}
6355				}
6356
6357				var pos = (resizeLast) ? null : this.getRowLayout(rows[0], tw);
6358				var lastCells = [];
6359				var y = offset.y;
6360
6361				// Updates row geometries
6362				for (var i = 0; i < rows.length; i++)
6363				{
6364					var row = this.graph.getCellGeometry(rows[i]);
6365
6366					if (row != null)
6367					{
6368						row = row.clone();
6369						row.x = offset.x;
6370						row.width = tw;
6371						row.y = Math.round(y);
6372
6373						if (resizeLastRow || fixedRows)
6374						{
6375							y += row.height;
6376						}
6377						else
6378						{
6379							y += (row.height / sh) * th;
6380						}
6381
6382						row.height = Math.round(y) - row.y;
6383						model.setGeometry(rows[i], row);
6384					}
6385
6386					// Updates cell geometries
6387					sw = Math.max(sw, this.layoutRow(rows[i], pos, row.height, tw, lastCells));
6388				}
6389
6390				if (fixedRows && th < sh)
6391				{
6392					table = table.clone();
6393					table.height = y + offset.height;
6394					model.setGeometry(parent, table);
6395				}
6396
6397				if (resizeLast && tw < sw + Graph.minTableColumnWidth)
6398				{
6399					table = table.clone();
6400					table.width = sw + offset.width + offset.x + Graph.minTableColumnWidth;
6401					model.setGeometry(parent, table);
6402				}
6403			}
6404		}
6405		finally
6406		{
6407			model.endUpdate();
6408		}
6409	}
6410};
6411
6412(function()
6413{
6414	/**
6415	 * Reset the list of processed edges.
6416	 */
6417	var mxGraphViewResetValidationState = mxGraphView.prototype.resetValidationState;
6418	mxGraphView.prototype.resetValidationState = function()
6419	{
6420		mxGraphViewResetValidationState.apply(this, arguments);
6421
6422		this.validEdges = [];
6423	};
6424
6425	/**
6426	 * Updates jumps for valid edges and repaints if needed.
6427	 */
6428	var mxGraphViewValidateCellState = mxGraphView.prototype.validateCellState;
6429	mxGraphView.prototype.validateCellState = function(cell, recurse)
6430	{
6431		recurse = (recurse != null) ? recurse : true;
6432		var state = this.getState(cell);
6433
6434		// Forces repaint if jumps change on a valid edge
6435		if (state != null && recurse && this.graph.model.isEdge(state.cell) &&
6436			state.style != null && state.style[mxConstants.STYLE_CURVED] != 1 &&
6437			!state.invalid && this.updateLineJumps(state))
6438		{
6439			this.graph.cellRenderer.redraw(state, false, this.isRendering());
6440		}
6441
6442		state = mxGraphViewValidateCellState.apply(this, arguments);
6443
6444		// Adds to the list of edges that may intersect with later edges
6445		if (state != null && recurse && this.graph.model.isEdge(state.cell) &&
6446			state.style != null && state.style[mxConstants.STYLE_CURVED] != 1)
6447		{
6448			// LATER: Reuse jumps for valid edges
6449			this.validEdges.push(state);
6450		}
6451
6452		return state;
6453	};
6454
6455	/**
6456	 * Overrides paint to add flowAnimation style.
6457	 */
6458	var mxShapePaint = mxShape.prototype.paint;
6459
6460	mxShape.prototype.paint = function()
6461	{
6462		mxShapePaint.apply(this, arguments);
6463
6464		if (this.state != null && this.node != null &&
6465			this.state.view.graph.enableFlowAnimation &&
6466			this.state.view.graph.model.isEdge(this.state.cell) &&
6467			mxUtils.getValue(this.state.style, 'flowAnimation', '0') == '1')
6468		{
6469			var paths = this.node.getElementsByTagName('path');
6470
6471			if (paths.length > 1)
6472			{
6473				if (mxUtils.getValue(this.state.style, mxConstants.STYLE_DASHED, '0') != '1')
6474				{
6475					paths[1].setAttribute('stroke-dasharray', (this.state.view.scale * 8));
6476				}
6477
6478				var anim = this.state.view.graph.getFlowAnimationStyle();
6479
6480				if (anim != null)
6481				{
6482					paths[1].setAttribute('class', anim.getAttribute('id'));
6483				}
6484			}
6485		}
6486	};
6487
6488	/**
6489	 * Forces repaint if routed points have changed.
6490	 */
6491	var mxCellRendererIsShapeInvalid = mxCellRenderer.prototype.isShapeInvalid;
6492	mxCellRenderer.prototype.isShapeInvalid = function(state, shape)
6493	{
6494		return mxCellRendererIsShapeInvalid.apply(this, arguments) ||
6495			(state.routedPoints != null && shape.routedPoints != null &&
6496			!mxUtils.equalPoints(shape.routedPoints, state.routedPoints))
6497	};
6498
6499	/**
6500	 * Updates jumps for invalid edges.
6501	 */
6502	var mxGraphViewUpdateCellState = mxGraphView.prototype.updateCellState;
6503	mxGraphView.prototype.updateCellState = function(state)
6504	{
6505		mxGraphViewUpdateCellState.apply(this, arguments);
6506
6507		// Updates jumps on invalid edge before repaint
6508		if (this.graph.model.isEdge(state.cell) &&
6509			state.style[mxConstants.STYLE_CURVED] != 1)
6510		{
6511			this.updateLineJumps(state);
6512		}
6513	};
6514
6515	/**
6516	 * Updates the jumps between given state and processed edges.
6517	 */
6518	mxGraphView.prototype.updateLineJumps = function(state)
6519	{
6520		var pts = state.absolutePoints;
6521
6522		if (Graph.lineJumpsEnabled)
6523		{
6524			var changed = state.routedPoints != null;
6525			var actual = null;
6526
6527			if (pts != null && this.validEdges != null &&
6528				mxUtils.getValue(state.style, 'jumpStyle', 'none') !== 'none')
6529			{
6530				var thresh = 0.5 * this.scale;
6531				changed = false;
6532				actual = [];
6533
6534				// Type 0 means normal waypoint, 1 means jump
6535				function addPoint(type, x, y)
6536				{
6537					var rpt = new mxPoint(x, y);
6538					rpt.type = type;
6539
6540					actual.push(rpt);
6541					var curr = (state.routedPoints != null) ? state.routedPoints[actual.length - 1] : null;
6542
6543					return curr == null || curr.type != type || curr.x != x || curr.y != y;
6544				};
6545
6546				for (var i = 0; i < pts.length - 1; i++)
6547				{
6548					var p1 = pts[i + 1];
6549					var p0 = pts[i];
6550					var list = [];
6551
6552					// Ignores waypoints on straight segments
6553					var pn = pts[i + 2];
6554
6555					while (i < pts.length - 2 &&
6556						mxUtils.ptSegDistSq(p0.x, p0.y, pn.x, pn.y,
6557						p1.x, p1.y) < 1 * this.scale * this.scale)
6558					{
6559						p1 = pn;
6560						i++;
6561						pn = pts[i + 2];
6562					}
6563
6564					changed = addPoint(0, p0.x, p0.y) || changed;
6565
6566					// Processes all previous edges
6567					for (var e = 0; e < this.validEdges.length; e++)
6568					{
6569						var state2 = this.validEdges[e];
6570						var pts2 = state2.absolutePoints;
6571
6572						if (pts2 != null && mxUtils.intersects(state, state2) && state2.style['noJump'] != '1')
6573						{
6574							// Compares each segment of the edge with the current segment
6575							for (var j = 0; j < pts2.length - 1; j++)
6576							{
6577								var p3 = pts2[j + 1];
6578								var p2 = pts2[j];
6579
6580								// Ignores waypoints on straight segments
6581								pn = pts2[j + 2];
6582
6583								while (j < pts2.length - 2 &&
6584									mxUtils.ptSegDistSq(p2.x, p2.y, pn.x, pn.y,
6585									p3.x, p3.y) < 1 * this.scale * this.scale)
6586								{
6587									p3 = pn;
6588									j++;
6589									pn = pts2[j + 2];
6590								}
6591
6592								var pt = mxUtils.intersection(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
6593
6594								// Handles intersection between two segments
6595								if (pt != null && (Math.abs(pt.x - p0.x) > thresh ||
6596									Math.abs(pt.y - p0.y) > thresh) &&
6597									(Math.abs(pt.x - p1.x) > thresh ||
6598									Math.abs(pt.y - p1.y) > thresh) &&
6599									(Math.abs(pt.x - p2.x) > thresh ||
6600									Math.abs(pt.y - p2.y) > thresh) &&
6601									(Math.abs(pt.x - p3.x) > thresh ||
6602									Math.abs(pt.y - p3.y) > thresh))
6603								{
6604									var dx = pt.x - p0.x;
6605									var dy = pt.y - p0.y;
6606									var temp = {distSq: dx * dx + dy * dy, x: pt.x, y: pt.y};
6607
6608									// Intersections must be ordered by distance from start of segment
6609									for (var t = 0; t < list.length; t++)
6610									{
6611										if (list[t].distSq > temp.distSq)
6612										{
6613											list.splice(t, 0, temp);
6614											temp = null;
6615
6616											break;
6617										}
6618									}
6619
6620									// Ignores multiple intersections at segment joint
6621									if (temp != null && (list.length == 0 ||
6622										list[list.length - 1].x !== temp.x ||
6623										list[list.length - 1].y !== temp.y))
6624									{
6625										list.push(temp);
6626									}
6627								}
6628							}
6629						}
6630					}
6631
6632					// Adds ordered intersections to routed points
6633					for (var j = 0; j < list.length; j++)
6634					{
6635						changed = addPoint(1, list[j].x, list[j].y) || changed;
6636					}
6637				}
6638
6639				var pt = pts[pts.length - 1];
6640				changed = addPoint(0, pt.x, pt.y) || changed;
6641			}
6642
6643			state.routedPoints = actual;
6644
6645			return changed;
6646		}
6647		else
6648		{
6649			return false;
6650		}
6651	};
6652
6653	/**
6654	 * Overrides painting the actual shape for taking into account jump style.
6655	 */
6656	var mxConnectorPaintLine = mxConnector.prototype.paintLine;
6657
6658	mxConnector.prototype.paintLine = function (c, absPts, rounded)
6659	{
6660		// Required for checking dirty state
6661		this.routedPoints = (this.state != null) ? this.state.routedPoints : null;
6662
6663		if (this.outline || this.state == null || this.style == null ||
6664			this.state.routedPoints == null || this.state.routedPoints.length == 0)
6665		{
6666			mxConnectorPaintLine.apply(this, arguments);
6667		}
6668		else
6669		{
6670			var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
6671				mxConstants.LINE_ARCSIZE) / 2;
6672			var size = (parseInt(mxUtils.getValue(this.style, 'jumpSize',
6673				Graph.defaultJumpSize)) - 2) / 2 + this.strokewidth;
6674			var style = mxUtils.getValue(this.style, 'jumpStyle', 'none');
6675			var moveTo = true;
6676			var last = null;
6677			var len = null;
6678			var pts = [];
6679			var n = null;
6680			c.begin();
6681
6682			for (var i = 0; i < this.state.routedPoints.length; i++)
6683			{
6684				var rpt = this.state.routedPoints[i];
6685				var pt = new mxPoint(rpt.x / this.scale, rpt.y / this.scale);
6686
6687				// Takes first and last point from passed-in array
6688				if (i == 0)
6689				{
6690					pt = absPts[0];
6691				}
6692				else if (i == this.state.routedPoints.length - 1)
6693				{
6694					pt = absPts[absPts.length - 1];
6695				}
6696
6697				var done = false;
6698
6699				// Type 1 is an intersection
6700				if (last != null && rpt.type == 1)
6701				{
6702					// Checks if next/previous points are too close
6703					var next = this.state.routedPoints[i + 1];
6704					var dx = next.x / this.scale - pt.x;
6705					var dy = next.y / this.scale - pt.y;
6706					var dist = dx * dx + dy * dy;
6707
6708					if (n == null)
6709					{
6710						n = new mxPoint(pt.x - last.x, pt.y - last.y);
6711						len = Math.sqrt(n.x * n.x + n.y * n.y);
6712
6713						if (len > 0)
6714						{
6715							n.x = n.x * size / len;
6716							n.y = n.y * size / len;
6717						}
6718						else
6719						{
6720							n = null;
6721						}
6722					}
6723
6724					if (dist > size * size && len > 0)
6725					{
6726						var dx = last.x - pt.x;
6727						var dy = last.y - pt.y;
6728						var dist = dx * dx + dy * dy;
6729
6730						if (dist > size * size)
6731						{
6732							var p0 = new mxPoint(pt.x - n.x, pt.y - n.y);
6733							var p1 = new mxPoint(pt.x + n.x, pt.y + n.y);
6734							pts.push(p0);
6735
6736							this.addPoints(c, pts, rounded, arcSize, false, null, moveTo);
6737
6738							var f = (Math.round(n.x) < 0 || (Math.round(n.x) == 0
6739									&& Math.round(n.y) <= 0)) ? 1 : -1;
6740							moveTo = false;
6741
6742							if (style == 'sharp')
6743							{
6744								c.lineTo(p0.x - n.y * f, p0.y + n.x * f);
6745								c.lineTo(p1.x - n.y * f, p1.y + n.x * f);
6746								c.lineTo(p1.x, p1.y);
6747							}
6748							else if (style == 'line')
6749							{
6750								c.moveTo(p0.x + n.y * f, p0.y - n.x * f);
6751								c.lineTo(p0.x - n.y * f, p0.y + n.x * f);
6752								c.moveTo(p1.x - n.y * f, p1.y + n.x * f);
6753								c.lineTo(p1.x + n.y * f, p1.y - n.x * f);
6754								c.moveTo(p1.x, p1.y);
6755							}
6756							else if (style == 'arc')
6757							{
6758								f *= 1.3;
6759								c.curveTo(p0.x - n.y * f, p0.y + n.x * f,
6760									p1.x - n.y * f, p1.y + n.x * f,
6761									p1.x, p1.y);
6762							}
6763							else
6764							{
6765								c.moveTo(p1.x, p1.y);
6766								moveTo = true;
6767							}
6768
6769							pts = [p1];
6770							done = true;
6771						}
6772					}
6773				}
6774				else
6775				{
6776					n = null;
6777				}
6778
6779				if (!done)
6780				{
6781					pts.push(pt);
6782					last = pt;
6783				}
6784			}
6785
6786			this.addPoints(c, pts, rounded, arcSize, false, null, moveTo);
6787			c.stroke();
6788		}
6789	};
6790
6791	/**
6792	 * Adds support for centerPerimeter which is a special case of a fixed point perimeter.
6793	 */
6794	var mxGraphViewGetFixedTerminalPoint = mxGraphView.prototype.getFixedTerminalPoint;
6795
6796	mxGraphView.prototype.getFixedTerminalPoint = function(edge, terminal, source, constraint)
6797	{
6798		if (terminal != null && terminal.style[mxConstants.STYLE_PERIMETER] == 'centerPerimeter')
6799		{
6800			return new mxPoint(terminal.getCenterX(), terminal.getCenterY());
6801		}
6802		else
6803		{
6804			return mxGraphViewGetFixedTerminalPoint.apply(this, arguments);
6805		}
6806	};
6807
6808	/**
6809	 * Adds support for snapToPoint style.
6810	 */
6811	var mxGraphViewUpdateFloatingTerminalPoint = mxGraphView.prototype.updateFloatingTerminalPoint;
6812
6813	mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source)
6814	{
6815		if (start != null && edge != null &&
6816			(start.style['snapToPoint'] == '1' ||
6817			edge.style['snapToPoint'] == '1'))
6818		{
6819		    start = this.getTerminalPort(edge, start, source);
6820		    var next = this.getNextPoint(edge, end, source);
6821
6822		    var orth = this.graph.isOrthogonal(edge);
6823		    var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
6824		    var center = new mxPoint(start.getCenterX(), start.getCenterY());
6825
6826		    if (alpha != 0)
6827		    {
6828		        var cos = Math.cos(-alpha);
6829		        var sin = Math.sin(-alpha);
6830		        next = mxUtils.getRotatedPoint(next, cos, sin, center);
6831		    }
6832
6833		    var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
6834		    border += parseFloat(edge.style[(source) ?
6835		        mxConstants.STYLE_SOURCE_PERIMETER_SPACING :
6836		        mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);
6837		    var pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border);
6838
6839		    if (alpha != 0)
6840		    {
6841		        var cos = Math.cos(alpha);
6842		        var sin = Math.sin(alpha);
6843		        pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
6844		    }
6845
6846		    edge.setAbsoluteTerminalPoint(this.snapToAnchorPoint(edge, start, end, source, pt), source);
6847		}
6848		else
6849		{
6850			mxGraphViewUpdateFloatingTerminalPoint.apply(this, arguments);
6851		}
6852	};
6853
6854	mxGraphView.prototype.snapToAnchorPoint = function(edge, start, end, source, pt)
6855	{
6856		if (start != null && edge != null)
6857		{
6858	        var constraints = this.graph.getAllConnectionConstraints(start)
6859	        var nearest = null;
6860	        var dist = null;
6861
6862	        if (constraints != null)
6863	        {
6864		        for (var i = 0; i < constraints.length; i++)
6865		        {
6866		            var cp = this.graph.getConnectionPoint(start, constraints[i]);
6867
6868		            if (cp != null)
6869		            {
6870		                var tmp = (cp.x - pt.x) * (cp.x - pt.x) + (cp.y - pt.y) * (cp.y - pt.y);
6871
6872		                if (dist == null || tmp < dist)
6873		                {
6874		                    nearest = cp;
6875		                    dist = tmp;
6876		                }
6877		            }
6878		        }
6879	        }
6880
6881	        if (nearest != null)
6882	        {
6883	            pt = nearest;
6884	        }
6885		}
6886
6887		return pt;
6888	};
6889
6890	/**
6891	 * Adds support for placeholders in text elements of shapes.
6892	 */
6893	var mxStencilEvaluateTextAttribute = mxStencil.prototype.evaluateTextAttribute;
6894
6895	mxStencil.prototype.evaluateTextAttribute = function(node, attribute, shape)
6896	{
6897		var result = mxStencilEvaluateTextAttribute.apply(this, arguments);
6898		var placeholders = node.getAttribute('placeholders');
6899
6900		if (placeholders == '1' && shape.state != null)
6901		{
6902			result = shape.state.view.graph.replacePlaceholders(shape.state.cell, result);
6903		}
6904
6905		return result;
6906	};
6907
6908	/**
6909	 * Adds custom stencils defined via shape=stencil(value) style. The value is a base64 encoded, compressed and
6910	 * URL encoded XML definition of the shape according to the stencil definition language of mxGraph.
6911	 *
6912	 * Needs to be in this file to make sure its part of the embed client code. Also the check for ZLib is
6913	 * different than for the Editor code.
6914	 */
6915	var mxCellRendererCreateShape = mxCellRenderer.prototype.createShape;
6916	mxCellRenderer.prototype.createShape = function(state)
6917	{
6918		if (state.style != null && typeof(pako) !== 'undefined')
6919		{
6920	    	var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);
6921
6922	    	// Extracts and decodes stencil XML if shape has the form shape=stencil(value)
6923	    	if (shape != null && typeof shape === 'string' && shape.substring(0, 8) == 'stencil(')
6924	    	{
6925	    		try
6926	    		{
6927	    			var stencil = shape.substring(8, shape.length - 1);
6928	    			var doc = mxUtils.parseXml(Graph.decompress(stencil));
6929
6930	    			return new mxShape(new mxStencil(doc.documentElement));
6931	    		}
6932	    		catch (e)
6933	    		{
6934	    			if (window.console != null)
6935	    			{
6936	    				console.log('Error in shape: ' + e);
6937	    			}
6938	    		}
6939	    	}
6940		}
6941
6942		return mxCellRendererCreateShape.apply(this, arguments);
6943	};
6944})();
6945
6946/**
6947 * Overrides stencil registry for dynamic loading of stencils.
6948 */
6949/**
6950 * Maps from library names to an array of Javascript filenames,
6951 * which are synchronously loaded. Currently only stencil files
6952 * (.xml) and JS files (.js) are supported.
6953 * IMPORTANT: For embedded diagrams to work entries must also
6954 * be added in EmbedServlet.java.
6955 */
6956mxStencilRegistry.libraries = {};
6957
6958/**
6959 * Global switch to disable dynamic loading.
6960 */
6961mxStencilRegistry.dynamicLoading = true;
6962
6963/**
6964 * Global switch to disable eval for JS (preload all JS instead).
6965 */
6966mxStencilRegistry.allowEval = true;
6967
6968/**
6969 * Stores all package names that have been dynamically loaded.
6970 * Each package is only loaded once.
6971 */
6972mxStencilRegistry.packages = [];
6973
6974/**
6975 * Stores all package names that have been dynamically loaded.
6976 * Each package is only loaded once.
6977 */
6978mxStencilRegistry.filesLoaded = {};
6979
6980// Extends the default stencil registry to add dynamic loading
6981mxStencilRegistry.getStencil = function(name)
6982{
6983	var result = mxStencilRegistry.stencils[name];
6984
6985	if (result == null && mxCellRenderer.defaultShapes[name] == null && mxStencilRegistry.dynamicLoading)
6986	{
6987		var basename = mxStencilRegistry.getBasenameForStencil(name);
6988
6989		// Loads stencil files and tries again
6990		if (basename != null)
6991		{
6992			var libs = mxStencilRegistry.libraries[basename];
6993
6994			if (libs != null)
6995			{
6996				if (mxStencilRegistry.packages[basename] == null)
6997				{
6998					for (var i = 0; i < libs.length; i++)
6999					{
7000						var fname = libs[i];
7001
7002						if (!mxStencilRegistry.filesLoaded[fname])
7003						{
7004							mxStencilRegistry.filesLoaded[fname] = true;
7005
7006							if (fname.toLowerCase().substring(fname.length - 4, fname.length) == '.xml')
7007							{
7008								mxStencilRegistry.loadStencilSet(fname, null);
7009							}
7010							else if (fname.toLowerCase().substring(fname.length - 3, fname.length) == '.js')
7011							{
7012								try
7013								{
7014									if (mxStencilRegistry.allowEval)
7015									{
7016										var req = mxUtils.load(fname);
7017
7018										if (req != null && req.getStatus() >= 200 && req.getStatus() <= 299)
7019										{
7020											eval.call(window, req.getText());
7021										}
7022									}
7023								}
7024								catch (e)
7025								{
7026									if (window.console != null)
7027									{
7028										console.log('error in getStencil:', name, basename, libs, fname, e);
7029									}
7030								}
7031							}
7032							else
7033							{
7034								// FIXME: This does not yet work as the loading is triggered after
7035								// the shape was used in the graph, at which point the keys have
7036								// typically been translated in the calling method.
7037								//mxResources.add(fname);
7038							}
7039						}
7040					}
7041
7042					mxStencilRegistry.packages[basename] = 1;
7043				}
7044			}
7045			else
7046			{
7047				// Replaces '_-_' with '_'
7048				basename = basename.replace('_-_', '_');
7049				mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/' + basename + '.xml', null);
7050			}
7051
7052			result = mxStencilRegistry.stencils[name];
7053		}
7054	}
7055
7056	return result;
7057};
7058
7059// Returns the basename for the given stencil or null if no file must be
7060// loaded to render the given stencil.
7061mxStencilRegistry.getBasenameForStencil = function(name)
7062{
7063	var tmp = null;
7064
7065	if (name != null && typeof name === 'string')
7066	{
7067		var parts = name.split('.');
7068
7069		if (parts.length > 0 && parts[0] == 'mxgraph')
7070		{
7071			tmp = parts[1];
7072
7073			for (var i = 2; i < parts.length - 1; i++)
7074			{
7075				tmp += '/' + parts[i];
7076			}
7077		}
7078	}
7079
7080	return tmp;
7081};
7082
7083// Loads the given stencil set
7084mxStencilRegistry.loadStencilSet = function(stencilFile, postStencilLoad, force, async)
7085{
7086	force = (force != null) ? force : false;
7087
7088	// Uses additional cache for detecting previous load attempts
7089	var xmlDoc = mxStencilRegistry.packages[stencilFile];
7090
7091	if (force || xmlDoc == null)
7092	{
7093		var install = false;
7094
7095		if (xmlDoc == null)
7096		{
7097			try
7098			{
7099				if (async)
7100				{
7101					mxStencilRegistry.loadStencil(stencilFile, mxUtils.bind(this, function(xmlDoc2)
7102					{
7103						if (xmlDoc2 != null && xmlDoc2.documentElement != null)
7104						{
7105							mxStencilRegistry.packages[stencilFile] = xmlDoc2;
7106							install = true;
7107							mxStencilRegistry.parseStencilSet(xmlDoc2.documentElement, postStencilLoad, install);
7108						}
7109					}));
7110
7111					return;
7112				}
7113				else
7114				{
7115					xmlDoc = mxStencilRegistry.loadStencil(stencilFile);
7116					mxStencilRegistry.packages[stencilFile] = xmlDoc;
7117					install = true;
7118				}
7119			}
7120			catch (e)
7121			{
7122				if (window.console != null)
7123				{
7124					console.log('error in loadStencilSet:', stencilFile, e);
7125				}
7126			}
7127		}
7128
7129		if (xmlDoc != null && xmlDoc.documentElement != null)
7130		{
7131			mxStencilRegistry.parseStencilSet(xmlDoc.documentElement, postStencilLoad, install);
7132		}
7133	}
7134};
7135
7136// Loads the given stencil XML file.
7137mxStencilRegistry.loadStencil = function(filename, fn)
7138{
7139	if (fn != null)
7140	{
7141		var req = mxUtils.get(filename, mxUtils.bind(this, function(req)
7142		{
7143			fn((req.getStatus() >= 200 && req.getStatus() <= 299) ? req.getXml() : null);
7144		}));
7145	}
7146	else
7147	{
7148		return mxUtils.load(filename).getXml();
7149	}
7150};
7151
7152// Takes array of strings
7153mxStencilRegistry.parseStencilSets = function(stencils)
7154{
7155	for (var i = 0; i < stencils.length; i++)
7156	{
7157		mxStencilRegistry.parseStencilSet(mxUtils.parseXml(stencils[i]).documentElement);
7158	}
7159};
7160
7161// Parses the given stencil set
7162mxStencilRegistry.parseStencilSet = function(root, postStencilLoad, install)
7163{
7164	if (root.nodeName == 'stencils')
7165	{
7166		var shapes = root.firstChild;
7167
7168		while (shapes != null)
7169		{
7170			if (shapes.nodeName == 'shapes')
7171			{
7172				mxStencilRegistry.parseStencilSet(shapes, postStencilLoad, install);
7173			}
7174
7175			shapes = shapes.nextSibling;
7176		}
7177	}
7178	else
7179	{
7180		install = (install != null) ? install : true;
7181		var shape = root.firstChild;
7182		var packageName = '';
7183		var name = root.getAttribute('name');
7184
7185		if (name != null)
7186		{
7187			packageName = name + '.';
7188		}
7189
7190		while (shape != null)
7191		{
7192			if (shape.nodeType == mxConstants.NODETYPE_ELEMENT)
7193			{
7194				name = shape.getAttribute('name');
7195
7196				if (name != null)
7197				{
7198					packageName = packageName.toLowerCase();
7199					var stencilName = name.replace(/ /g,"_");
7200
7201					if (install)
7202					{
7203						mxStencilRegistry.addStencil(packageName + stencilName.toLowerCase(), new mxStencil(shape));
7204					}
7205
7206					if (postStencilLoad != null)
7207					{
7208						var w = shape.getAttribute('w');
7209						var h = shape.getAttribute('h');
7210
7211						w = (w == null) ? 80 : parseInt(w, 10);
7212						h = (h == null) ? 80 : parseInt(h, 10);
7213
7214						postStencilLoad(packageName, stencilName, name, w, h);
7215					}
7216				}
7217			}
7218
7219			shape = shape.nextSibling;
7220		}
7221	}
7222};
7223
7224/**
7225 * These overrides are only added if mxVertexHandler is defined (ie. not in embedded graph)
7226 */
7227if (typeof mxVertexHandler != 'undefined')
7228{
7229	(function()
7230	{
7231		// Sets colors for handles
7232		mxConstants.HANDLE_FILLCOLOR = '#29b6f2';
7233		mxConstants.HANDLE_STROKECOLOR = '#0088cf';
7234		mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
7235		mxConstants.OUTLINE_COLOR = '#00a8ff';
7236		mxConstants.OUTLINE_HANDLE_FILLCOLOR = '#99ccff';
7237		mxConstants.OUTLINE_HANDLE_STROKECOLOR = '#00a8ff';
7238		mxConstants.CONNECT_HANDLE_FILLCOLOR = '#cee7ff';
7239		mxConstants.EDGE_SELECTION_COLOR = '#00a8ff';
7240		mxConstants.DEFAULT_VALID_COLOR = '#00a8ff';
7241		mxConstants.LABEL_HANDLE_FILLCOLOR = '#cee7ff';
7242		mxConstants.GUIDE_COLOR = '#0088cf';
7243		mxConstants.HIGHLIGHT_OPACITY = 30;
7244	    mxConstants.HIGHLIGHT_SIZE = 5;
7245
7246		// Enables snapping to off-grid terminals for edge waypoints
7247		mxEdgeHandler.prototype.snapToTerminals = true;
7248
7249		// Enables guides
7250		mxGraphHandler.prototype.guidesEnabled = true;
7251
7252		// Removes parents where all child cells are moved out
7253		mxGraphHandler.prototype.removeEmptyParents = true;
7254
7255		// Enables fading of rubberband
7256		mxRubberband.prototype.fadeOut = true;
7257
7258		// Alt-move disables guides
7259		mxGuide.prototype.isEnabledForEvent = function(evt)
7260		{
7261			return !mxEvent.isAltDown(evt);
7262		};
7263
7264		// Ignores all table cells in layouts
7265		var graphLayoutIsVertexIgnored = mxGraphLayout.prototype.isVertexIgnored;
7266		mxGraphLayout.prototype.isVertexIgnored = function(vertex)
7267		{
7268			return graphLayoutIsVertexIgnored.apply(this, arguments) ||
7269				this.graph.isTableRow(vertex) || this.graph.isTableCell(vertex);
7270		};
7271
7272		// Adds support for ignoreEdge style
7273		var graphLayoutIsEdgeIgnored = mxGraphLayout.prototype.isEdgeIgnored;
7274		mxGraphLayout.prototype.isEdgeIgnored = function(edge)
7275		{
7276			return graphLayoutIsEdgeIgnored.apply(this, arguments) ||
7277				this.graph.isEdgeIgnored(edge);
7278		};
7279
7280		// Extends connection handler to enable ctrl+drag for cloning source cell
7281		// since copyOnConnect is now disabled by default
7282		var mxConnectionHandlerCreateTarget = mxConnectionHandler.prototype.isCreateTarget;
7283		mxConnectionHandler.prototype.isCreateTarget = function(evt)
7284		{
7285			return this.graph.isCloneEvent(evt) || mxConnectionHandlerCreateTarget.apply(this, arguments);
7286		};
7287
7288		// Overrides highlight shape for connection points
7289		mxConstraintHandler.prototype.createHighlightShape = function()
7290		{
7291			var hl = new mxEllipse(null, this.highlightColor, this.highlightColor, 0);
7292			hl.opacity = mxConstants.HIGHLIGHT_OPACITY;
7293
7294			return hl;
7295		};
7296
7297		// Overrides edge preview to use current edge shape and default style
7298		mxConnectionHandler.prototype.livePreview = true;
7299		mxConnectionHandler.prototype.cursor = 'crosshair';
7300
7301		// Uses current edge style for connect preview
7302		mxConnectionHandler.prototype.createEdgeState = function(me)
7303		{
7304			var style = this.graph.createCurrentEdgeStyle();
7305			var edge = this.graph.createEdge(null, null, null, null, null, style);
7306			var state = new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
7307
7308			for (var key in this.graph.currentEdgeStyle)
7309			{
7310				state.style[key] = this.graph.currentEdgeStyle[key];
7311			}
7312
7313			state.style = this.graph.postProcessCellStyle(state.style);
7314
7315			return state;
7316		};
7317
7318		// Overrides dashed state with current edge style
7319		var connectionHandlerCreateShape = mxConnectionHandler.prototype.createShape;
7320		mxConnectionHandler.prototype.createShape = function()
7321		{
7322			var shape = connectionHandlerCreateShape.apply(this, arguments);
7323
7324			shape.isDashed = this.graph.currentEdgeStyle[mxConstants.STYLE_DASHED] == '1';
7325
7326			return shape;
7327		}
7328
7329		// Overrides live preview to keep current style
7330		mxConnectionHandler.prototype.updatePreview = function(valid)
7331		{
7332			// do not change color of preview
7333		};
7334
7335		// Overrides connection handler to ignore edges instead of not allowing connections
7336		var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
7337		mxConnectionHandler.prototype.createMarker = function()
7338		{
7339			var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
7340
7341			var markerGetCell = marker.getCell;
7342			marker.getCell = mxUtils.bind(this, function(me)
7343			{
7344				var result = markerGetCell.apply(this, arguments);
7345
7346				this.error = null;
7347
7348				return result;
7349			});
7350
7351			return marker;
7352		};
7353
7354		/**
7355		 *
7356		 */
7357		Graph.prototype.defaultVertexStyle = {};
7358
7359		/**
7360		 * Contains the default style for edges.
7361		 */
7362		Graph.prototype.defaultEdgeStyle = {'edgeStyle': 'orthogonalEdgeStyle', 'rounded': '0',
7363			'jettySize': 'auto', 'orthogonalLoop': '1'};
7364
7365		/**
7366		 * Returns the current edge style as a string.
7367		 */
7368		Graph.prototype.createCurrentEdgeStyle = function()
7369		{
7370			var style = 'edgeStyle=' + (this.currentEdgeStyle['edgeStyle'] || 'none') + ';';
7371			var keys = ['shape', 'curved', 'rounded', 'comic', 'sketch', 'fillWeight', 'hachureGap',
7372				'hachureAngle', 'jiggle', 'disableMultiStroke', 'disableMultiStrokeFill', 'fillStyle',
7373				'curveFitting', 'simplification', 'comicStyle', 'jumpStyle', 'jumpSize'];
7374
7375			for (var i = 0; i < keys.length; i++)
7376			{
7377				if (this.currentEdgeStyle[keys[i]] != null)
7378				{
7379					style += keys[i] + '=' + this.currentEdgeStyle[keys[i]] + ';';
7380				}
7381			}
7382
7383			// Overrides the global default to match the default edge style
7384			if (this.currentEdgeStyle['orthogonalLoop'] != null)
7385			{
7386				style += 'orthogonalLoop=' + this.currentEdgeStyle['orthogonalLoop'] + ';';
7387			}
7388			else if (Graph.prototype.defaultEdgeStyle['orthogonalLoop'] != null)
7389			{
7390				style += 'orthogonalLoop=' + Graph.prototype.defaultEdgeStyle['orthogonalLoop'] + ';';
7391			}
7392
7393			// Overrides the global default to match the default edge style
7394			if (this.currentEdgeStyle['jettySize'] != null)
7395			{
7396				style += 'jettySize=' + this.currentEdgeStyle['jettySize'] + ';';
7397			}
7398			else if (Graph.prototype.defaultEdgeStyle['jettySize'] != null)
7399			{
7400				style += 'jettySize=' + Graph.prototype.defaultEdgeStyle['jettySize'] + ';';
7401			}
7402
7403			// Special logic for custom property of elbowEdgeStyle
7404			if (this.currentEdgeStyle['edgeStyle'] == 'elbowEdgeStyle' && this.currentEdgeStyle['elbow'] != null)
7405			{
7406				style += 'elbow=' + this.currentEdgeStyle['elbow'] + ';';
7407			}
7408
7409			if (this.currentEdgeStyle['html'] != null)
7410			{
7411				style += 'html=' + this.currentEdgeStyle['html'] + ';';
7412			}
7413			else
7414			{
7415				style += 'html=1;';
7416			}
7417
7418			return style;
7419		};
7420
7421		/**
7422		 * Removes implicit styles from cell styles so that dark mode works using the
7423		 * default values from the stylesheet.
7424		 */
7425		Graph.prototype.updateCellStyles = function(key, value, cells)
7426		{
7427			this.model.beginUpdate();
7428			try
7429			{
7430				for (var i = 0; i < cells.length; i++)
7431				{
7432					if (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i]))
7433					{
7434						this.setCellStyles(key, null, [cells[i]]);
7435						var style = this.getCellStyle(cells[i]);
7436						var temp = style[key];
7437
7438						if (value != ((temp == null) ? mxConstants.NONE : temp))
7439						{
7440							this.setCellStyles(key, value, [cells[i]]);
7441						}
7442					}
7443				}
7444			}
7445			finally
7446			{
7447				this.model.endUpdate();
7448			}
7449		};
7450
7451		/**
7452		 * Hook for subclassers.
7453		 */
7454		Graph.prototype.getPagePadding = function()
7455		{
7456			return new mxPoint(0, 0);
7457		};
7458
7459		/**
7460		 * Loads the stylesheet for this graph.
7461		 */
7462		Graph.prototype.loadStylesheet = function()
7463		{
7464			var node = (this.themes != null) ? this.themes[this.defaultThemeName] :
7465				(!mxStyleRegistry.dynamicLoading) ? null :
7466				mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement();
7467
7468			if (node != null)
7469			{
7470				var dec = new mxCodec(node.ownerDocument);
7471				dec.decode(node, this.getStylesheet());
7472			}
7473		};
7474
7475		/**
7476		 * Creates lookup from object IDs to cell IDs.
7477		 */
7478		Graph.prototype.createCellLookup = function(cells, lookup)
7479		{
7480			lookup = (lookup != null) ? lookup : new Object();
7481
7482			for (var i = 0; i < cells.length; i++)
7483			{
7484				var cell = cells[i];
7485				lookup[mxObjectIdentity.get(cell)] = cell.getId();
7486				var childCount = this.model.getChildCount(cell);
7487
7488				for (var j = 0; j < childCount; j++)
7489				{
7490					this.createCellLookup([this.model.getChildAt(cell, j)], lookup);
7491				}
7492			}
7493
7494			return lookup;
7495		};
7496
7497		/**
7498		 * Creates lookup from original to cloned cell IDs where mapping is
7499		 * the mapping used in cloneCells and lookup is a mapping from
7500		 * object IDs to cell IDs.
7501		 */
7502		Graph.prototype.createCellMapping = function(mapping, lookup, cellMapping)
7503		{
7504			cellMapping = (cellMapping != null) ? cellMapping : new Object();
7505
7506			for (var objectId in mapping)
7507			{
7508				var cellId = lookup[objectId];
7509
7510				if (cellMapping[cellId] == null)
7511				{
7512					// Uses empty string if clone ID was null which means
7513					// the cell was cloned but not inserted into the model.
7514					cellMapping[cellId] = mapping[objectId].getId() || '';
7515				}
7516			}
7517
7518			return cellMapping;
7519		};
7520
7521		/**
7522		 *
7523		 */
7524		Graph.prototype.importGraphModel = function(node, dx, dy, crop)
7525		{
7526			dx = (dx != null) ? dx : 0;
7527			dy = (dy != null) ? dy : 0;
7528
7529			var codec = new mxCodec(node.ownerDocument);
7530			var tempModel = new mxGraphModel();
7531			codec.decode(node, tempModel);
7532			var cells = []
7533
7534			// Clones cells to remove invalid edges
7535			var cloneMap = new Object();
7536			var cellMapping = new Object();
7537			var layers = tempModel.getChildren(this.cloneCell(tempModel.root,
7538				this.isCloneInvalidEdges(), cloneMap));
7539
7540			if (layers != null)
7541			{
7542				// Creates lookup from object IDs to cell IDs
7543				var lookup = this.createCellLookup([tempModel.root]);
7544
7545				// Uses copy as layers are removed from array inside loop
7546				layers = layers.slice();
7547
7548				this.model.beginUpdate();
7549				try
7550				{
7551					// Merges into unlocked current layer if one layer is pasted
7552					if (layers.length == 1 && !this.isCellLocked(this.getDefaultParent()))
7553					{
7554						var children = tempModel.getChildren(layers[0]);
7555
7556						if (children != null)
7557						{
7558							cells = this.moveCells(children,
7559								dx, dy, false, this.getDefaultParent());
7560
7561							// Imported default parent maps to local default parent
7562							cellMapping[tempModel.getChildAt(tempModel.root, 0).getId()] =
7563								this.getDefaultParent().getId();
7564						}
7565					}
7566					else
7567					{
7568						for (var i = 0; i < layers.length; i++)
7569						{
7570							var children = this.model.getChildren(this.moveCells(
7571								[layers[i]], dx, dy, false, this.model.getRoot())[0]);
7572
7573							if (children != null)
7574							{
7575								cells = cells.concat(children);
7576							}
7577						}
7578					}
7579
7580					if (cells != null)
7581					{
7582						// Adds mapping for all cloned entries from imported to local cell ID
7583						this.createCellMapping(cloneMap, lookup, cellMapping);
7584						this.updateCustomLinks(cellMapping, cells);
7585
7586						if (crop)
7587						{
7588							if (this.isGridEnabled())
7589							{
7590								dx = this.snap(dx);
7591								dy = this.snap(dy);
7592							}
7593
7594							var bounds = this.getBoundingBoxFromGeometry(cells, true);
7595
7596							if (bounds != null)
7597							{
7598								this.moveCells(cells, dx - bounds.x, dy - bounds.y);
7599							}
7600						}
7601					}
7602				}
7603				finally
7604				{
7605					this.model.endUpdate();
7606				}
7607			}
7608
7609			return cells;
7610		};
7611
7612		/**
7613		 * Translates this point by the given vector.
7614		 *
7615		 * @param {number} dx X-coordinate of the translation.
7616		 * @param {number} dy Y-coordinate of the translation.
7617		 */
7618		Graph.prototype.encodeCells = function(cells)
7619		{
7620			var cloneMap = new Object();
7621			var clones = this.cloneCells(cells, null, cloneMap);
7622
7623			// Creates a dictionary for fast lookups
7624			var dict = new mxDictionary();
7625
7626			for (var i = 0; i < cells.length; i++)
7627			{
7628				dict.put(cells[i], true);
7629			}
7630
7631			var codec = new mxCodec();
7632			var model = new mxGraphModel();
7633			var parent = model.getChildAt(model.getRoot(), 0);
7634
7635			for (var i = 0; i < clones.length; i++)
7636			{
7637				model.add(parent, clones[i]);
7638
7639				// Checks for orphaned relative children and makes absolute
7640				var state = this.view.getState(cells[i]);
7641
7642				if (state != null)
7643				{
7644					var geo = this.getCellGeometry(clones[i]);
7645
7646					if (geo != null && geo.relative && !this.model.isEdge(cells[i]) &&
7647						dict.get(this.model.getParent(cells[i])) == null)
7648					{
7649						geo.offset = null;
7650						geo.relative = false;
7651						geo.x = state.x / state.view.scale - state.view.translate.x;
7652						geo.y = state.y / state.view.scale - state.view.translate.y;
7653					}
7654				}
7655			}
7656
7657			this.updateCustomLinks(this.createCellMapping(cloneMap,
7658				this.createCellLookup(cells)), clones);
7659
7660			return codec.encode(model);
7661		};
7662
7663		/**
7664		 * Overridden to check for table shape.
7665		 */
7666		Graph.prototype.isSwimlane = function(cell, ignoreState)
7667		{
7668			if (cell != null && this.model.getParent(cell) != this.model.getRoot() &&
7669				!this.model.isEdge(cell))
7670			{
7671				var shape = this.getCurrentCellStyle(cell, ignoreState)
7672					[mxConstants.STYLE_SHAPE];
7673
7674				return shape == mxConstants.SHAPE_SWIMLANE || shape == 'table';
7675			}
7676
7677			return false;
7678		};
7679
7680		/**
7681		 * Overridden to add expand style.
7682		 */
7683		var graphIsExtendParent = Graph.prototype.isExtendParent;
7684		Graph.prototype.isExtendParent = function(cell)
7685		{
7686			var parent = this.model.getParent(cell);
7687
7688			if (parent != null)
7689			{
7690				var style = this.getCurrentCellStyle(parent);
7691
7692				if (style['expand'] != null)
7693				{
7694					return style['expand'] != '0';
7695				}
7696			}
7697
7698			return graphIsExtendParent.apply(this, arguments) &&
7699				(parent == null || !this.isTable(parent));
7700		};
7701
7702		/**
7703		 * Overridden to use table cell instead of table as parent.
7704		 */
7705		var graphSplitEdge = Graph.prototype.splitEdge;
7706		Graph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy, x, y, parent)
7707		{
7708			if (parent == null)
7709			{
7710				parent = this.model.getParent(edge);
7711
7712				if (this.isTable(parent) || this.isTableRow(parent))
7713				{
7714					parent = this.getCellAt(x, y, null, true, false);
7715				}
7716			}
7717
7718			var newEdge = null;
7719
7720			this.model.beginUpdate();
7721			try
7722			{
7723				var newEdge = graphSplitEdge.apply(this, [edge, cells, newEdge, dx, dy, x, y, parent]);
7724
7725				// Removes cloned value on first segment
7726				this.model.setValue(newEdge, '');
7727
7728				// Removes child labels on first or second segment depending on coordinate
7729				// LATER: Split and reposition labels based on x and y
7730				var sourceLabels = this.getChildCells(newEdge, true);
7731
7732				for (var i = 0; i < sourceLabels.length; i++)
7733				{
7734					var geo = this.getCellGeometry(sourceLabels[i]);
7735
7736					if (geo != null && geo.relative && geo.x > 0)
7737					{
7738						this.model.remove(sourceLabels[i]);
7739					}
7740				}
7741
7742				var targetLabels = this.getChildCells(edge, true);
7743
7744				for (var i = 0; i < targetLabels.length; i++)
7745				{
7746					var geo = this.getCellGeometry(targetLabels[i]);
7747
7748					if (geo != null && geo.relative && geo.x <= 0)
7749					{
7750						this.model.remove(targetLabels[i]);
7751					}
7752				}
7753
7754				// Removes end arrow and target perimetr Spacing on first segment, start arrow on second segment
7755				this.setCellStyles(mxConstants.STYLE_TARGET_PERIMETER_SPACING, null, [newEdge]);
7756				this.setCellStyles(mxConstants.STYLE_ENDARROW, mxConstants.NONE, [newEdge]);
7757
7758				// Removes start arrow and source perimeter spacing on second segment
7759				this.setCellStyles(mxConstants.STYLE_SOURCE_PERIMETER_SPACING, null, [edge]);
7760				this.setCellStyles(mxConstants.STYLE_STARTARROW, mxConstants.NONE, [edge]);
7761
7762				// Removes entryX/Y and exitX/Y if snapToPoint is used
7763				var target = this.model.getTerminal(newEdge, false);
7764
7765				if (target != null)
7766				{
7767					var style = this.getCurrentCellStyle(target);
7768
7769					if (style != null && style['snapToPoint'] == '1')
7770					{
7771						this.setCellStyles(mxConstants.STYLE_EXIT_X, null, [edge]);
7772						this.setCellStyles(mxConstants.STYLE_EXIT_Y, null, [edge]);
7773						this.setCellStyles(mxConstants.STYLE_ENTRY_X, null, [newEdge]);
7774						this.setCellStyles(mxConstants.STYLE_ENTRY_Y, null, [newEdge]);
7775					}
7776				}
7777
7778			}
7779			finally
7780			{
7781				this.model.endUpdate();
7782			}
7783
7784			return newEdge;
7785		};
7786
7787		/**
7788		 * Overridden to flatten cell hierarchy for selecting next and previous.
7789		 */
7790		var graphSelectCell = Graph.prototype.selectCell;
7791		Graph.prototype.selectCell = function(isNext, isParent, isChild)
7792		{
7793			if (isParent || isChild)
7794			{
7795				graphSelectCell.apply(this, arguments);
7796			}
7797			else
7798			{
7799				var cell = this.getSelectionCell();
7800				var index = null;
7801				var cells = [];
7802
7803				// LATER: Reverse traverse order for !isNext
7804				var flatten = mxUtils.bind(this, function(temp)
7805				{
7806					if (this.view.getState(temp) != null &&
7807						(this.model.isVertex(temp) ||
7808						this.model.isEdge(temp)))
7809					{
7810						cells.push(temp);
7811
7812						if (temp == cell)
7813						{
7814							index = cells.length - 1;
7815						}
7816						else if ((isNext && cell == null && cells.length > 0) ||
7817							(index != null && ((isNext && cells.length > index)) ||
7818							(!isNext && index > 0)))
7819						{
7820							return;
7821						}
7822					}
7823
7824					for (var i = 0; i < this.model.getChildCount(temp); i++)
7825					{
7826						flatten(this.model.getChildAt(temp, i));
7827					}
7828				});
7829
7830				flatten(this.model.root);
7831
7832				if (cells.length > 0)
7833				{
7834					if (index != null)
7835					{
7836						index = mxUtils.mod(index + ((isNext) ? 1 : -1), cells.length)
7837					}
7838					else
7839					{
7840						index = 0;
7841					}
7842
7843					this.setSelectionCell(cells[index]);
7844				}
7845			}
7846		};
7847
7848		/**
7849		 * Swaps UML Lifelines.
7850		 */
7851		Graph.prototype.swapUmlLifelines = function(cells, target)
7852		{
7853			var result = false;
7854
7855			if (target != null && cells.length == 1)
7856			{
7857				var targetState = this.view.getState(target);
7858				var sourceState = this.view.getState(cells[0]);
7859
7860				if (targetState != null && sourceState != null &&
7861					targetState.style['shape'] == 'umlLifeline' &&
7862					sourceState.style['shape'] == 'umlLifeline')
7863				{
7864					var g1 = this.getCellGeometry(target);
7865					var g2 = this.getCellGeometry(cells[0]);
7866
7867					if (g1 != null && g2 != null)
7868					{
7869						var ng1 = g1.clone();
7870						var ng2 = g2.clone();
7871						ng2.x = ng1.x;
7872						ng2.y = ng1.y;
7873						ng1.x = g2.x;
7874						ng1.y = g2.y;
7875
7876						this.model.beginUpdate();
7877						try
7878						{
7879							this.model.setGeometry(target, ng1);
7880							this.model.setGeometry(cells[0], ng2);
7881						}
7882						finally
7883						{
7884							this.model.endUpdate();
7885						}
7886
7887						result = true;
7888					}
7889				}
7890			}
7891
7892			return result;
7893		};
7894
7895		/**
7896		 * Overrides cloning cells in moveCells.
7897		 */
7898		var graphMoveCells = Graph.prototype.moveCells;
7899		Graph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping)
7900		{
7901			if (!clone && this.swapUmlLifelines(cells, target))
7902			{
7903				return cells;
7904			}
7905
7906			mapping = (mapping != null) ? mapping : new Object();
7907
7908			// Replaces source tables with rows
7909			if (this.isTable(target))
7910			{
7911				var newCells = [];
7912
7913				for (var i = 0; i < cells.length; i++)
7914				{
7915					if (this.isTable(cells[i]))
7916					{
7917						newCells = newCells.concat(this.model.getChildCells(cells[i], true).reverse());
7918					}
7919					else
7920					{
7921						newCells.push(cells[i]);
7922					}
7923				}
7924
7925				cells = newCells;
7926			}
7927
7928			this.model.beginUpdate();
7929			try
7930			{
7931				// Updates source and target table heights and matches
7932				// column count for moving rows between tables
7933				var sourceTables = [];
7934
7935				for (var i = 0; i < cells.length; i++)
7936				{
7937					if (target != null && this.isTableRow(cells[i]))
7938					{
7939						var parent = this.model.getParent(cells[i]);
7940						var row = this.getCellGeometry(cells[i]);
7941
7942						if (this.isTable(parent))
7943						{
7944							sourceTables.push(parent);
7945						}
7946
7947						if (parent != null && row != null &&
7948							this.isTable(parent) &&
7949							this.isTable(target) &&
7950							(clone || parent != target))
7951						{
7952							if (!clone)
7953							{
7954								var table = this.getCellGeometry(parent);
7955
7956								if (table != null)
7957								{
7958									table = table.clone();
7959									table.height -= row.height;
7960									this.model.setGeometry(parent, table);
7961								}
7962							}
7963
7964							var table = this.getCellGeometry(target);
7965
7966							if (table != null)
7967							{
7968								table = table.clone();
7969								table.height += row.height;
7970								this.model.setGeometry(target, table);
7971							}
7972
7973							// Matches column count
7974							var rows = this.model.getChildCells(target, true);
7975
7976							if (rows.length > 0)
7977							{
7978								cells[i] = (clone) ? this.cloneCell(cells[i]) : cells[i];
7979								var sourceCols = this.model.getChildCells(cells[i], true);
7980								var cols = this.model.getChildCells(rows[0], true);
7981								var count = cols.length - sourceCols.length;
7982
7983								if (count > 0)
7984								{
7985									for (var j = 0; j < count; j++)
7986									{
7987										var col = this.cloneCell(sourceCols[sourceCols.length - 1]);
7988
7989										if (col != null)
7990										{
7991											col.value = '';
7992
7993											this.model.add(cells[i], col);
7994										}
7995									}
7996								}
7997								else if (count < 0)
7998								{
7999									for (var j = 0; j > count; j--)
8000									{
8001										this.model.remove(sourceCols[sourceCols.length + j - 1]);
8002									}
8003								}
8004
8005								// Updates column widths
8006								sourceCols = this.model.getChildCells(cells[i], true);
8007
8008								for (var j = 0; j < cols.length; j++)
8009								{
8010									var geo = this.getCellGeometry(cols[j]);
8011									var geo2 = this.getCellGeometry(sourceCols[j]);
8012
8013									if (geo != null && geo2 != null)
8014									{
8015										geo2 = geo2.clone();
8016										geo2.width = geo.width;
8017
8018										this.model.setGeometry(sourceCols[j], geo2);
8019									}
8020								}
8021							}
8022						}
8023					}
8024				}
8025
8026				var result = graphMoveCells.apply(this, arguments);
8027
8028				// Removes empty tables
8029				for (var i = 0; i < sourceTables.length; i++)
8030				{
8031					if (!clone && this.model.contains(sourceTables[i]) &&
8032						this.model.getChildCount(sourceTables[i]) == 0)
8033					{
8034						this.model.remove(sourceTables[i]);
8035					}
8036				}
8037
8038				if (clone)
8039				{
8040					this.updateCustomLinks(this.createCellMapping(mapping,
8041						this.createCellLookup(cells)), result);
8042				}
8043			}
8044			finally
8045			{
8046				this.model.endUpdate();
8047			}
8048
8049			return result;
8050		};
8051
8052		/**
8053		 * Overriddes to delete label for table cells.
8054		 */
8055		var graphRemoveCells = Graph.prototype.removeCells;
8056		Graph.prototype.removeCells = function(cells, includeEdges)
8057		{
8058			var result = [];
8059
8060			this.model.beginUpdate();
8061			try
8062			{
8063				// Clears labels on table cells
8064				for (var i = 0; i < cells.length; i++)
8065				{
8066					if (this.isTableCell(cells[i]))
8067					{
8068						var row = this.model.getParent(cells[i]);
8069						var table = this.model.getParent(row);
8070
8071						// Removes table if one cell in one row left
8072						if (this.model.getChildCount(row) == 1 &&
8073							this.model.getChildCount(table) == 1)
8074						{
8075							if (mxUtils.indexOf(cells, table) < 0 &&
8076								mxUtils.indexOf(result, table) < 0)
8077							{
8078								result.push(table);
8079							}
8080						}
8081						else
8082						{
8083							this.labelChanged(cells[i], '');
8084						}
8085					}
8086					else
8087					{
8088						// Deletes table if all rows are removed
8089						if (this.isTableRow(cells[i]))
8090						{
8091							var table = this.model.getParent(cells[i]);
8092
8093							if (mxUtils.indexOf(cells, table) < 0 &&
8094								mxUtils.indexOf(result, table) < 0)
8095							{
8096								var rows = this.model.getChildCells(table, true);
8097								var deleteCount = 0;
8098
8099								for (var j = 0; j < rows.length; j++)
8100								{
8101									if (mxUtils.indexOf(cells, rows[j]) >= 0)
8102									{
8103										deleteCount++;
8104									}
8105								}
8106
8107								if (deleteCount == rows.length)
8108								{
8109									result.push(table);
8110								}
8111							}
8112						}
8113
8114						result.push(cells[i]);
8115					}
8116				}
8117
8118				result = graphRemoveCells.apply(this, [result, includeEdges]);
8119			}
8120			finally
8121			{
8122				this.model.endUpdate();
8123			}
8124
8125			return result;
8126		};
8127
8128		/**
8129		 * Updates cells IDs for custom links in the given cells using an
8130		 * optional graph to avoid changing the undo history.
8131		 */
8132		Graph.prototype.updateCustomLinks = function(mapping, cells, graph)
8133		{
8134			graph = (graph != null) ? graph : new Graph();
8135
8136			for (var i = 0; i < cells.length; i++)
8137			{
8138				if (cells[i] != null)
8139				{
8140					graph.updateCustomLinksForCell(mapping, cells[i], graph);
8141				}
8142			}
8143		};
8144
8145		/**
8146		 * Updates cell IDs in custom links on the given cell and its label.
8147		 */
8148		Graph.prototype.updateCustomLinksForCell = function(mapping, cell)
8149		{
8150			this.doUpdateCustomLinksForCell(mapping, cell);
8151			var childCount = this.model.getChildCount(cell);
8152
8153			for (var i = 0; i < childCount; i++)
8154			{
8155				this.updateCustomLinksForCell(mapping,
8156					this.model.getChildAt(cell, i));
8157			}
8158		};
8159
8160		/**
8161		 * Updates cell IDs in custom links on the given cell and its label.
8162		 */
8163		 Graph.prototype.doUpdateCustomLinksForCell = function(mapping, cell)
8164		 {
8165			 // Hook for subclassers
8166		 };
8167
8168		/**
8169		 * Overrides method to provide connection constraints for shapes.
8170		 */
8171		Graph.prototype.getAllConnectionConstraints = function(terminal, source)
8172		{
8173			if (terminal != null)
8174			{
8175				var constraints = mxUtils.getValue(terminal.style, 'points', null);
8176
8177				if (constraints != null)
8178				{
8179					// Requires an array of arrays with x, y (0..1), an optional
8180					// [perimeter (0 or 1), dx, and dy] eg. points=[[0,0,1,-10,10],[0,1,0],[1,1]]
8181					var result = [];
8182
8183					try
8184					{
8185						var c = JSON.parse(constraints);
8186
8187						for (var i = 0; i < c.length; i++)
8188						{
8189							var tmp = c[i];
8190							result.push(new mxConnectionConstraint(new mxPoint(tmp[0], tmp[1]), (tmp.length > 2) ? tmp[2] != '0' : true,
8191									null, (tmp.length > 3) ? tmp[3] : 0, (tmp.length > 4) ? tmp[4] : 0));
8192						}
8193					}
8194					catch (e)
8195					{
8196						// ignore
8197					}
8198
8199					return result;
8200				}
8201				else if (terminal.shape != null && terminal.shape.bounds != null)
8202				{
8203					var dir = terminal.shape.direction;
8204					var bounds = terminal.shape.bounds;
8205					var scale = terminal.shape.scale;
8206					var w = bounds.width / scale;
8207					var h = bounds.height / scale;
8208
8209					if (dir == mxConstants.DIRECTION_NORTH || dir == mxConstants.DIRECTION_SOUTH)
8210					{
8211						var tmp = w;
8212						w = h;
8213						h = tmp;
8214					}
8215
8216					constraints = terminal.shape.getConstraints(terminal.style, w, h);
8217
8218					if (constraints != null)
8219					{
8220						return constraints;
8221					}
8222					else if (terminal.shape.stencil != null && terminal.shape.stencil.constraints != null)
8223					{
8224						return terminal.shape.stencil.constraints;
8225					}
8226					else if (terminal.shape.constraints != null)
8227					{
8228						return terminal.shape.constraints;
8229					}
8230				}
8231			}
8232
8233			return null;
8234		};
8235
8236		/**
8237		 * Inverts the elbow edge style without removing existing styles.
8238		 */
8239		Graph.prototype.flipEdge = function(edge)
8240		{
8241			if (edge != null)
8242			{
8243				var style = this.getCurrentCellStyle(edge);
8244				var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
8245					mxConstants.ELBOW_HORIZONTAL);
8246				var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
8247					mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
8248				this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
8249			}
8250		};
8251
8252		/**
8253		 * Disables drill-down for non-swimlanes.
8254		 */
8255		Graph.prototype.isValidRoot = function(cell)
8256		{
8257			// Counts non-relative children
8258			var childCount = this.model.getChildCount(cell);
8259			var realChildCount = 0;
8260
8261			for (var i = 0; i < childCount; i++)
8262			{
8263				var child = this.model.getChildAt(cell, i);
8264
8265				if (this.model.isVertex(child))
8266				{
8267					var geometry = this.getCellGeometry(child);
8268
8269					if (geometry != null && !geometry.relative)
8270					{
8271						realChildCount++;
8272					}
8273				}
8274			}
8275
8276			return realChildCount > 0 || this.isContainer(cell);
8277		};
8278
8279		/**
8280		 * Disables drill-down for non-swimlanes.
8281		 */
8282		Graph.prototype.isValidDropTarget = function(cell, cells, evt)
8283		{
8284			var style = this.getCurrentCellStyle(cell);
8285			var tables = true;
8286			var rows = true;
8287
8288			for (var i = 0; i < cells.length && rows; i++)
8289			{
8290				tables = tables && this.isTable(cells[i]);
8291				rows = rows && this.isTableRow(cells[i]);
8292			}
8293
8294			return ((mxUtils.getValue(style, 'part', '0') != '1' || this.isContainer(cell)) &&
8295				mxUtils.getValue(style, 'dropTarget', '1') != '0' &&
8296				(mxGraph.prototype.isValidDropTarget.apply(this, arguments) ||
8297				this.isContainer(cell)) && !this.isTableRow(cell) &&
8298				(!this.isTable(cell) || rows || tables)) && !this.isCellLocked(cell);
8299		};
8300
8301		/**
8302		 * Overrides createGroupCell to set the group style for new groups to 'group'.
8303		 */
8304		Graph.prototype.createGroupCell = function()
8305		{
8306			var group = mxGraph.prototype.createGroupCell.apply(this, arguments);
8307			group.setStyle('group');
8308
8309			return group;
8310		};
8311
8312		/**
8313		 * Disables extending parents with stack layouts on add
8314		 */
8315		Graph.prototype.isExtendParentsOnAdd = function(cell)
8316		{
8317			var result = mxGraph.prototype.isExtendParentsOnAdd.apply(this, arguments);
8318
8319			if (result && cell != null && this.layoutManager != null)
8320			{
8321				var parent = this.model.getParent(cell);
8322
8323				if (parent != null)
8324				{
8325					var layout = this.layoutManager.getLayout(parent);
8326
8327					if (layout != null && layout.constructor == mxStackLayout)
8328					{
8329						result = false;
8330					}
8331				}
8332			}
8333
8334			return result;
8335		};
8336
8337		/**
8338		 * Overrides autosize to add a border.
8339		 */
8340		Graph.prototype.getPreferredSizeForCell = function(cell)
8341		{
8342			var result = mxGraph.prototype.getPreferredSizeForCell.apply(this, arguments);
8343
8344			// Adds buffer
8345			if (result != null)
8346			{
8347				result.width += 10;
8348				result.height += 4;
8349
8350				if (this.gridEnabled)
8351				{
8352					result.width = this.snap(result.width);
8353					result.height = this.snap(result.height);
8354				}
8355			}
8356
8357			return result;
8358		}
8359
8360		/**
8361		 * Turns the given cells and returns the changed cells.
8362		 */
8363		Graph.prototype.turnShapes = function(cells, backwards)
8364		{
8365			var model = this.getModel();
8366			var select = [];
8367
8368			model.beginUpdate();
8369			try
8370			{
8371				for (var i = 0; i < cells.length; i++)
8372				{
8373					var cell = cells[i];
8374
8375					if (model.isEdge(cell))
8376					{
8377						var src = model.getTerminal(cell, true);
8378						var trg = model.getTerminal(cell, false);
8379
8380						model.setTerminal(cell, trg, true);
8381						model.setTerminal(cell, src, false);
8382
8383						var geo = model.getGeometry(cell);
8384
8385						if (geo != null)
8386						{
8387							geo = geo.clone();
8388
8389							if (geo.points != null)
8390							{
8391								geo.points.reverse();
8392							}
8393
8394							var sp = geo.getTerminalPoint(true);
8395							var tp = geo.getTerminalPoint(false)
8396
8397							geo.setTerminalPoint(sp, false);
8398							geo.setTerminalPoint(tp, true);
8399							model.setGeometry(cell, geo);
8400
8401							// Inverts constraints
8402							var edgeState = this.view.getState(cell);
8403							var sourceState = this.view.getState(src);
8404							var targetState = this.view.getState(trg);
8405
8406							if (edgeState != null)
8407							{
8408								var sc = (sourceState != null) ? this.getConnectionConstraint(edgeState, sourceState, true) : null;
8409								var tc = (targetState != null) ? this.getConnectionConstraint(edgeState, targetState, false) : null;
8410
8411								this.setConnectionConstraint(cell, src, true, tc);
8412								this.setConnectionConstraint(cell, trg, false, sc);
8413
8414								// Inverts perimeter spacings
8415								var temp = mxUtils.getValue(edgeState.style, mxConstants.STYLE_SOURCE_PERIMETER_SPACING);
8416								this.setCellStyles(mxConstants.STYLE_SOURCE_PERIMETER_SPACING, mxUtils.getValue(
8417									edgeState.style, mxConstants.STYLE_TARGET_PERIMETER_SPACING), [cell]);
8418								this.setCellStyles(mxConstants.STYLE_TARGET_PERIMETER_SPACING, temp, [cell]);
8419							}
8420
8421							select.push(cell);
8422						}
8423					}
8424					else if (model.isVertex(cell))
8425					{
8426						var geo = this.getCellGeometry(cell);
8427
8428						if (geo != null)
8429						{
8430							// Rotates the size and position in the geometry
8431							if (!this.isTable(cell) && !this.isTableRow(cell) &&
8432								!this.isTableCell(cell) && !this.isSwimlane(cell))
8433							{
8434								geo = geo.clone();
8435								geo.x += geo.width / 2 - geo.height / 2;
8436								geo.y += geo.height / 2 - geo.width / 2;
8437								var tmp = geo.width;
8438								geo.width = geo.height;
8439								geo.height = tmp;
8440								model.setGeometry(cell, geo);
8441							}
8442
8443							// Reads the current direction and advances by 90 degrees
8444							var state = this.view.getState(cell);
8445
8446							if (state != null)
8447							{
8448								var dirs = [mxConstants.DIRECTION_EAST, mxConstants.DIRECTION_SOUTH,
8449									mxConstants.DIRECTION_WEST, mxConstants.DIRECTION_NORTH];
8450								var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
8451									mxConstants.DIRECTION_EAST);
8452								this.setCellStyles(mxConstants.STYLE_DIRECTION,
8453									dirs[mxUtils.mod(mxUtils.indexOf(dirs, dir) +
8454									((backwards) ? -1 : 1), dirs.length)], [cell]);
8455							}
8456
8457							select.push(cell);
8458						}
8459					}
8460				}
8461			}
8462			finally
8463			{
8464				model.endUpdate();
8465			}
8466
8467			return select;
8468		};
8469
8470		/**
8471		 * Returns true if the given stencil contains any placeholder text.
8472		 */
8473		Graph.prototype.stencilHasPlaceholders = function(stencil)
8474		{
8475			if (stencil != null && stencil.fgNode != null)
8476			{
8477				var node = stencil.fgNode.firstChild;
8478
8479				while (node != null)
8480				{
8481					if (node.nodeName == 'text' && node.getAttribute('placeholders') == '1')
8482					{
8483						return true;
8484					}
8485
8486					node = node.nextSibling;
8487				}
8488			}
8489
8490			return false;
8491		};
8492
8493		/**
8494		 * Updates the child cells with placeholders if metadata of a
8495		 * cell has changed and propagates geometry changes in tables.
8496		 */
8497		var graphProcessChange = Graph.prototype.processChange;
8498		Graph.prototype.processChange = function(change)
8499		{
8500			if (change instanceof mxGeometryChange &&
8501				(this.isTableCell(change.cell) || this.isTableRow(change.cell)) &&
8502				((change.previous == null && change.geometry != null) ||
8503				(change.previous != null && !change.previous.equals(change.geometry))))
8504			{
8505				var cell = change.cell;
8506
8507				if (this.isTableCell(cell))
8508				{
8509					cell = this.model.getParent(cell);
8510				}
8511
8512				if (this.isTableRow(cell))
8513				{
8514					cell = this.model.getParent(cell);
8515				}
8516
8517				// Forces repaint of table with unchanged style and geometry
8518				var state = this.view.getState(cell);
8519
8520				if (state != null && state.shape != null)
8521				{
8522					this.view.invalidate(cell);
8523					state.shape.bounds = null;
8524				}
8525			}
8526
8527			graphProcessChange.apply(this, arguments);
8528
8529			if (change instanceof mxValueChange && change.cell != null &&
8530				change.cell.value != null && typeof(change.cell.value) == 'object')
8531			{
8532				this.invalidateDescendantsWithPlaceholders(change.cell);
8533			}
8534		};
8535
8536		/**
8537		 * Replaces the given element with a span.
8538		 */
8539		Graph.prototype.invalidateDescendantsWithPlaceholders = function(cell)
8540		{
8541			// Invalidates all descendants with placeholders
8542			var desc = this.model.getDescendants(cell);
8543
8544			// LATER: Check if only label or tooltip have changed
8545			if (desc.length > 0)
8546			{
8547				for (var i = 0; i < desc.length; i++)
8548				{
8549					var state = this.view.getState(desc[i]);
8550
8551					if (state != null && state.shape != null && state.shape.stencil != null &&
8552						this.stencilHasPlaceholders(state.shape.stencil))
8553					{
8554						this.removeStateForCell(desc[i]);
8555					}
8556					else if (this.isReplacePlaceholders(desc[i]))
8557					{
8558						this.view.invalidate(desc[i], false, false);
8559					}
8560				}
8561			}
8562		};
8563
8564		/**
8565		 * Replaces the given element with a span.
8566		 */
8567		Graph.prototype.replaceElement = function(elt, tagName)
8568		{
8569			var span = elt.ownerDocument.createElement((tagName != null) ? tagName : 'span');
8570			var attributes = Array.prototype.slice.call(elt.attributes);
8571
8572			while (attr = attributes.pop())
8573			{
8574				span.setAttribute(attr.nodeName, attr.nodeValue);
8575			}
8576
8577			span.innerHTML = elt.innerHTML;
8578			elt.parentNode.replaceChild(span, elt);
8579		};
8580
8581		/**
8582		 *
8583		 */
8584		Graph.prototype.processElements = function(elt, fn)
8585		{
8586			if (elt != null)
8587			{
8588				var elts = elt.getElementsByTagName('*');
8589
8590				for (var i = 0; i < elts.length; i++)
8591				{
8592					fn(elts[i]);
8593				}
8594			}
8595		};
8596
8597		/**
8598		 * Handles label changes for XML user objects.
8599		 */
8600		Graph.prototype.updateLabelElements = function(cells, fn, tagName)
8601		{
8602			cells = (cells != null) ? cells : this.getSelectionCells();
8603			var div = document.createElement('div');
8604
8605			for (var i = 0; i < cells.length; i++)
8606			{
8607				// Changes font tags inside HTML labels
8608				if (this.isHtmlLabel(cells[i]))
8609				{
8610					var label = this.convertValueToString(cells[i]);
8611
8612					if (label != null && label.length > 0)
8613					{
8614						div.innerHTML = label;
8615						var elts = div.getElementsByTagName((tagName != null) ? tagName : '*');
8616
8617						for (var j = 0; j < elts.length; j++)
8618						{
8619							fn(elts[j]);
8620						}
8621
8622						if (div.innerHTML != label)
8623						{
8624							this.cellLabelChanged(cells[i], div.innerHTML);
8625						}
8626					}
8627				}
8628			}
8629		};
8630
8631		/**
8632		 * Handles label changes for XML user objects.
8633		 */
8634		Graph.prototype.cellLabelChanged = function(cell, value, autoSize)
8635		{
8636			// Removes all illegal control characters in user input
8637			value = Graph.zapGremlins(value);
8638
8639			this.model.beginUpdate();
8640			try
8641			{
8642				if (cell.value != null && typeof cell.value == 'object')
8643				{
8644					if (this.isReplacePlaceholders(cell) &&
8645						cell.getAttribute('placeholder') != null)
8646					{
8647						// LATER: Handle delete, name change
8648						var name = cell.getAttribute('placeholder');
8649						var current = cell;
8650
8651						while (current != null)
8652						{
8653							if (current == this.model.getRoot() || (current.value != null &&
8654								typeof(current.value) == 'object' && current.hasAttribute(name)))
8655							{
8656								this.setAttributeForCell(current, name, value);
8657
8658								break;
8659							}
8660
8661							current = this.model.getParent(current);
8662						}
8663					}
8664
8665					var tmp = cell.value.cloneNode(true);
8666
8667					if (Graph.translateDiagram && Graph.diagramLanguage != null &&
8668						tmp.hasAttribute('label_' + Graph.diagramLanguage))
8669					{
8670						tmp.setAttribute('label_' + Graph.diagramLanguage, value);
8671					}
8672					else
8673					{
8674						tmp.setAttribute('label', value);
8675					}
8676
8677					value = tmp;
8678				}
8679
8680				mxGraph.prototype.cellLabelChanged.apply(this, arguments);
8681			}
8682			finally
8683			{
8684				this.model.endUpdate();
8685			}
8686		};
8687
8688		/**
8689		 * Removes transparent empty groups if all children are removed.
8690		 */
8691		Graph.prototype.cellsRemoved = function(cells)
8692		{
8693			if (cells != null)
8694			{
8695				var dict = new mxDictionary();
8696
8697				for (var i = 0; i < cells.length; i++)
8698				{
8699					dict.put(cells[i], true);
8700				}
8701
8702				// LATER: Recurse up the cell hierarchy
8703				var parents = [];
8704
8705				for (var i = 0; i < cells.length; i++)
8706				{
8707					var parent = this.model.getParent(cells[i]);
8708
8709					if (parent != null && !dict.get(parent))
8710					{
8711						dict.put(parent, true);
8712						parents.push(parent);
8713					}
8714				}
8715
8716				for (var i = 0; i < parents.length; i++)
8717				{
8718					var state = this.view.getState(parents[i]);
8719
8720					if (state != null && (this.model.isEdge(state.cell) ||
8721						this.model.isVertex(state.cell)) &&
8722						this.isCellDeletable(state.cell) &&
8723						this.isTransparentState(state))
8724					{
8725						var allChildren = true;
8726
8727						for (var j = 0; j < this.model.getChildCount(state.cell) && allChildren; j++)
8728						{
8729							if (!dict.get(this.model.getChildAt(state.cell, j)))
8730							{
8731								allChildren = false;
8732							}
8733						}
8734
8735						if (allChildren)
8736						{
8737							cells.push(state.cell);
8738						}
8739					}
8740				}
8741			}
8742
8743			mxGraph.prototype.cellsRemoved.apply(this, arguments);
8744		};
8745
8746		/**
8747		 * Overrides ungroup to check if group should be removed.
8748		 */
8749		Graph.prototype.removeCellsAfterUngroup = function(cells)
8750		{
8751			var cellsToRemove = [];
8752
8753			for (var i = 0; i < cells.length; i++)
8754			{
8755				if (this.isCellDeletable(cells[i]) &&
8756					this.isTransparentState(
8757						this.view.getState(cells[i])))
8758				{
8759					cellsToRemove.push(cells[i]);
8760				}
8761			}
8762
8763			cells = cellsToRemove;
8764
8765			mxGraph.prototype.removeCellsAfterUngroup.apply(this, arguments);
8766		};
8767
8768		/**
8769		 * Sets the link for the given cell.
8770		 */
8771		Graph.prototype.setLinkForCell = function(cell, link)
8772		{
8773			this.setAttributeForCell(cell, 'link', link);
8774		};
8775
8776		/**
8777		 * Sets the link for the given cell.
8778		 */
8779		Graph.prototype.setTooltipForCell = function(cell, link)
8780		{
8781			var key = 'tooltip';
8782
8783			if (Graph.translateDiagram && Graph.diagramLanguage != null &&
8784				mxUtils.isNode(cell.value) && cell.value.hasAttribute('tooltip_' + Graph.diagramLanguage))
8785			{
8786				key = 'tooltip_' + Graph.diagramLanguage;
8787			}
8788
8789			this.setAttributeForCell(cell, key, link);
8790		};
8791
8792		/**
8793		 * Returns the cells in the model (or given array) that have all of the
8794		 * given tags in their tags property.
8795		 */
8796		Graph.prototype.getAttributeForCell = function(cell, attributeName, defaultValue)
8797		{
8798			var value = (cell.value != null && typeof cell.value === 'object') ?
8799				cell.value.getAttribute(attributeName) : null;
8800
8801			return (value != null) ? value : defaultValue;
8802		};
8803
8804		/**
8805		 * Sets the link for the given cell.
8806		 */
8807		Graph.prototype.setAttributeForCell = function(cell, attributeName, attributeValue)
8808		{
8809			var value = null;
8810
8811			if (cell.value != null && typeof(cell.value) == 'object')
8812			{
8813				value = cell.value.cloneNode(true);
8814			}
8815			else
8816			{
8817				var doc = mxUtils.createXmlDocument();
8818
8819				value = doc.createElement('UserObject');
8820				value.setAttribute('label', cell.value || '');
8821			}
8822
8823			if (attributeValue != null)
8824			{
8825				value.setAttribute(attributeName, attributeValue);
8826			}
8827			else
8828			{
8829				value.removeAttribute(attributeName);
8830			}
8831
8832			this.model.setValue(cell, value);
8833		};
8834
8835		/**
8836		 * Overridden to stop moving edge labels between cells.
8837		 */
8838		var graphGetDropTarget = Graph.prototype.getDropTarget;
8839		Graph.prototype.getDropTarget = function(cells, evt, cell, clone)
8840		{
8841			var model = this.getModel();
8842
8843			// Disables drop into group if alt is pressed
8844			if (mxEvent.isAltDown(evt))
8845			{
8846				return null;
8847			}
8848
8849			// Disables dragging edge labels out of edges
8850			for (var i = 0; i < cells.length; i++)
8851			{
8852				var parent = this.model.getParent(cells[i]);
8853
8854				if (this.model.isEdge(parent) && mxUtils.indexOf(cells, parent) < 0)
8855				{
8856					return null;
8857				}
8858			}
8859
8860			var target = graphGetDropTarget.apply(this, arguments);
8861
8862			// Always drops rows to tables
8863			var rows = true;
8864
8865			for (var i = 0; i < cells.length && rows; i++)
8866			{
8867				rows = rows && this.isTableRow(cells[i]);
8868			}
8869
8870			if (rows)
8871			{
8872				if (this.isTableCell(target))
8873				{
8874					target = this.model.getParent(target);
8875				}
8876
8877				if (this.isTableRow(target))
8878				{
8879					target = this.model.getParent(target);
8880				}
8881
8882				if (!this.isTable(target))
8883				{
8884					target = null;
8885				}
8886			}
8887
8888			return target;
8889		};
8890
8891		/**
8892		 * Overrides double click handling to avoid accidental inserts of new labels in dblClick below.
8893		 */
8894		Graph.prototype.click = function(me)
8895		{
8896			mxGraph.prototype.click.call(this, me);
8897
8898			// Stores state and source for checking in dblClick
8899			this.firstClickState = me.getState();
8900			this.firstClickSource = me.getSource();
8901		};
8902
8903		/**
8904		 * Overrides double click handling to add the tolerance and inserting text.
8905		 */
8906		Graph.prototype.dblClick = function(evt, cell)
8907		{
8908			if (this.isEnabled())
8909			{
8910				cell = this.insertTextForEvent(evt, cell);
8911				mxGraph.prototype.dblClick.call(this, evt, cell);
8912			}
8913		};
8914
8915		/**
8916		 * Overrides double click handling to add the tolerance and inserting text.
8917		 */
8918		Graph.prototype.insertTextForEvent = function(evt, cell)
8919		{
8920			var pt = mxUtils.convertPoint(this.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
8921
8922			// Automatically adds new child cells to edges on double click
8923			if (evt != null && !this.model.isVertex(cell))
8924			{
8925				var state = (this.model.isEdge(cell)) ? this.view.getState(cell) : null;
8926				var src = mxEvent.getSource(evt);
8927
8928				if ((this.firstClickState == state && this.firstClickSource == src) &&
8929					(state == null || (state.text == null || state.text.node == null ||
8930					state.text.boundingBox == null || (!mxUtils.contains(state.text.boundingBox,
8931					pt.x, pt.y) && !mxUtils.isAncestorNode(state.text.node, mxEvent.getSource(evt))))) &&
8932					((state == null && !this.isCellLocked(this.getDefaultParent())) ||
8933					(state != null && !this.isCellLocked(state.cell))) &&
8934					(state != null ||
8935					(mxClient.IS_SVG && src == this.view.getCanvas().ownerSVGElement)))
8936				{
8937					if (state == null)
8938					{
8939						state = this.view.getState(this.getCellAt(pt.x, pt.y));
8940					}
8941
8942					cell = this.addText(pt.x, pt.y, state);
8943				}
8944			}
8945
8946			return cell;
8947		};
8948
8949		/**
8950		 * Returns a point that specifies the location for inserting cells.
8951		 */
8952		Graph.prototype.getInsertPoint = function()
8953		{
8954			var gs = this.getGridSize();
8955			var dx = this.container.scrollLeft / this.view.scale - this.view.translate.x;
8956			var dy = this.container.scrollTop / this.view.scale - this.view.translate.y;
8957
8958			if (this.pageVisible)
8959			{
8960				var layout = this.getPageLayout();
8961				var page = this.getPageSize();
8962				dx = Math.max(dx, layout.x * page.width);
8963				dy = Math.max(dy, layout.y * page.height);
8964			}
8965
8966			return new mxPoint(this.snap(dx + gs), this.snap(dy + gs));
8967		};
8968
8969		/**
8970		 *
8971		 */
8972		Graph.prototype.getFreeInsertPoint = function()
8973		{
8974			var view = this.view;
8975			var bds = this.getGraphBounds();
8976			var pt = this.getInsertPoint();
8977
8978			// Places at same x-coord and 2 grid sizes below existing graph
8979			var x = this.snap(Math.round(Math.max(pt.x, bds.x / view.scale - view.translate.x +
8980				((bds.width == 0) ? 2 * this.gridSize : 0))));
8981			var y = this.snap(Math.round(Math.max(pt.y, (bds.y + bds.height) / view.scale - view.translate.y +
8982				2 * this.gridSize)));
8983
8984			return new mxPoint(x, y);
8985		};
8986
8987		/**
8988		 *
8989		 */
8990		Graph.prototype.getCenterInsertPoint = function(bbox)
8991		{
8992			bbox = (bbox != null) ? bbox : new mxRectangle();
8993
8994			if (mxUtils.hasScrollbars(this.container))
8995			{
8996				return new mxPoint(
8997					this.snap(Math.round((this.container.scrollLeft + this.container.clientWidth / 2) /
8998						this.view.scale - this.view.translate.x - bbox.width / 2)),
8999					this.snap(Math.round((this.container.scrollTop + this.container.clientHeight / 2) /
9000						this.view.scale - this.view.translate.y - bbox.height / 2)));
9001			}
9002			else
9003			{
9004				return new mxPoint(
9005					this.snap(Math.round(this.container.clientWidth / 2 / this.view.scale -
9006						this.view.translate.x - bbox.width / 2)),
9007					this.snap(Math.round(this.container.clientHeight / 2 / this.view.scale -
9008						this.view.translate.y - bbox.height / 2)));
9009			}
9010		};
9011
9012		/**
9013		 * Hook for subclassers to return true if the current insert point was defined
9014		 * using a mouse hover event.
9015		 */
9016		Graph.prototype.isMouseInsertPoint = function()
9017		{
9018			return false;
9019		};
9020
9021		/**
9022		 * Adds a new label at the given position and returns the new cell. State is
9023		 * an optional edge state to be used as the parent for the label. Vertices
9024		 * are not allowed currently as states.
9025		 */
9026		Graph.prototype.addText = function(x, y, state)
9027		{
9028			// Creates a new edge label with a predefined text
9029			var label = new mxCell();
9030			label.value = 'Text';
9031			label.geometry = new mxGeometry(0, 0, 0, 0);
9032			label.vertex = true;
9033			var style = 'html=1;align=center;verticalAlign=middle;resizable=0;points=[];';
9034
9035			if (state != null && this.model.isEdge(state.cell))
9036			{
9037				label.style = 'edgeLabel;' + style;
9038				label.geometry.relative = true;
9039				label.connectable = false;
9040
9041				// Resets the relative location stored inside the geometry
9042				var pt2 = this.view.getRelativePoint(state, x, y);
9043				label.geometry.x = Math.round(pt2.x * 10000) / 10000;
9044				label.geometry.y = Math.round(pt2.y);
9045
9046		    	// Resets the offset inside the geometry to find the offset from the resulting point
9047				label.geometry.offset = new mxPoint(0, 0);
9048				pt2 = this.view.getPoint(state, label.geometry);
9049
9050				var scale = this.view.scale;
9051				label.geometry.offset = new mxPoint(Math.round((x - pt2.x) / scale), Math.round((y - pt2.y) / scale));
9052			}
9053			else
9054			{
9055				var tr = this.view.translate;
9056				label.style = 'text;' + style;
9057				label.geometry.width = 40;
9058				label.geometry.height = 20;
9059				label.geometry.x = Math.round(x / this.view.scale) -
9060					tr.x - ((state != null) ? state.origin.x : 0);
9061				label.geometry.y = Math.round(y / this.view.scale) -
9062					tr.y - ((state != null) ? state.origin.y : 0);
9063				label.style += 'autosize=1;'
9064			}
9065
9066			this.getModel().beginUpdate();
9067			try
9068			{
9069				this.addCells([label], (state != null) ? state.cell : null);
9070				this.fireEvent(new mxEventObject('textInserted', 'cells', [label]));
9071
9072		    	// Updates size of text after possible change of style via event
9073				this.autoSizeCell(label);
9074			}
9075			finally
9076			{
9077				this.getModel().endUpdate();
9078			}
9079
9080			return label;
9081		};
9082
9083		/**
9084		 * Adds a handler for clicking on shapes with links. This replaces all links in labels.
9085		 */
9086		Graph.prototype.addClickHandler = function(highlight, beforeClick, onClick)
9087		{
9088			// Replaces links in labels for consistent right-clicks
9089			var checkLinks = mxUtils.bind(this, function()
9090			{
9091				var links = this.container.getElementsByTagName('a');
9092
9093				if (links != null)
9094				{
9095					for (var i = 0; i < links.length; i++)
9096					{
9097						var href = this.getAbsoluteUrl(links[i].getAttribute('href'));
9098
9099						if (href != null)
9100						{
9101							links[i].setAttribute('rel', this.linkRelation);
9102							links[i].setAttribute('href', href);
9103
9104							if (beforeClick != null)
9105			    			{
9106								mxEvent.addGestureListeners(links[i], null, null, beforeClick);
9107			    			}
9108						}
9109					}
9110				}
9111			});
9112
9113			this.model.addListener(mxEvent.CHANGE, checkLinks);
9114			checkLinks();
9115
9116			var cursor = this.container.style.cursor;
9117			var tol = this.getTolerance();
9118			var graph = this;
9119
9120			var mouseListener =
9121			{
9122			    currentState: null,
9123			    currentLink: null,
9124				currentTarget: null,
9125			    highlight: (highlight != null && highlight != '' && highlight != mxConstants.NONE) ?
9126			    	new mxCellHighlight(graph, highlight, 4) : null,
9127			    startX: 0,
9128			    startY: 0,
9129			    scrollLeft: 0,
9130			    scrollTop: 0,
9131			    updateCurrentState: function(me)
9132			    {
9133			    	var tmp = me.sourceState;
9134
9135			    	// Gets first intersecting ancestor with link
9136			    	if (tmp == null || graph.getLinkForCell(tmp.cell) == null)
9137			    	{
9138			    		var cell = graph.getCellAt(me.getGraphX(), me.getGraphY(), null, null, null, function(state, x, y)
9139	    				{
9140			    			return graph.getLinkForCell(state.cell) == null;
9141	    				});
9142
9143			    		tmp = (tmp != null && !graph.model.isAncestor(cell, tmp.cell)) ? null : graph.view.getState(cell);
9144			    	}
9145
9146			      	if (tmp != this.currentState)
9147			      	{
9148			        	if (this.currentState != null)
9149			        	{
9150				          	this.clear();
9151			        	}
9152
9153			        	this.currentState = tmp;
9154
9155			        	if (this.currentState != null)
9156			        	{
9157				          	this.activate(this.currentState);
9158			        	}
9159			      	}
9160			    },
9161			    mouseDown: function(sender, me)
9162			    {
9163			    	this.startX = me.getGraphX();
9164			    	this.startY = me.getGraphY();
9165				    this.scrollLeft = graph.container.scrollLeft;
9166				    this.scrollTop = graph.container.scrollTop;
9167
9168		    		if (this.currentLink == null && graph.container.style.overflow == 'auto')
9169		    		{
9170		    			graph.container.style.cursor = 'move';
9171		    		}
9172
9173		    		this.updateCurrentState(me);
9174			    },
9175			    mouseMove: function(sender, me)
9176			    {
9177			    	if (graph.isMouseDown)
9178			    	{
9179			    		if (this.currentLink != null)
9180			    		{
9181					    	var dx = Math.abs(this.startX - me.getGraphX());
9182					    	var dy = Math.abs(this.startY - me.getGraphY());
9183
9184					    	if (dx > tol || dy > tol)
9185					    	{
9186					    		this.clear();
9187					    	}
9188			    		}
9189			    	}
9190			    	else
9191			    	{
9192				    	// Checks for parent link
9193				    	var linkNode = me.getSource();
9194
9195				    	while (linkNode != null && linkNode.nodeName.toLowerCase() != 'a')
9196				    	{
9197				    		linkNode = linkNode.parentNode;
9198				    	}
9199
9200			    		if (linkNode != null)
9201			    		{
9202			    			this.clear();
9203			    		}
9204			    		else
9205			    		{
9206				    		if (graph.tooltipHandler != null && this.currentLink != null && this.currentState != null)
9207				    		{
9208				    			graph.tooltipHandler.reset(me, true, this.currentState);
9209				    		}
9210
9211					    	if (this.currentState != null && (me.getState() == this.currentState || me.sourceState == null) &&
9212					    		graph.intersects(this.currentState, me.getGraphX(), me.getGraphY()))
9213					    	{
9214				    			return;
9215					    	}
9216
9217					    	this.updateCurrentState(me);
9218			    		}
9219			    	}
9220			    },
9221			    mouseUp: function(sender, me)
9222			    {
9223			    	var source = me.getSource();
9224			    	var evt = me.getEvent();
9225
9226			    	// Checks for parent link
9227			    	var linkNode = source;
9228
9229			    	while (linkNode != null && linkNode.nodeName.toLowerCase() != 'a')
9230			    	{
9231			    		linkNode = linkNode.parentNode;
9232			    	}
9233
9234			    	// Ignores clicks on links and collapse/expand icon
9235			    	if (linkNode == null &&
9236			    		(((Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
9237			        	Math.abs(this.scrollTop - graph.container.scrollTop) < tol) &&
9238			    		(me.sourceState == null || !me.isSource(me.sourceState.control))) &&
9239			    		(((mxEvent.isLeftMouseButton(evt) || mxEvent.isMiddleMouseButton(evt)) &&
9240			    		!mxEvent.isPopupTrigger(evt)) || mxEvent.isTouchEvent(evt))))
9241			    	{
9242				    	if (this.currentLink != null)
9243				    	{
9244				    		var blank = graph.isBlankLink(this.currentLink);
9245
9246				    		if ((this.currentLink.substring(0, 5) === 'data:' ||
9247				    			!blank) && beforeClick != null)
9248				    		{
9249			    				beforeClick(evt, this.currentLink);
9250				    		}
9251
9252				    		if (!mxEvent.isConsumed(evt))
9253				    		{
9254					    		var target = (this.currentTarget != null) ?
9255									this.currentTarget : ((mxEvent.isMiddleMouseButton(evt)) ? '_blank' :
9256					    			((blank) ? graph.linkTarget : '_top'));
9257
9258					    		graph.openLink(this.currentLink, target);
9259					    		me.consume();
9260				    		}
9261				    	}
9262				    	else if (onClick != null && !me.isConsumed() &&
9263			    			(Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
9264			        		Math.abs(this.scrollTop - graph.container.scrollTop) < tol) &&
9265			        		(Math.abs(this.startX - me.getGraphX()) < tol &&
9266			        		Math.abs(this.startY - me.getGraphY()) < tol))
9267			        	{
9268				    		onClick(me.getEvent());
9269			    		}
9270			    	}
9271
9272			    	this.clear();
9273			    },
9274			    activate: function(state)
9275			    {
9276			    	this.currentLink = graph.getAbsoluteUrl(graph.getLinkForCell(state.cell));
9277
9278			    	if (this.currentLink != null)
9279			    	{
9280						this.currentTarget = graph.getLinkTargetForCell(state.cell)
9281			    		graph.container.style.cursor = 'pointer';
9282
9283			    		if (this.highlight != null)
9284			    		{
9285			    			this.highlight.highlight(state);
9286			    		}
9287				    }
9288			    },
9289			    clear: function()
9290			    {
9291			    	if (graph.container != null)
9292			    	{
9293			    		graph.container.style.cursor = cursor;
9294			    	}
9295
9296					this.currentTarget = null;
9297			    	this.currentState = null;
9298			    	this.currentLink = null;
9299
9300			    	if (this.highlight != null)
9301			    	{
9302			    		this.highlight.hide();
9303			    	}
9304
9305			    	if (graph.tooltipHandler != null)
9306		    		{
9307		    			graph.tooltipHandler.hide();
9308		    		}
9309			    }
9310			};
9311
9312			// Ignores built-in click handling
9313			graph.click = function(me) {};
9314			graph.addMouseListener(mouseListener);
9315
9316			mxEvent.addListener(document, 'mouseleave', function(evt)
9317			{
9318				mouseListener.clear();
9319			});
9320		};
9321
9322		/**
9323		 * Duplicates the given cells and returns the duplicates.
9324		 */
9325		Graph.prototype.duplicateCells = function(cells, append)
9326		{
9327			cells = (cells != null) ? cells : this.getSelectionCells();
9328			append = (append != null) ? append : true;
9329
9330			// Duplicates rows for table cells
9331			for (var i = 0; i < cells.length; i++)
9332			{
9333				if (this.isTableCell(cells[i]))
9334				{
9335					cells[i] = this.model.getParent(cells[i]);
9336				}
9337			}
9338
9339			cells = this.model.getTopmostCells(cells);
9340
9341			var model = this.getModel();
9342			var s = this.gridSize;
9343			var select = [];
9344
9345			model.beginUpdate();
9346			try
9347			{
9348
9349				var cloneMap = new Object();
9350				var lookup = this.createCellLookup(cells);
9351				var clones = this.cloneCells(cells, false, cloneMap, true);
9352
9353				for (var i = 0; i < cells.length; i++)
9354				{
9355					var parent = model.getParent(cells[i]);
9356
9357					if (parent != null)
9358					{
9359						var child = this.moveCells([clones[i]], s, s, false)[0];
9360						select.push(child);
9361
9362						if (append)
9363						{
9364							model.add(parent, clones[i]);
9365						}
9366						else
9367						{
9368							// Maintains child index by inserting after clone in parent
9369							var index = parent.getIndex(cells[i]);
9370							model.add(parent, clones[i], index + 1);
9371						}
9372
9373						// Extends tables
9374						if (this.isTable(parent))
9375						{
9376							var row = this.getCellGeometry(clones[i]);
9377							var table = this.getCellGeometry(parent);
9378
9379							if (row != null && table != null)
9380							{
9381								table = table.clone();
9382								table.height += row.height;
9383								model.setGeometry(parent, table);
9384							}
9385						}
9386					}
9387					else
9388					{
9389						select.push(clones[i]);
9390					}
9391				}
9392
9393				// Updates custom links after inserting into the model for cells to have new IDs
9394				this.updateCustomLinks(this.createCellMapping(cloneMap, lookup), clones, this);
9395				this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', clones));
9396			}
9397			finally
9398			{
9399				model.endUpdate();
9400			}
9401
9402			return select;
9403		};
9404
9405		/**
9406		 * Inserts the given image at the cursor in a content editable text box using
9407		 * the insertimage command on the document instance.
9408		 */
9409		Graph.prototype.insertImage = function(newValue, w, h)
9410		{
9411			// To find the new image, we create a list of all existing links first
9412			if (newValue != null && this.cellEditor.textarea != null)
9413			{
9414				var tmp = this.cellEditor.textarea.getElementsByTagName('img');
9415				var oldImages = [];
9416
9417				for (var i = 0; i < tmp.length; i++)
9418				{
9419					oldImages.push(tmp[i]);
9420				}
9421
9422				// LATER: Fix inserting link/image in IE8/quirks after focus lost
9423				document.execCommand('insertimage', false, newValue);
9424
9425				// Sets size of new image
9426				var newImages = this.cellEditor.textarea.getElementsByTagName('img');
9427
9428				if (newImages.length == oldImages.length + 1)
9429				{
9430					// Inverse order in favor of appended images
9431					for (var i = newImages.length - 1; i >= 0; i--)
9432					{
9433						if (i == 0 || newImages[i] != oldImages[i - 1])
9434						{
9435							// Workaround for lost styles during undo and redo is using attributes
9436							newImages[i].setAttribute('width', w);
9437							newImages[i].setAttribute('height', h);
9438
9439							break;
9440						}
9441					}
9442				}
9443			}
9444		};
9445
9446		/**
9447		 * Inserts the given image at the cursor in a content editable text box using
9448		 * the insertimage command on the document instance.
9449		 */
9450		Graph.prototype.insertLink = function(value)
9451		{
9452			if (this.cellEditor.textarea != null)
9453			{
9454				if (value.length == 0)
9455				{
9456					document.execCommand('unlink', false);
9457				}
9458				else if (mxClient.IS_FF)
9459				{
9460					// Workaround for Firefox that adds a new link and removes
9461					// the href from the inner link if its parent is a span is
9462					// to remove all inner links inside the new outer link
9463					var tmp = this.cellEditor.textarea.getElementsByTagName('a');
9464					var oldLinks = [];
9465
9466					for (var i = 0; i < tmp.length; i++)
9467					{
9468						oldLinks.push(tmp[i]);
9469					}
9470
9471					document.execCommand('createlink', false, mxUtils.trim(value));
9472
9473					// Finds the new link element
9474					var newLinks = this.cellEditor.textarea.getElementsByTagName('a');
9475
9476					if (newLinks.length == oldLinks.length + 1)
9477					{
9478						// Inverse order in favor of appended links
9479						for (var i = newLinks.length - 1; i >= 0; i--)
9480						{
9481							if (newLinks[i] != oldLinks[i - 1])
9482							{
9483								// Removes all inner links from the new link and
9484								// moves the children to the inner link parent
9485								var tmp = newLinks[i].getElementsByTagName('a');
9486
9487								while (tmp.length > 0)
9488								{
9489									var parent = tmp[0].parentNode;
9490
9491									while (tmp[0].firstChild != null)
9492									{
9493										parent.insertBefore(tmp[0].firstChild, tmp[0]);
9494									}
9495
9496									parent.removeChild(tmp[0]);
9497								}
9498
9499								break;
9500							}
9501						}
9502					}
9503				}
9504				else
9505				{
9506					// LATER: Fix inserting link/image in IE8/quirks after focus lost
9507					document.execCommand('createlink', false, mxUtils.trim(value));
9508				}
9509			}
9510		};
9511
9512		/**
9513		 *
9514		 * @param cell
9515		 * @returns {Boolean}
9516		 */
9517		Graph.prototype.isCellResizable = function(cell)
9518		{
9519			var result = mxGraph.prototype.isCellResizable.apply(this, arguments);
9520			var style = this.getCurrentCellStyle(cell);
9521
9522			return !this.isTableCell(cell) && !this.isTableRow(cell) && (result ||
9523				(mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0' &&
9524				style[mxConstants.STYLE_WHITE_SPACE] == 'wrap'));
9525		};
9526
9527		/**
9528		 * Function: distributeCells
9529		 *
9530		 * Distribuets the centers of the given cells equally along the available
9531		 * horizontal or vertical space.
9532		 *
9533		 * Parameters:
9534		 *
9535		 * horizontal - Boolean that specifies the direction of the distribution.
9536		 * cells - Optional array of <mxCells> to be distributed. Edges are ignored.
9537		 */
9538		Graph.prototype.distributeCells = function(horizontal, cells)
9539		{
9540			if (cells == null)
9541			{
9542				cells = this.getSelectionCells();
9543			}
9544
9545			if (cells != null && cells.length > 1)
9546			{
9547				var vertices = [];
9548				var max = null;
9549				var min = null;
9550
9551				for (var i = 0; i < cells.length; i++)
9552				{
9553					if (this.getModel().isVertex(cells[i]))
9554					{
9555						var state = this.view.getState(cells[i]);
9556
9557						if (state != null)
9558						{
9559							var tmp = (horizontal) ? state.getCenterX() : state.getCenterY();
9560							max = (max != null) ? Math.max(max, tmp) : tmp;
9561							min = (min != null) ? Math.min(min, tmp) : tmp;
9562
9563							vertices.push(state);
9564						}
9565					}
9566				}
9567
9568				if (vertices.length > 2)
9569				{
9570					vertices.sort(function(a, b)
9571					{
9572						return (horizontal) ? a.x - b.x : a.y - b.y;
9573					});
9574
9575					var t = this.view.translate;
9576					var s = this.view.scale;
9577
9578					min = min / s - ((horizontal) ? t.x : t.y);
9579					max = max / s - ((horizontal) ? t.x : t.y);
9580
9581					this.getModel().beginUpdate();
9582					try
9583					{
9584						var dt = (max - min) / (vertices.length - 1);
9585						var t0 = min;
9586
9587						for (var i = 1; i < vertices.length - 1; i++)
9588						{
9589							var pstate = this.view.getState(this.model.getParent(vertices[i].cell));
9590							var geo = this.getCellGeometry(vertices[i].cell);
9591							t0 += dt;
9592
9593							if (geo != null && pstate != null)
9594							{
9595								geo = geo.clone();
9596
9597								if (horizontal)
9598								{
9599									geo.x = Math.round(t0 - geo.width / 2) - pstate.origin.x;
9600								}
9601								else
9602								{
9603									geo.y = Math.round(t0 - geo.height / 2) - pstate.origin.y;
9604								}
9605
9606								this.getModel().setGeometry(vertices[i].cell, geo);
9607							}
9608						}
9609					}
9610					finally
9611					{
9612						this.getModel().endUpdate();
9613					}
9614				}
9615			}
9616
9617			return cells;
9618		};
9619
9620		/**
9621		 * Adds meta-drag an Mac.
9622		 * @param evt
9623		 * @returns
9624		 */
9625		Graph.prototype.isCloneEvent = function(evt)
9626		{
9627			return (mxClient.IS_MAC && mxEvent.isMetaDown(evt)) || mxEvent.isControlDown(evt);
9628		};
9629
9630		/**
9631		 * Translates this point by the given vector.
9632		 *
9633		 * @param {number} dx X-coordinate of the translation.
9634		 * @param {number} dy Y-coordinate of the translation.
9635		 */
9636		Graph.prototype.createSvgImageExport = function()
9637		{
9638			var exp = new mxImageExport();
9639
9640			// Adds hyperlinks (experimental)
9641			exp.getLinkForCellState = mxUtils.bind(this, function(state, canvas)
9642			{
9643				return this.getLinkForCell(state.cell);
9644			});
9645
9646			return exp;
9647		};
9648
9649		/**
9650		 * Parses the given background image.
9651		 */
9652		Graph.prototype.parseBackgroundImage = function(json)
9653		{
9654			var result = null;
9655
9656			if (json != null && json.length > 0)
9657			{
9658				var obj = JSON.parse(json);
9659				result = new mxImage(obj.src, obj.width, obj.height)
9660			}
9661
9662			return result;
9663		};
9664
9665		/**
9666		 * Parses the given background image.
9667		 */
9668		Graph.prototype.getBackgroundImageObject = function(obj)
9669		{
9670			return obj;
9671		};
9672
9673		/**
9674		 * Translates this point by the given vector.
9675		 *
9676		 * @param {number} dx X-coordinate of the translation.
9677		 * @param {number} dy Y-coordinate of the translation.
9678		 */
9679		Graph.prototype.getSvg = function(background, scale, border, nocrop, crisp,
9680			ignoreSelection, showText, imgExport, linkTarget, hasShadow, incExtFonts,
9681			keepTheme, exportType, cells)
9682		{
9683			var lookup = null;
9684
9685			if (cells != null)
9686			{
9687				lookup = new mxDictionary();
9688
9689				for (var i = 0; i < cells.length; i++)
9690		    	{
9691		    		lookup.put(cells[i], true);
9692		        }
9693			}
9694
9695			//Disable Css Transforms if it is used
9696			var origUseCssTrans = this.useCssTransforms;
9697
9698			if (origUseCssTrans)
9699			{
9700				this.useCssTransforms = false;
9701				this.view.revalidate();
9702				this.sizeDidChange();
9703			}
9704
9705			try
9706			{
9707				scale = (scale != null) ? scale : 1;
9708				border = (border != null) ? border : 0;
9709				crisp = (crisp != null) ? crisp : true;
9710				ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
9711				showText = (showText != null) ? showText : true;
9712				hasShadow = (hasShadow != null) ? hasShadow : false;
9713
9714				var bounds = (exportType == 'page') ? this.view.getBackgroundPageBounds() :
9715					(((ignoreSelection && lookup == null) || nocrop ||
9716					exportType == 'diagram') ? this.getGraphBounds() :
9717					this.getBoundingBox(this.getSelectionCells()));
9718				var vs = this.view.scale;
9719
9720				if (exportType == 'diagram' && this.backgroundImage != null)
9721				{
9722					bounds = mxRectangle.fromRectangle(bounds);
9723					bounds.add(new mxRectangle(
9724						(this.view.translate.x + this.backgroundImage.x) * vs,
9725						(this.view.translate.y + this.backgroundImage.y) * vs,
9726					 	this.backgroundImage.width * vs,
9727					 	this.backgroundImage.height * vs));
9728				}
9729
9730				if (bounds == null)
9731				{
9732					throw Error(mxResources.get('drawingEmpty'));
9733				}
9734
9735				// Prepares SVG document that holds the output
9736				var svgDoc = mxUtils.createXmlDocument();
9737				var root = (svgDoc.createElementNS != null) ?
9738			    	svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');
9739
9740				if (background != null)
9741				{
9742					if (root.style != null)
9743					{
9744						root.style.backgroundColor = background;
9745					}
9746					else
9747					{
9748						root.setAttribute('style', 'background-color:' + background);
9749					}
9750				}
9751
9752				if (svgDoc.createElementNS == null)
9753				{
9754			    	root.setAttribute('xmlns', mxConstants.NS_SVG);
9755			    	root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
9756				}
9757				else
9758				{
9759					// KNOWN: Ignored in IE9-11, adds namespace for each image element instead. No workaround.
9760					root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
9761				}
9762
9763				var s = scale / vs;
9764				var w = Math.max(1, Math.ceil(bounds.width * s) + 2 * border) +
9765					((hasShadow && border == 0) ? 5 : 0);
9766				var h = Math.max(1, Math.ceil(bounds.height * s) + 2 * border) +
9767					((hasShadow && border == 0) ? 5 : 0);
9768
9769				root.setAttribute('version', '1.1');
9770				root.setAttribute('width', w + 'px');
9771				root.setAttribute('height', h + 'px');
9772				root.setAttribute('viewBox', ((crisp) ? '-0.5 -0.5' : '0 0') + ' ' + w + ' ' + h);
9773				svgDoc.appendChild(root);
9774
9775			    // Renders graph. Offset will be multiplied with state's scale when painting state.
9776				// TextOffset only seems to affect FF output but used everywhere for consistency.
9777				var group = (svgDoc.createElementNS != null) ?
9778			    	svgDoc.createElementNS(mxConstants.NS_SVG, 'g') : svgDoc.createElement('g');
9779			    root.appendChild(group);
9780
9781				var svgCanvas = this.createSvgCanvas(group);
9782				svgCanvas.foOffset = (crisp) ? -0.5 : 0;
9783				svgCanvas.textOffset = (crisp) ? -0.5 : 0;
9784				svgCanvas.imageOffset = (crisp) ? -0.5 : 0;
9785				svgCanvas.translate(Math.floor(border / scale - bounds.x / vs),
9786					Math.floor(border / scale - bounds.y / vs));
9787
9788				// Convert HTML entities
9789				var htmlConverter = document.createElement('div');
9790
9791				// Adds simple text fallback for viewers with no support for foreignObjects
9792				var getAlternateText = svgCanvas.getAlternateText;
9793				svgCanvas.getAlternateText = function(fo, x, y, w, h, str,
9794					align, valign, wrap, format, overflow, clip, rotation)
9795				{
9796					// Assumes a max character width of 0.5em
9797					if (str != null && this.state.fontSize > 0)
9798					{
9799						try
9800						{
9801							if (mxUtils.isNode(str))
9802							{
9803								str = str.innerText;
9804							}
9805							else
9806							{
9807								htmlConverter.innerHTML = str;
9808								str = mxUtils.extractTextWithWhitespace(htmlConverter.childNodes);
9809							}
9810
9811							// Workaround for substring breaking double byte UTF
9812							var exp = Math.ceil(2 * w / this.state.fontSize);
9813							var result = [];
9814							var length = 0;
9815							var index = 0;
9816
9817							while ((exp == 0 || length < exp) && index < str.length)
9818							{
9819								var char = str.charCodeAt(index);
9820
9821								if (char == 10 || char == 13)
9822								{
9823									if (length > 0)
9824									{
9825										break;
9826									}
9827								}
9828								else
9829								{
9830									result.push(str.charAt(index));
9831
9832									if (char < 255)
9833									{
9834										length++;
9835									}
9836								}
9837
9838								index++;
9839							}
9840
9841							// Uses result and adds ellipsis if more than 1 char remains
9842							if (result.length < str.length && str.length - result.length > 1)
9843							{
9844								str = mxUtils.trim(result.join('')) + '...';
9845							}
9846
9847							return str;
9848						}
9849						catch (e)
9850						{
9851							return getAlternateText.apply(this, arguments);
9852						}
9853					}
9854					else
9855					{
9856						return getAlternateText.apply(this, arguments);
9857					}
9858				};
9859
9860				// Paints background image
9861				var bgImg = this.backgroundImage;
9862
9863				if (bgImg != null)
9864				{
9865					var s2 = vs / scale;
9866					var tr = this.view.translate;
9867					var tmp = new mxRectangle((bgImg.x + tr.x) * s2, (bgImg.y + tr.y) * s2,
9868						bgImg.width * s2, bgImg.height * s2);
9869
9870					// Checks if visible
9871					if (mxUtils.intersects(bounds, tmp))
9872					{
9873						svgCanvas.image(bgImg.x + tr.x, bgImg.y + tr.y,
9874							bgImg.width, bgImg.height, bgImg.src, true);
9875					}
9876				}
9877
9878				svgCanvas.scale(s);
9879				svgCanvas.textEnabled = showText;
9880
9881				imgExport = (imgExport != null) ? imgExport : this.createSvgImageExport();
9882				var imgExportDrawCellState = imgExport.drawCellState;
9883
9884				// Ignores custom links
9885				var imgExportGetLinkForCellState = imgExport.getLinkForCellState;
9886
9887				imgExport.getLinkForCellState = function(state, canvas)
9888				{
9889					var result = imgExportGetLinkForCellState.apply(this, arguments);
9890
9891					return (result != null && !state.view.graph.isCustomLink(result)) ? result : null;
9892				};
9893
9894				imgExport.getLinkTargetForCellState = function(state, canvas)
9895				{
9896					return state.view.graph.getLinkTargetForCell(state.cell);
9897				};
9898
9899				// Implements ignoreSelection flag
9900				imgExport.drawCellState = function(state, canvas)
9901				{
9902					var graph = state.view.graph;
9903					var selected = (lookup != null) ? lookup.get(state.cell) :
9904						graph.isCellSelected(state.cell);
9905					var parent = graph.model.getParent(state.cell);
9906
9907					// Checks if parent cell is selected
9908					while ((!ignoreSelection || lookup != null) &&
9909						!selected && parent != null)
9910					{
9911						selected = (lookup != null) ? lookup.get(parent) :
9912							graph.isCellSelected(parent);
9913						parent = graph.model.getParent(parent);
9914					}
9915
9916					if ((ignoreSelection && lookup == null) || selected)
9917					{
9918						imgExportDrawCellState.apply(this, arguments);
9919					}
9920				};
9921
9922				imgExport.drawState(this.getView().getState(this.model.root), svgCanvas);
9923				this.updateSvgLinks(root, linkTarget, true);
9924				this.addForeignObjectWarning(svgCanvas, root);
9925
9926				return root;
9927			}
9928			finally
9929			{
9930				if (origUseCssTrans)
9931				{
9932					this.useCssTransforms = true;
9933					this.view.revalidate();
9934					this.sizeDidChange();
9935				}
9936			}
9937		};
9938
9939		/**
9940		 * Adds warning for truncated labels in older viewers.
9941		 */
9942		Graph.prototype.addForeignObjectWarning = function(canvas, root)
9943		{
9944			if (urlParams['svg-warning'] != '0' && root.getElementsByTagName('foreignObject').length > 0)
9945			{
9946				var sw = canvas.createElement('switch');
9947				var g1 = canvas.createElement('g');
9948				g1.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility');
9949				var a = canvas.createElement('a');
9950				a.setAttribute('transform', 'translate(0,-5)');
9951
9952				// Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
9953				// in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
9954				if (a.setAttributeNS == null || (root.ownerDocument != document && document.documentMode == null))
9955				{
9956					a.setAttribute('xlink:href', Graph.foreignObjectWarningLink);
9957					a.setAttribute('target', '_blank');
9958				}
9959				else
9960				{
9961					a.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', Graph.foreignObjectWarningLink);
9962					a.setAttributeNS(mxConstants.NS_XLINK, 'target', '_blank');
9963				}
9964
9965				var text = canvas.createElement('text');
9966				text.setAttribute('text-anchor', 'middle');
9967				text.setAttribute('font-size', '10px');
9968				text.setAttribute('x', '50%');
9969				text.setAttribute('y', '100%');
9970				mxUtils.write(text, Graph.foreignObjectWarningText);
9971
9972				sw.appendChild(g1);
9973				a.appendChild(text);
9974				sw.appendChild(a);
9975				root.appendChild(sw);
9976			}
9977		};
9978
9979		/**
9980		 * Hook for creating the canvas used in getSvg.
9981		 */
9982		Graph.prototype.updateSvgLinks = function(node, target, removeCustom)
9983		{
9984			var links = node.getElementsByTagName('a');
9985
9986			for (var i = 0; i < links.length; i++)
9987			{
9988				if (links[i].getAttribute('target') == null)
9989				{
9990					var href = links[i].getAttribute('href');
9991
9992					if (href == null)
9993					{
9994						href = links[i].getAttribute('xlink:href');
9995					}
9996
9997					if (href != null)
9998					{
9999						if (target != null && /^https?:\/\//.test(href))
10000						{
10001							links[i].setAttribute('target', target);
10002						}
10003						else if (removeCustom && this.isCustomLink(href))
10004						{
10005							links[i].setAttribute('href', 'javascript:void(0);');
10006						}
10007					}
10008				}
10009			}
10010		};
10011
10012		/**
10013		 * Hook for creating the canvas used in getSvg.
10014		 */
10015		Graph.prototype.createSvgCanvas = function(node)
10016		{
10017			var canvas = new mxSvgCanvas2D(node);
10018
10019			canvas.pointerEvents = true;
10020
10021			return canvas;
10022		};
10023
10024		/**
10025		 * Returns the first ancestor of the current selection with the given name.
10026		 */
10027		Graph.prototype.getSelectedElement = function()
10028		{
10029			var node = null;
10030
10031			if (window.getSelection)
10032			{
10033				var sel = window.getSelection();
10034
10035			    if (sel.getRangeAt && sel.rangeCount)
10036			    {
10037			        var range = sel.getRangeAt(0);
10038			        node = range.commonAncestorContainer;
10039			    }
10040			}
10041			else if (document.selection)
10042			{
10043				node = document.selection.createRange().parentElement();
10044			}
10045
10046			return node;
10047		};
10048
10049		/**
10050		 * Returns the text editing element.
10051		 */
10052		Graph.prototype.getSelectedEditingElement = function()
10053		{
10054			var node = this.getSelectedElement();
10055
10056			while (node != null && node.nodeType != mxConstants.NODETYPE_ELEMENT)
10057			{
10058				node = node.parentNode;
10059			}
10060
10061			if (node != null)
10062			{
10063				// Workaround for commonAncestor on range in IE11 returning parent of common ancestor
10064				if (node == this.cellEditor.textarea && this.cellEditor.textarea.children.length == 1 &&
10065					this.cellEditor.textarea.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
10066				{
10067					node = this.cellEditor.textarea.firstChild;
10068				}
10069			}
10070
10071			return node;
10072		};
10073
10074		/**
10075		 * Returns the first ancestor of the current selection with the given name.
10076		 */
10077		Graph.prototype.getParentByName = function(node, name, stopAt)
10078		{
10079			while (node != null)
10080			{
10081				if (node.nodeName == name)
10082				{
10083					return node;
10084				}
10085
10086				if (node == stopAt)
10087				{
10088					return null;
10089				}
10090
10091				node = node.parentNode;
10092			}
10093
10094			return node;
10095		};
10096
10097		/**
10098		 * Returns the first ancestor of the current selection with the given name.
10099		 */
10100		Graph.prototype.getParentByNames = function(node, names, stopAt)
10101		{
10102			while (node != null)
10103			{
10104				if (mxUtils.indexOf(names, node.nodeName) >= 0)
10105				{
10106					return node;
10107				}
10108
10109				if (node == stopAt)
10110				{
10111					return null;
10112				}
10113
10114				node = node.parentNode;
10115			}
10116
10117			return node;
10118		};
10119
10120		/**
10121		 * Selects the given node.
10122		 */
10123		Graph.prototype.selectNode = function(node)
10124		{
10125			var sel = null;
10126
10127		    // IE9 and non-IE
10128			if (window.getSelection)
10129		    {
10130		    	sel = window.getSelection();
10131
10132		        if (sel.getRangeAt && sel.rangeCount)
10133		        {
10134		        	var range = document.createRange();
10135		            range.selectNode(node);
10136		            sel.removeAllRanges();
10137		            sel.addRange(range);
10138		        }
10139		    }
10140		    // IE < 9
10141			else if ((sel = document.selection) && sel.type != 'Control')
10142		    {
10143		        var originalRange = sel.createRange();
10144		        originalRange.collapse(true);
10145		        var range = sel.createRange();
10146		        range.setEndPoint('StartToStart', originalRange);
10147		        range.select();
10148		    }
10149		};
10150
10151		/**
10152		 * Deletes the given cells  and returns the cells to be selected.
10153		 */
10154		Graph.prototype.deleteCells = function(cells, includeEdges)
10155		{
10156			var select = null;
10157
10158			if (cells != null && cells.length > 0)
10159			{
10160				this.model.beginUpdate();
10161				try
10162				{
10163					// Shrinks tables
10164					for (var i = 0; i < cells.length; i++)
10165					{
10166						var parent = this.model.getParent(cells[i]);
10167
10168						if (this.isTable(parent))
10169						{
10170							var row = this.getCellGeometry(cells[i]);
10171							var table = this.getCellGeometry(parent);
10172
10173							if (row != null && table != null)
10174							{
10175								table = table.clone();
10176								table.height -= row.height;
10177								this.model.setGeometry(parent, table);
10178							}
10179						}
10180					}
10181
10182					var parents = (this.selectParentAfterDelete) ? this.model.getParents(cells) : null;
10183					this.removeCells(cells, includeEdges);
10184				}
10185				finally
10186				{
10187					this.model.endUpdate();
10188				}
10189
10190				// Selects parents for easier editing of groups
10191				if (parents != null)
10192				{
10193					select = [];
10194
10195					for (var i = 0; i < parents.length; i++)
10196					{
10197						if (this.model.contains(parents[i]) &&
10198							(this.model.isVertex(parents[i]) ||
10199							this.model.isEdge(parents[i])))
10200						{
10201							select.push(parents[i]);
10202						}
10203					}
10204				}
10205			}
10206
10207			return select;
10208		};
10209
10210		/**
10211		 * Inserts a column in the table for the given cell.
10212		 */
10213		Graph.prototype.insertTableColumn = function(cell, before)
10214		{
10215			var model = this.getModel();
10216			model.beginUpdate();
10217
10218			try
10219			{
10220				var table = cell;
10221				var index = 0;
10222
10223				if (this.isTableCell(cell))
10224				{
10225					var row = model.getParent(cell);
10226					table = model.getParent(row);
10227					index = mxUtils.indexOf(model.getChildCells(row, true), cell);
10228				}
10229				else
10230				{
10231					if (this.isTableRow(cell))
10232					{
10233						table = model.getParent(cell);
10234					}
10235					else
10236					{
10237						cell = model.getChildCells(table, true)[0];
10238					}
10239
10240					if (!before)
10241					{
10242						index = model.getChildCells(cell, true).length - 1;
10243					}
10244				}
10245
10246				var rows = model.getChildCells(table, true);
10247				var dw = Graph.minTableColumnWidth;
10248
10249				for (var i = 0; i < rows.length; i++)
10250				{
10251					var child = model.getChildCells(rows[i], true)[index];
10252					var clone = model.cloneCell(child, false);
10253					var geo = this.getCellGeometry(clone);
10254					clone.value = null;
10255
10256					if (geo != null)
10257					{
10258						dw = geo.width;
10259						var rowGeo = this.getCellGeometry(rows[i]);
10260
10261						if (rowGeo != null)
10262						{
10263							geo.height = rowGeo.height;
10264						}
10265					}
10266
10267					model.add(rows[i], clone, index + ((before) ? 0 : 1));
10268				}
10269
10270				var tableGeo = this.getCellGeometry(table);
10271
10272				if (tableGeo != null)
10273				{
10274					tableGeo = tableGeo.clone();
10275					tableGeo.width += dw;
10276
10277					model.setGeometry(table, tableGeo);
10278				}
10279			}
10280			finally
10281			{
10282				model.endUpdate();
10283			}
10284		};
10285
10286		/**
10287		 * Inserts a row in the table for the given cell.
10288		 */
10289		Graph.prototype.deleteLane = function(cell)
10290		{
10291			var model = this.getModel();
10292			model.beginUpdate();
10293
10294			try
10295			{
10296				var pool = null;
10297				var lane = cell;
10298				var style = this.getCurrentCellStyle(lane);
10299
10300				if (style['childLayout'] == 'stackLayout')
10301				{
10302					pool = lane;
10303				}
10304				else
10305				{
10306					pool = model.getParent(lane);
10307				}
10308
10309				var lanes = model.getChildCells(pool, true);
10310
10311				if (lanes.length == 0)
10312				{
10313					model.remove(pool);
10314				}
10315				else
10316				{
10317					if (pool == lane)
10318					{
10319						lane = lanes[lanes.length - 1];
10320					}
10321
10322					model.remove(lane);
10323				}
10324			}
10325			finally
10326			{
10327				model.endUpdate();
10328			}
10329		};
10330
10331		/**
10332		 * Inserts a row in the table for the given cell.
10333		 */
10334		Graph.prototype.insertLane = function(cell, before)
10335		{
10336			var model = this.getModel();
10337			model.beginUpdate();
10338
10339			try
10340			{
10341				var pool = null;
10342				var lane = cell;
10343				var style = this.getCurrentCellStyle(lane);
10344
10345				if (style['childLayout'] == 'stackLayout')
10346				{
10347					pool = lane;
10348					var lanes = model.getChildCells(pool, true);
10349					lane = lanes[(before) ? 0 : lanes.length - 1];
10350				}
10351				else
10352				{
10353					pool = model.getParent(lane);
10354				}
10355
10356				var index = pool.getIndex(lane);
10357				lane = model.cloneCell(lane, false);
10358				lane.value = null;
10359				model.add(pool, lane, index + ((before) ? 0 : 1));
10360			}
10361			finally
10362			{
10363				model.endUpdate();
10364			}
10365		};
10366
10367		/**
10368		 * Inserts a row in the table for the given cell.
10369		 */
10370		Graph.prototype.insertTableRow = function(cell, before)
10371		{
10372			var model = this.getModel();
10373			model.beginUpdate();
10374
10375			try
10376			{
10377				var table = cell;
10378				var row = cell;
10379
10380				if (this.isTableCell(cell))
10381				{
10382					row = model.getParent(cell);
10383					table = model.getParent(row);
10384				}
10385				else if (this.isTableRow(cell))
10386				{
10387					table = model.getParent(cell);
10388				}
10389				else
10390				{
10391					var rows = model.getChildCells(table, true);
10392					row = rows[(before) ? 0 : rows.length - 1];
10393				}
10394
10395				var cells = model.getChildCells(row, true);
10396				var index = table.getIndex(row);
10397				row = model.cloneCell(row, false);
10398				row.value = null;
10399
10400				var rowGeo = this.getCellGeometry(row);
10401
10402				if (rowGeo != null)
10403				{
10404					for (var i = 0; i < cells.length; i++)
10405					{
10406						var cell = model.cloneCell(cells[i], false);
10407						row.insert(cell);
10408						cell.value = null;
10409
10410						var geo = this.getCellGeometry(cell);
10411
10412						if (geo != null)
10413						{
10414							geo.height = rowGeo.height;
10415						}
10416					}
10417
10418					model.add(table, row, index + ((before) ? 0 : 1));
10419
10420					var tableGeo = this.getCellGeometry(table);
10421
10422					if (tableGeo != null)
10423					{
10424						tableGeo = tableGeo.clone();
10425						tableGeo.height += rowGeo.height;
10426
10427						model.setGeometry(table, tableGeo);
10428					}
10429				}
10430			}
10431			finally
10432			{
10433				model.endUpdate();
10434			}
10435		};
10436
10437		/**
10438		 *
10439		 */
10440		Graph.prototype.deleteTableColumn = function(cell)
10441		{
10442			var model = this.getModel();
10443			model.beginUpdate();
10444
10445			try
10446			{
10447				var table = cell;
10448				var row = cell;
10449
10450				if (this.isTableCell(cell))
10451				{
10452					row = model.getParent(cell);
10453				}
10454
10455				if (this.isTableRow(row))
10456				{
10457					table = model.getParent(row);
10458				}
10459
10460				var rows = model.getChildCells(table, true);
10461
10462				if (rows.length == 0)
10463				{
10464					model.remove(table);
10465				}
10466				else
10467				{
10468					if (!this.isTableRow(row))
10469					{
10470						row = rows[0];
10471					}
10472
10473					var cells = model.getChildCells(row, true);
10474
10475					if (cells.length <= 1)
10476					{
10477						model.remove(table);
10478					}
10479					else
10480					{
10481						var index = cells.length - 1;
10482
10483						if (this.isTableCell(cell))
10484						{
10485							index = mxUtils.indexOf(cells, cell);
10486						}
10487
10488						var width = 0;
10489
10490						for (var i = 0; i < rows.length; i++)
10491						{
10492							var child = model.getChildCells(rows[i], true)[index];
10493							model.remove(child);
10494
10495							var geo = this.getCellGeometry(child);
10496
10497							if (geo != null)
10498							{
10499								width = Math.max(width, geo.width);
10500							}
10501						}
10502
10503						var tableGeo = this.getCellGeometry(table);
10504
10505						if (tableGeo != null)
10506						{
10507							tableGeo = tableGeo.clone();
10508							tableGeo.width -= width;
10509
10510							model.setGeometry(table, tableGeo);
10511						}
10512					}
10513				}
10514			}
10515			finally
10516			{
10517				model.endUpdate();
10518			}
10519		};
10520
10521		/**
10522		 *
10523		 */
10524		Graph.prototype.deleteTableRow = function(cell)
10525		{
10526			var model = this.getModel();
10527			model.beginUpdate();
10528
10529			try
10530			{
10531				var table = cell;
10532				var row = cell;
10533
10534				if (this.isTableCell(cell))
10535				{
10536					row = model.getParent(cell);
10537					cell = row;
10538				}
10539
10540				if (this.isTableRow(cell))
10541				{
10542					table = model.getParent(row);
10543				}
10544
10545				var rows = model.getChildCells(table, true);
10546
10547				if (rows.length <= 1)
10548				{
10549					model.remove(table);
10550				}
10551				else
10552				{
10553					if (!this.isTableRow(row))
10554					{
10555						row = rows[rows.length - 1];
10556					}
10557
10558					model.remove(row);
10559					var height = 0;
10560
10561					var geo = this.getCellGeometry(row);
10562
10563					if (geo != null)
10564					{
10565						height = geo.height;
10566					}
10567
10568					var tableGeo = this.getCellGeometry(table);
10569
10570					if (tableGeo != null)
10571					{
10572						tableGeo = tableGeo.clone();
10573						tableGeo.height -= height;
10574
10575						model.setGeometry(table, tableGeo);
10576					}
10577				}
10578			}
10579			finally
10580			{
10581				model.endUpdate();
10582			}
10583		};
10584
10585		/**
10586		 * Inserts a new row into the given table.
10587		 */
10588		Graph.prototype.insertRow = function(table, index)
10589		{
10590			var bd = table.tBodies[0];
10591			var cells = bd.rows[0].cells;
10592			var cols = 0;
10593
10594			// Counts columns including colspans
10595			for (var i = 0; i < cells.length; i++)
10596			{
10597				var colspan = cells[i].getAttribute('colspan');
10598				cols += (colspan != null) ? parseInt(colspan) : 1;
10599			}
10600
10601			var row = bd.insertRow(index);
10602
10603			for (var i = 0; i < cols; i++)
10604			{
10605				mxUtils.br(row.insertCell(-1));
10606			}
10607
10608			return row.cells[0];
10609		};
10610
10611		/**
10612		 * Deletes the given column.
10613		 */
10614		Graph.prototype.deleteRow = function(table, index)
10615		{
10616			table.tBodies[0].deleteRow(index);
10617		};
10618
10619		/**
10620		 * Deletes the given column.
10621		 */
10622		Graph.prototype.insertColumn = function(table, index)
10623		{
10624			var hd = table.tHead;
10625
10626			if (hd != null)
10627			{
10628				// TODO: use colIndex
10629				for (var h = 0; h < hd.rows.length; h++)
10630				{
10631					var th = document.createElement('th');
10632					hd.rows[h].appendChild(th);
10633					mxUtils.br(th);
10634				}
10635			}
10636
10637			var bd = table.tBodies[0];
10638
10639			for (var i = 0; i < bd.rows.length; i++)
10640			{
10641				var cell = bd.rows[i].insertCell(index);
10642				mxUtils.br(cell);
10643			}
10644
10645			return bd.rows[0].cells[(index >= 0) ? index : bd.rows[0].cells.length - 1];
10646		};
10647
10648		/**
10649		 * Deletes the given column.
10650		 */
10651		Graph.prototype.deleteColumn = function(table, index)
10652		{
10653			if (index >= 0)
10654			{
10655				var bd = table.tBodies[0];
10656				var rows = bd.rows;
10657
10658				for (var i = 0; i < rows.length; i++)
10659				{
10660					if (rows[i].cells.length > index)
10661					{
10662						rows[i].deleteCell(index);
10663					}
10664				}
10665			}
10666		};
10667
10668		/**
10669		 * Inserts the given HTML at the caret position (no undo).
10670		 */
10671		Graph.prototype.pasteHtmlAtCaret = function(html)
10672		{
10673		    var sel, range;
10674
10675			// IE9 and non-IE
10676		    if (window.getSelection)
10677		    {
10678		        sel = window.getSelection();
10679
10680		        if (sel.getRangeAt && sel.rangeCount)
10681		        {
10682		            range = sel.getRangeAt(0);
10683		            range.deleteContents();
10684
10685		            // Range.createContextualFragment() would be useful here but is
10686		            // only relatively recently standardized and is not supported in
10687		            // some browsers (IE9, for one)
10688		            var el = document.createElement("div");
10689		            el.innerHTML = html;
10690		            var frag = document.createDocumentFragment(), node;
10691
10692		            while ((node = el.firstChild))
10693		            {
10694		                lastNode = frag.appendChild(node);
10695		            }
10696
10697		            range.insertNode(frag);
10698		        }
10699		    }
10700		    // IE < 9
10701		    else if ((sel = document.selection) && sel.type != "Control")
10702		    {
10703		    	// FIXME: Does not work if selection is empty
10704		        sel.createRange().pasteHTML(html);
10705		    }
10706		};
10707
10708		/**
10709		 * Creates an anchor elements for handling the given link in the
10710		 * hint that is shown when the cell is selected.
10711		 */
10712		Graph.prototype.createLinkForHint = function(link, label)
10713		{
10714			link = (link != null) ? link : 'javascript:void(0);';
10715
10716			if (label == null || label.length == 0)
10717			{
10718				if (this.isCustomLink(link))
10719				{
10720					label = this.getLinkTitle(link);
10721				}
10722				else
10723				{
10724					label = link;
10725				}
10726			}
10727
10728			// Helper function to shorten strings
10729			function short(str, max)
10730			{
10731				if (str.length > max)
10732				{
10733					str = str.substring(0, Math.round(max / 2)) + '...' +
10734						str.substring(str.length - Math.round(max / 4));
10735				}
10736
10737				return str;
10738			};
10739
10740			var a = document.createElement('a');
10741			a.setAttribute('rel', this.linkRelation);
10742			a.setAttribute('href', this.getAbsoluteUrl(link));
10743			a.setAttribute('title', short((this.isCustomLink(link)) ?
10744				this.getLinkTitle(link) : link, 80));
10745
10746			if (this.linkTarget != null)
10747			{
10748				a.setAttribute('target', this.linkTarget);
10749			}
10750
10751			// Adds shortened label to link
10752			mxUtils.write(a, short(label, 40));
10753
10754			// Handles custom links
10755			if (this.isCustomLink(link))
10756			{
10757				mxEvent.addListener(a, 'click', mxUtils.bind(this, function(evt)
10758				{
10759					this.customLinkClicked(link);
10760					mxEvent.consume(evt);
10761				}));
10762			}
10763
10764			return a;
10765		};
10766
10767		/**
10768		 * Customized graph for touch devices.
10769		 */
10770		Graph.prototype.initTouch = function()
10771		{
10772			// Disables new connections via "hotspot"
10773			this.connectionHandler.marker.isEnabled = function()
10774			{
10775				return this.graph.connectionHandler.first != null;
10776			};
10777
10778			// Hides menu when editing starts
10779			this.addListener(mxEvent.START_EDITING, function(sender, evt)
10780			{
10781				this.popupMenuHandler.hideMenu();
10782			});
10783
10784			// Adds custom hit detection if native hit detection found no cell
10785			var graphUpdateMouseEvent = this.updateMouseEvent;
10786			this.updateMouseEvent = function(me)
10787			{
10788				me = graphUpdateMouseEvent.apply(this, arguments);
10789
10790				if (mxEvent.isTouchEvent(me.getEvent()) && me.getState() == null)
10791				{
10792					var cell = this.getCellAt(me.graphX, me.graphY);
10793
10794					if (cell != null && this.isSwimlane(cell) && this.hitsSwimlaneContent(cell, me.graphX, me.graphY))
10795					{
10796						cell = null;
10797					}
10798					else
10799					{
10800						me.state = this.view.getState(cell);
10801
10802						if (me.state != null && me.state.shape != null)
10803						{
10804							this.container.style.cursor = me.state.shape.node.style.cursor;
10805						}
10806					}
10807				}
10808
10809				if (me.getState() == null && this.isEnabled())
10810				{
10811					this.container.style.cursor = 'default';
10812				}
10813
10814				return me;
10815			};
10816
10817			// Context menu trigger implementation depending on current selection state
10818			// combined with support for normal popup trigger.
10819			var cellSelected = false;
10820			var selectionEmpty = false;
10821			var menuShowing = false;
10822
10823			var oldFireMouseEvent = this.fireMouseEvent;
10824
10825			this.fireMouseEvent = function(evtName, me, sender)
10826			{
10827				if (evtName == mxEvent.MOUSE_DOWN)
10828				{
10829					// For hit detection on edges
10830					me = this.updateMouseEvent(me);
10831
10832					cellSelected = this.isCellSelected(me.getCell());
10833					selectionEmpty = this.isSelectionEmpty();
10834					menuShowing = this.popupMenuHandler.isMenuShowing();
10835				}
10836
10837				oldFireMouseEvent.apply(this, arguments);
10838			};
10839
10840			// Shows popup menu if cell was selected or selection was empty and background was clicked
10841			// FIXME: Conflicts with mxPopupMenuHandler.prototype.getCellForPopupEvent in Editor.js by
10842			// selecting parent for selected children in groups before this check can be made.
10843			this.popupMenuHandler.mouseUp = mxUtils.bind(this, function(sender, me)
10844			{
10845				var isMouseEvent = mxEvent.isMouseEvent(me.getEvent());
10846				this.popupMenuHandler.popupTrigger = !this.isEditing() && this.isEnabled() &&
10847					(me.getState() == null || !me.isSource(me.getState().control)) &&
10848					(this.popupMenuHandler.popupTrigger || (!menuShowing && !isMouseEvent &&
10849					((selectionEmpty && me.getCell() == null && this.isSelectionEmpty()) ||
10850					(cellSelected && this.isCellSelected(me.getCell())))));
10851
10852				// Delays popup menu to allow for double tap to start editing
10853				var popup = (!cellSelected || isMouseEvent) ? null : mxUtils.bind(this, function(cell)
10854				{
10855					window.setTimeout(mxUtils.bind(this, function()
10856					{
10857						if (!this.isEditing())
10858						{
10859							var origin = mxUtils.getScrollOrigin();
10860							this.popupMenuHandler.popup(me.getX() + origin.x + 1,
10861								me.getY() + origin.y + 1, cell, me.getEvent());
10862						}
10863					}), 500);
10864				});
10865
10866				mxPopupMenuHandler.prototype.mouseUp.apply(this.popupMenuHandler, [sender, me, popup]);
10867			});
10868		};
10869
10870		/**
10871		 * HTML in-place editor
10872		 */
10873		mxCellEditor.prototype.isContentEditing = function()
10874		{
10875			var state = this.graph.view.getState(this.editingCell);
10876
10877			return state != null && state.style['html'] == 1;
10878		};
10879
10880		/**
10881		 * Returns true if all selected text is inside a table element.
10882		 */
10883		mxCellEditor.prototype.isTableSelected = function()
10884		{
10885			return this.graph.getParentByName(
10886				this.graph.getSelectedElement(),
10887				'TABLE', this.textarea) != null;
10888		};
10889
10890		/**
10891		 * Returns true if text is selected.
10892		 */
10893		mxCellEditor.prototype.isTextSelected = function()
10894		{
10895		    var txt = '';
10896
10897		    if (window.getSelection)
10898			{
10899		        txt = window.getSelection();
10900		    }
10901			else if (document.getSelection)
10902			{
10903		        txt = document.getSelection();
10904		    }
10905			else if (document.selection)
10906			{
10907		        txt = document.selection.createRange().text;
10908		    }
10909
10910			return txt != '';
10911		};
10912
10913		/**
10914		 * Inserts a tab at the cursor position.
10915		 */
10916		mxCellEditor.prototype.insertTab = function(spaces)
10917		{
10918			var editor = this.textarea;
10919	        var doc = editor.ownerDocument.defaultView;
10920	        var sel = doc.getSelection();
10921	        var range = sel.getRangeAt(0);
10922			var str = '\t';
10923
10924			if (spaces != null)
10925			{
10926				str = '';
10927
10928				while (spaces > 0)
10929				{
10930					str += '\xa0';
10931					spaces--;
10932				}
10933			}
10934
10935			// LATER: Fix normalized tab after editing plain text labels
10936			var tabNode = document.createElement('span');
10937			tabNode.style.whiteSpace = 'pre';
10938			tabNode.appendChild(document.createTextNode(str));
10939			range.insertNode(tabNode);
10940	        range.setStartAfter(tabNode);
10941	        range.setEndAfter(tabNode);
10942	        sel.removeAllRanges();
10943	        sel.addRange(range);
10944		};
10945
10946		/**
10947		 * Sets the alignment of the current selected cell. This sets the
10948		 * alignment in the cell style, removes all alignment within the
10949		 * text and invokes the built-in alignment function.
10950		 *
10951		 * Only the built-in function is invoked if shift is pressed or
10952		 * if table cells are selected and shift is not pressed.
10953		 */
10954		mxCellEditor.prototype.alignText = function(align, evt)
10955		{
10956			var shiftPressed = evt != null && mxEvent.isShiftDown(evt);
10957
10958			if (shiftPressed || (window.getSelection != null && window.getSelection().containsNode != null))
10959			{
10960				var allSelected = true;
10961
10962				this.graph.processElements(this.textarea, function(node)
10963				{
10964					if (shiftPressed || window.getSelection().containsNode(node, true))
10965					{
10966						node.removeAttribute('align');
10967						node.style.textAlign = null;
10968					}
10969					else
10970					{
10971						allSelected = false;
10972					}
10973				});
10974
10975				if (allSelected)
10976				{
10977					this.graph.cellEditor.setAlign(align);
10978				}
10979			}
10980
10981			document.execCommand('justify' + align.toLowerCase(), false, null);
10982		};
10983
10984		/**
10985		 * Creates the keyboard event handler for the current graph and history.
10986		 */
10987		mxCellEditor.prototype.saveSelection = function()
10988		{
10989		    if (window.getSelection)
10990		    {
10991		        var sel = window.getSelection();
10992
10993		        if (sel.getRangeAt && sel.rangeCount)
10994		        {
10995		            var ranges = [];
10996
10997		            for (var i = 0, len = sel.rangeCount; i < len; ++i)
10998		            {
10999		                ranges.push(sel.getRangeAt(i));
11000		            }
11001
11002		            return ranges;
11003		        }
11004		    }
11005		    else if (document.selection && document.selection.createRange)
11006		    {
11007		        return document.selection.createRange();
11008		    }
11009
11010		    return null;
11011		};
11012
11013		/**
11014		 * Creates the keyboard event handler for the current graph and history.
11015		 */
11016		mxCellEditor.prototype.restoreSelection = function(savedSel)
11017		{
11018			try
11019			{
11020				if (savedSel)
11021				{
11022					if (window.getSelection)
11023					{
11024						sel = window.getSelection();
11025						sel.removeAllRanges();
11026
11027						for (var i = 0, len = savedSel.length; i < len; ++i)
11028						{
11029							sel.addRange(savedSel[i]);
11030						}
11031					}
11032					else if (document.selection && savedSel.select)
11033					{
11034						savedSel.select();
11035					}
11036				}
11037			}
11038			catch (e)
11039			{
11040				// ignore
11041			}
11042		};
11043
11044		/**
11045		 * Handling of special nl2Br style for not converting newlines to breaks in HTML labels.
11046		 * NOTE: Since it's easier to set this when the label is created we assume that it does
11047		 * not change during the lifetime of the mxText instance.
11048		 */
11049		var mxCellRendererInitializeLabel = mxCellRenderer.prototype.initializeLabel;
11050		mxCellRenderer.prototype.initializeLabel = function(state)
11051		{
11052			if (state.text != null)
11053			{
11054				state.text.replaceLinefeeds = mxUtils.getValue(state.style, 'nl2Br', '1') != '0';
11055			}
11056
11057			mxCellRendererInitializeLabel.apply(this, arguments);
11058		};
11059
11060		var mxConstraintHandlerUpdate = mxConstraintHandler.prototype.update;
11061		mxConstraintHandler.prototype.update = function(me, source)
11062		{
11063			if (this.isKeepFocusEvent(me) || !mxEvent.isAltDown(me.getEvent()))
11064			{
11065				mxConstraintHandlerUpdate.apply(this, arguments);
11066			}
11067			else
11068			{
11069				this.reset();
11070			}
11071		};
11072
11073		/**
11074		 * No dashed shapes.
11075		 */
11076		mxGuide.prototype.createGuideShape = function(horizontal)
11077		{
11078			var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
11079
11080			return guide;
11081		};
11082
11083		/**
11084		 * HTML in-place editor
11085		 */
11086		mxCellEditor.prototype.escapeCancelsEditing = false;
11087
11088		/**
11089		 * Overridden to set CSS classes.
11090		 */
11091		var mxCellEditorStartEditing = mxCellEditor.prototype.startEditing;
11092		mxCellEditor.prototype.startEditing = function(cell, trigger)
11093		{
11094			cell = this.graph.getStartEditingCell(cell, trigger);
11095
11096			mxCellEditorStartEditing.apply(this, arguments);
11097
11098			// Overrides class in case of HTML content to add
11099			// dashed borders for divs and table cells
11100			var state = this.graph.view.getState(cell);
11101
11102			if (state != null && state.style['html'] == 1)
11103			{
11104				this.textarea.className = 'mxCellEditor geContentEditable';
11105			}
11106			else
11107			{
11108				this.textarea.className = 'mxCellEditor mxPlainTextEditor';
11109			}
11110
11111			// Toggles markup vs wysiwyg mode
11112			this.codeViewMode = false;
11113
11114			// Stores current selection range when switching between markup and code
11115			this.switchSelectionState = null;
11116
11117			// Selects editing cell
11118			this.graph.setSelectionCell(cell);
11119
11120			// Enables focus outline for edges and edge labels
11121			var parent = this.graph.getModel().getParent(cell);
11122			var geo = this.graph.getCellGeometry(cell);
11123
11124			if ((this.graph.getModel().isEdge(parent) && geo != null && geo.relative) ||
11125				this.graph.getModel().isEdge(cell))
11126			{
11127				// IE>8 and FF on Windows uses outline default of none
11128				if (mxClient.IS_IE || mxClient.IS_IE11 || (mxClient.IS_FF && mxClient.IS_WIN))
11129				{
11130					this.textarea.style.outline = 'gray dotted 1px';
11131				}
11132				else
11133				{
11134					this.textarea.style.outline = '';
11135				}
11136			}
11137		}
11138
11139		/**
11140		 * HTML in-place editor
11141		 */
11142		var cellEditorInstallListeners = mxCellEditor.prototype.installListeners;
11143		mxCellEditor.prototype.installListeners = function(elt)
11144		{
11145			cellEditorInstallListeners.apply(this, arguments);
11146
11147			// Adds a reference from the clone to the original node, recursively
11148			function reference(node, clone)
11149			{
11150				clone.originalNode = node;
11151
11152				node = node.firstChild;
11153				var child = clone.firstChild;
11154
11155				while (node != null && child != null)
11156				{
11157					reference(node, child);
11158					node = node.nextSibling;
11159					child = child.nextSibling;
11160				}
11161
11162				return clone;
11163			};
11164
11165			// Checks the given node for new nodes, recursively
11166			function checkNode(node, clone)
11167			{
11168				if (node != null)
11169				{
11170					if (clone.originalNode != node)
11171					{
11172						cleanNode(node);
11173					}
11174					else
11175					{
11176						node = node.firstChild;
11177						clone = clone.firstChild;
11178
11179						while (node != null)
11180						{
11181							var nextNode = node.nextSibling;
11182
11183							if (clone == null)
11184							{
11185								cleanNode(node);
11186							}
11187							else
11188							{
11189								checkNode(node, clone);
11190								clone = clone.nextSibling;
11191							}
11192
11193							node = nextNode;
11194						}
11195					}
11196				}
11197			};
11198
11199			// Removes unused DOM nodes and attributes, recursively
11200			function cleanNode(node)
11201			{
11202				var child = node.firstChild;
11203
11204				while (child != null)
11205				{
11206					var next = child.nextSibling;
11207					cleanNode(child);
11208					child = next;
11209				}
11210
11211				if ((node.nodeType != 1 || (node.nodeName !== 'BR' && node.firstChild == null)) &&
11212					(node.nodeType != 3 || mxUtils.trim(mxUtils.getTextContent(node)).length == 0))
11213				{
11214					node.parentNode.removeChild(node);
11215				}
11216				else
11217				{
11218					// Removes linefeeds
11219					if (node.nodeType == 3)
11220					{
11221						mxUtils.setTextContent(node, mxUtils.getTextContent(node).replace(/\n|\r/g, ''));
11222					}
11223
11224					// Removes CSS classes and styles (for Word and Excel)
11225					if (node.nodeType == 1)
11226					{
11227						node.removeAttribute('style');
11228						node.removeAttribute('class');
11229						node.removeAttribute('width');
11230						node.removeAttribute('cellpadding');
11231						node.removeAttribute('cellspacing');
11232						node.removeAttribute('border');
11233					}
11234				}
11235			};
11236
11237			// Handles paste from Word, Excel etc by removing styles, classnames and unused nodes
11238			// LATER: Fix undo/redo for paste
11239			if (document.documentMode !== 7 && document.documentMode !== 8)
11240			{
11241				mxEvent.addListener(this.textarea, 'paste', mxUtils.bind(this, function(evt)
11242				{
11243					var clone = reference(this.textarea, this.textarea.cloneNode(true));
11244
11245					window.setTimeout(mxUtils.bind(this, function()
11246					{
11247						if (this.textarea != null)
11248						{
11249							// Paste from Word or Excel
11250							if (this.textarea.innerHTML.indexOf('<o:OfficeDocumentSettings>') >= 0 ||
11251								this.textarea.innerHTML.indexOf('<!--[if !mso]>') >= 0)
11252							{
11253								checkNode(this.textarea, clone);
11254							}
11255							else
11256							{
11257								Graph.removePasteFormatting(this.textarea);
11258							}
11259						}
11260					}), 0);
11261				}));
11262			}
11263		};
11264
11265		mxCellEditor.prototype.toggleViewMode = function()
11266		{
11267			var state = this.graph.view.getState(this.editingCell);
11268
11269			if (state != null)
11270			{
11271				var nl2Br = state != null && mxUtils.getValue(state.style, 'nl2Br', '1') != '0';
11272				var tmp = this.saveSelection();
11273
11274				if (!this.codeViewMode)
11275				{
11276					// Clears the initial empty label on the first keystroke
11277					if (this.clearOnChange && this.textarea.innerHTML == this.getEmptyLabelText())
11278					{
11279						this.clearOnChange = false;
11280						this.textarea.innerHTML = '';
11281					}
11282
11283					// Removes newlines from HTML and converts breaks to newlines
11284					// to match the HTML output in plain text
11285					var content = mxUtils.htmlEntities(this.textarea.innerHTML);
11286
11287				    // Workaround for trailing line breaks being ignored in the editor
11288					if (document.documentMode != 8)
11289					{
11290						content = mxUtils.replaceTrailingNewlines(content, '<div><br></div>');
11291					}
11292
11293				    content = this.graph.sanitizeHtml((nl2Br) ? content.replace(/\n/g, '').replace(/&lt;br\s*.?&gt;/g, '<br>') : content, true);
11294					this.textarea.className = 'mxCellEditor mxPlainTextEditor';
11295
11296					var size = mxConstants.DEFAULT_FONTSIZE;
11297
11298					this.textarea.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
11299					this.textarea.style.fontSize = Math.round(size) + 'px';
11300					this.textarea.style.textDecoration = '';
11301					this.textarea.style.fontWeight = 'normal';
11302					this.textarea.style.fontStyle = '';
11303					this.textarea.style.fontFamily = mxConstants.DEFAULT_FONTFAMILY;
11304					this.textarea.style.textAlign = 'left';
11305					this.textarea.style.width = '';
11306
11307					// Adds padding to make cursor visible with borders
11308					this.textarea.style.padding = '2px';
11309
11310					if (this.textarea.innerHTML != content)
11311					{
11312						this.textarea.innerHTML = content;
11313					}
11314
11315					this.codeViewMode = true;
11316				}
11317				else
11318				{
11319					var content = mxUtils.extractTextWithWhitespace(this.textarea.childNodes);
11320
11321					// Strips trailing line break
11322				    if (content.length > 0 && content.charAt(content.length - 1) == '\n')
11323				    {
11324				    	content = content.substring(0, content.length - 1);
11325				    }
11326
11327					content = this.graph.sanitizeHtml((nl2Br) ? content.replace(/\n/g, '<br/>') : content, true)
11328					this.textarea.className = 'mxCellEditor geContentEditable';
11329
11330					var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
11331					var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);
11332					var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
11333					var bold = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
11334							mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD;
11335					var italic = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
11336							mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC;
11337					var txtDecor = [];
11338
11339					if ((mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
11340							mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
11341					{
11342						txtDecor.push('underline');
11343					}
11344
11345					if ((mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
11346							mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH)
11347					{
11348						txtDecor.push('line-through');
11349					}
11350
11351					this.textarea.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
11352					this.textarea.style.fontSize = Math.round(size) + 'px';
11353					this.textarea.style.textDecoration = txtDecor.join(' ');
11354					this.textarea.style.fontWeight = (bold) ? 'bold' : 'normal';
11355					this.textarea.style.fontStyle = (italic) ? 'italic' : '';
11356					this.textarea.style.fontFamily = family;
11357					this.textarea.style.textAlign = align;
11358					this.textarea.style.padding = '0px';
11359
11360					if (this.textarea.innerHTML != content)
11361					{
11362						this.textarea.innerHTML = content;
11363
11364						if (this.textarea.innerHTML.length == 0)
11365						{
11366							this.textarea.innerHTML = this.getEmptyLabelText();
11367							this.clearOnChange = this.textarea.innerHTML.length > 0;
11368						}
11369					}
11370
11371					this.codeViewMode = false;
11372				}
11373
11374				this.textarea.focus();
11375
11376				if (this.switchSelectionState != null)
11377				{
11378					this.restoreSelection(this.switchSelectionState);
11379				}
11380
11381				this.switchSelectionState = tmp;
11382				this.resize();
11383			}
11384		};
11385
11386		var mxCellEditorResize = mxCellEditor.prototype.resize;
11387		mxCellEditor.prototype.resize = function(state, trigger)
11388		{
11389			if (this.textarea != null)
11390			{
11391				var state = this.graph.getView().getState(this.editingCell);
11392
11393				if (this.codeViewMode && state != null)
11394				{
11395					var scale = state.view.scale;
11396					this.bounds = mxRectangle.fromRectangle(state);
11397
11398					// General placement of code editor if cell has no size
11399					// LATER: Fix HTML editor bounds for edge labels
11400					if (this.bounds.width == 0 && this.bounds.height == 0)
11401					{
11402						this.bounds.width = 160 * scale;
11403						this.bounds.height = 60 * scale;
11404
11405						var m = (state.text != null) ? state.text.margin : null;
11406
11407						if (m == null)
11408						{
11409							m = mxUtils.getAlignmentAsPoint(mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER),
11410									mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE));
11411						}
11412
11413						this.bounds.x += m.x * this.bounds.width;
11414						this.bounds.y += m.y * this.bounds.height;
11415					}
11416
11417					this.textarea.style.width = Math.round((this.bounds.width - 4) / scale) + 'px';
11418					this.textarea.style.height = Math.round((this.bounds.height - 4) / scale) + 'px';
11419					this.textarea.style.overflow = 'auto';
11420
11421					// Adds scrollbar offset if visible
11422					if (this.textarea.clientHeight < this.textarea.offsetHeight)
11423					{
11424						this.textarea.style.height = Math.round((this.bounds.height / scale)) + (this.textarea.offsetHeight - this.textarea.clientHeight) + 'px';
11425						this.bounds.height = parseInt(this.textarea.style.height) * scale;
11426					}
11427
11428					if (this.textarea.clientWidth < this.textarea.offsetWidth)
11429					{
11430						this.textarea.style.width = Math.round((this.bounds.width / scale)) + (this.textarea.offsetWidth - this.textarea.clientWidth) + 'px';
11431						this.bounds.width = parseInt(this.textarea.style.width) * scale;
11432					}
11433
11434					this.textarea.style.left = Math.round(this.bounds.x) + 'px';
11435					this.textarea.style.top = Math.round(this.bounds.y) + 'px';
11436
11437					mxUtils.setPrefixedStyle(this.textarea.style, 'transform', 'scale(' + scale + ',' + scale + ')');
11438				}
11439				else
11440				{
11441					this.textarea.style.height = '';
11442					this.textarea.style.overflow = '';
11443					mxCellEditorResize.apply(this, arguments);
11444				}
11445			}
11446		};
11447
11448		mxCellEditorGetInitialValue = mxCellEditor.prototype.getInitialValue;
11449		mxCellEditor.prototype.getInitialValue = function(state, trigger)
11450		{
11451			if (mxUtils.getValue(state.style, 'html', '0') == '0')
11452			{
11453				return mxCellEditorGetInitialValue.apply(this, arguments);
11454			}
11455			else
11456			{
11457				var result = this.graph.getEditingValue(state.cell, trigger)
11458
11459				if (mxUtils.getValue(state.style, 'nl2Br', '1') == '1')
11460				{
11461					result = result.replace(/\n/g, '<br/>');
11462				}
11463
11464				result = this.graph.sanitizeHtml(result, true);
11465
11466				return result;
11467			}
11468		};
11469
11470		mxCellEditorGetCurrentValue = mxCellEditor.prototype.getCurrentValue;
11471		mxCellEditor.prototype.getCurrentValue = function(state)
11472		{
11473			if (mxUtils.getValue(state.style, 'html', '0') == '0')
11474			{
11475				return mxCellEditorGetCurrentValue.apply(this, arguments);
11476			}
11477			else
11478			{
11479				var result = this.graph.sanitizeHtml(this.textarea.innerHTML, true);
11480
11481				if (mxUtils.getValue(state.style, 'nl2Br', '1') == '1')
11482				{
11483					result = result.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>');
11484				}
11485				else
11486				{
11487					result = result.replace(/\r\n/g, '').replace(/\n/g, '');
11488				}
11489
11490				return result;
11491			}
11492		};
11493
11494		var mxCellEditorStopEditing = mxCellEditor.prototype.stopEditing;
11495		mxCellEditor.prototype.stopEditing = function(cancel)
11496		{
11497			// Restores default view mode before applying value
11498			if (this.codeViewMode)
11499			{
11500				this.toggleViewMode();
11501			}
11502
11503			mxCellEditorStopEditing.apply(this, arguments);
11504
11505			// Tries to move focus back to container after editing if possible
11506			this.focusContainer();
11507		};
11508
11509		mxCellEditor.prototype.focusContainer = function()
11510		{
11511			try
11512			{
11513				this.graph.container.focus();
11514			}
11515			catch (e)
11516			{
11517				// ignore
11518			}
11519		};
11520
11521		var mxCellEditorApplyValue = mxCellEditor.prototype.applyValue;
11522		mxCellEditor.prototype.applyValue = function(state, value)
11523		{
11524			// Removes empty relative child labels in edges
11525			this.graph.getModel().beginUpdate();
11526
11527			try
11528			{
11529				mxCellEditorApplyValue.apply(this, arguments);
11530
11531				if (value == '' && this.graph.isCellDeletable(state.cell) &&
11532					this.graph.model.getChildCount(state.cell) == 0 &&
11533					this.graph.isTransparentState(state))
11534				{
11535					this.graph.removeCells([state.cell], false);
11536				}
11537			}
11538			finally
11539			{
11540				this.graph.getModel().endUpdate();
11541			}
11542		};
11543
11544		/**
11545		 * Returns the background color to be used for the editing box. This returns
11546		 * the label background for edge labels and null for all other cases.
11547		 */
11548		mxCellEditor.prototype.getBackgroundColor = function(state)
11549		{
11550			var color = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, null);
11551
11552			if ((color == null || color == mxConstants.NONE) &&
11553				(state.cell.geometry != null && state.cell.geometry.width > 0) &&
11554				(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0) != 0 ||
11555				mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 0))
11556			{
11557				color = mxUtils.getValue(state.style, mxConstants.STYLE_FILLCOLOR, null);
11558			}
11559
11560			if (color == mxConstants.NONE)
11561			{
11562				color = null;
11563			}
11564
11565			return color;
11566		};
11567
11568		mxCellEditor.prototype.getMinimumSize = function(state)
11569		{
11570			var scale = this.graph.getView().scale;
11571
11572			return new mxRectangle(0, 0, (state.text == null) ? 30 :  state.text.size * scale + 20, 30);
11573		};
11574
11575		/**
11576		 * Hold Alt to ignore drop target.
11577		 */
11578		mxGraphHandlerIsValidDropTarget = mxGraphHandler.prototype.isValidDropTarget;
11579		mxGraphHandler.prototype.isValidDropTarget = function(target, me)
11580		{
11581			return mxGraphHandlerIsValidDropTarget.apply(this, arguments) &&
11582				!mxEvent.isAltDown(me.getEvent);
11583		};
11584
11585		/**
11586		 * Hints on handlers
11587		 */
11588		function createHint()
11589		{
11590			var hint = document.createElement('div');
11591			hint.className = 'geHint';
11592			hint.style.whiteSpace = 'nowrap';
11593			hint.style.position = 'absolute';
11594
11595			return hint;
11596		};
11597
11598		/**
11599		 * Format pixels in the given unit
11600		 */
11601		function formatHintText(pixels, unit)
11602		{
11603		    switch(unit)
11604		    {
11605		        case mxConstants.POINTS:
11606		            return pixels;
11607		        case mxConstants.MILLIMETERS:
11608		            return (pixels / mxConstants.PIXELS_PER_MM).toFixed(1);
11609				case mxConstants.METERS:
11610            		return (pixels / (mxConstants.PIXELS_PER_MM * 1000)).toFixed(4);
11611		        case mxConstants.INCHES:
11612		            return (pixels / mxConstants.PIXELS_PER_INCH).toFixed(2);
11613		    }
11614		};
11615
11616
11617		mxGraphView.prototype.formatUnitText = function(pixels)
11618		{
11619			return pixels? formatHintText(pixels, this.unit) : pixels;
11620		};
11621
11622		/**
11623		 * Updates the hint for the current operation.
11624		 */
11625		mxGraphHandler.prototype.updateHint = function(me)
11626		{
11627			if (this.pBounds != null && (this.shape != null || this.livePreviewActive))
11628			{
11629				if (this.hint == null)
11630				{
11631					this.hint = createHint();
11632					this.graph.container.appendChild(this.hint);
11633				}
11634
11635				var t = this.graph.view.translate;
11636				var s = this.graph.view.scale;
11637				var x = this.roundLength((this.bounds.x + this.currentDx) / s - t.x);
11638				var y = this.roundLength((this.bounds.y + this.currentDy) / s - t.y);
11639				var unit = this.graph.view.unit;
11640
11641				this.hint.innerHTML = formatHintText(x, unit) + ', ' + formatHintText(y, unit);
11642
11643				this.hint.style.left = (this.pBounds.x + this.currentDx +
11644					Math.round((this.pBounds.width - this.hint.clientWidth) / 2)) + 'px';
11645				this.hint.style.top = (this.pBounds.y + this.currentDy +
11646					this.pBounds.height + Editor.hintOffset) + 'px';
11647			}
11648		};
11649
11650		/**
11651		 * Updates the hint for the current operation.
11652		 */
11653		mxGraphHandler.prototype.removeHint = function()
11654		{
11655			if (this.hint != null)
11656			{
11657				this.hint.parentNode.removeChild(this.hint);
11658				this.hint = null;
11659			}
11660		};
11661
11662		/**
11663		 * Overridden to allow for shrinking pools when lanes are resized.
11664		 */
11665		var stackLayoutResizeCell = mxStackLayout.prototype.resizeCell;
11666		mxStackLayout.prototype.resizeCell = function(cell, bounds)
11667		{
11668			stackLayoutResizeCell.apply(this, arguments);
11669			var style = this.graph.getCellStyle(cell);
11670
11671			if (style['childLayout'] == null)
11672			{
11673				var parent = this.graph.model.getParent(cell);
11674				var geo = (parent != null) ? this.graph.getCellGeometry(parent) : null;
11675
11676				if (geo != null)
11677				{
11678					style = this.graph.getCellStyle(parent);
11679
11680					if (style['childLayout'] == 'stackLayout')
11681					{
11682						var border = parseFloat(mxUtils.getValue(style, 'stackBorder', mxStackLayout.prototype.border));
11683						var horizontal = mxUtils.getValue(style, 'horizontalStack', '1') == '1';
11684						var start = this.graph.getActualStartSize(parent);
11685						geo = geo.clone();
11686
11687						if (horizontal)
11688						{
11689							geo.height = bounds.height + start.y + start.height + 2 * border;
11690						}
11691						else
11692						{
11693							geo.width = bounds.width + start.x + start.width + 2 * border;
11694						}
11695
11696						this.graph.model.setGeometry(parent, geo);
11697					}
11698				}
11699			}
11700		};
11701
11702		/**
11703		 * Shows handle for table instead of rows and cells.
11704		 */
11705		var selectionCellsHandlerGetHandledSelectionCells = mxSelectionCellsHandler.prototype.getHandledSelectionCells;
11706		mxSelectionCellsHandler.prototype.getHandledSelectionCells = function()
11707		{
11708			var cells = selectionCellsHandlerGetHandledSelectionCells.apply(this, arguments);
11709			var dict = new mxDictionary();
11710			var model = this.graph.model;
11711			var result = [];
11712
11713			function addCell(cell)
11714			{
11715				if (!dict.get(cell))
11716				{
11717					dict.put(cell, true);
11718					result.push(cell);
11719				}
11720			};
11721
11722			for (var i = 0; i < cells.length; i++)
11723			{
11724				var cell = cells[i];
11725
11726				if (this.graph.isTableCell(cell))
11727				{
11728					addCell(model.getParent(model.getParent(cell)));
11729				}
11730				else if (this.graph.isTableRow(cell))
11731				{
11732					addCell(model.getParent(cell));
11733				}
11734
11735				addCell(cell);
11736			}
11737
11738			return result;
11739		};
11740
11741		/**
11742		 * Creates the shape used to draw the selection border.
11743		 */
11744		var vertexHandlerCreateParentHighlightShape = mxVertexHandler.prototype.createParentHighlightShape;
11745		mxVertexHandler.prototype.createParentHighlightShape = function(bounds)
11746		{
11747			var shape = vertexHandlerCreateParentHighlightShape.apply(this, arguments);
11748
11749			shape.stroke = '#C0C0C0';
11750			shape.strokewidth = 1;
11751
11752			return shape;
11753		};
11754
11755		/**
11756		 * Creates the shape used to draw the selection border.
11757		 */
11758		var edgeHandlerCreateParentHighlightShape = mxEdgeHandler.prototype.createParentHighlightShape;
11759		mxEdgeHandler.prototype.createParentHighlightShape = function(bounds)
11760		{
11761			var shape = edgeHandlerCreateParentHighlightShape.apply(this, arguments);
11762
11763			shape.stroke = '#C0C0C0';
11764			shape.strokewidth = 1;
11765
11766			return shape;
11767		};
11768
11769		/**
11770		 * Moves rotation handle to top, right corner.
11771		 */
11772		mxVertexHandler.prototype.rotationHandleVSpacing = -12;
11773		mxVertexHandler.prototype.getRotationHandlePosition = function()
11774		{
11775			var padding = this.getHandlePadding();
11776
11777			return new mxPoint(this.bounds.x + this.bounds.width - this.rotationHandleVSpacing + padding.x / 2,
11778				this.bounds.y + this.rotationHandleVSpacing - padding.y / 2)
11779		};
11780
11781		/**
11782		 * Enables recursive resize for groups.
11783		 */
11784		mxVertexHandler.prototype.isRecursiveResize = function(state, me)
11785		{
11786			return this.graph.isRecursiveVertexResize(state) &&
11787				!mxEvent.isControlDown(me.getEvent());
11788		};
11789
11790		/**
11791		 * Enables centered resize events.
11792		 */
11793		mxVertexHandler.prototype.isCenteredEvent = function(state, me)
11794		{
11795			return (!(!this.graph.isSwimlane(state.cell) && this.graph.model.getChildCount(state.cell) > 0 &&
11796					!this.graph.isCellCollapsed(state.cell) &&
11797					mxUtils.getValue(state.style, 'recursiveResize', '1') == '1' &&
11798					mxUtils.getValue(state.style, 'childLayout', null) == null) &&
11799					mxEvent.isControlDown(me.getEvent())) ||
11800				mxEvent.isMetaDown(me.getEvent());
11801		};
11802
11803		/**
11804		 * Hides rotation handle for table cells and rows.
11805		 */
11806		var vertexHandlerIsRotationHandleVisible = mxVertexHandler.prototype.isRotationHandleVisible;
11807		mxVertexHandler.prototype.isRotationHandleVisible = function()
11808		{
11809			return vertexHandlerIsRotationHandleVisible.apply(this, arguments)  &&
11810				!this.graph.isTableCell(this.state.cell) &&
11811				!this.graph.isTableRow(this.state.cell) &&
11812				!this.graph.isTable(this.state.cell);
11813		};
11814
11815		/**
11816		 * Hides rotation handle for table cells and rows.
11817		 */
11818		mxVertexHandler.prototype.getSizerBounds = function()
11819		{
11820			if (this.graph.isTableCell(this.state.cell))
11821			{
11822				return this.graph.view.getState(this.graph.model.getParent(this.graph.model.getParent(this.state.cell)));
11823			}
11824			else
11825			{
11826				return this.bounds;
11827			}
11828		};
11829
11830		/**
11831		 * Hides rotation handle for table cells and rows.
11832		 */
11833		var vertexHandlerIsParentHighlightVisible = mxVertexHandler.prototype.isParentHighlightVisible;
11834		mxVertexHandler.prototype.isParentHighlightVisible = function()
11835		{
11836			return vertexHandlerIsParentHighlightVisible.apply(this, arguments) &&
11837				!this.graph.isTableCell(this.state.cell) &&
11838				!this.graph.isTableRow(this.state.cell);
11839		};
11840
11841		/**
11842		 * Hides rotation handle for table cells and rows.
11843		 */
11844		var vertexHandlerIsCustomHandleVisible = mxVertexHandler.prototype.isCustomHandleVisible;
11845		mxVertexHandler.prototype.isCustomHandleVisible = function(handle)
11846		{
11847			return handle.tableHandle ||
11848				(vertexHandlerIsCustomHandleVisible.apply(this, arguments) &&
11849				(!this.graph.isTable(this.state.cell) ||
11850				this.graph.isCellSelected(this.state.cell)));
11851		};
11852
11853		/**
11854		 * Adds selection border inset for table cells and rows.
11855		 */
11856		mxVertexHandler.prototype.getSelectionBorderInset = function()
11857		{
11858			var result = 0;
11859
11860			if (this.graph.isTableRow(this.state.cell))
11861			{
11862				result = 1;
11863			}
11864			else if (this.graph.isTableCell(this.state.cell))
11865			{
11866				result = 2;
11867			}
11868
11869			return result;
11870		};
11871
11872		/**
11873		 * Adds custom handles for table cells.
11874		 */
11875		var vertexHandlerGetSelectionBorderBounds = mxVertexHandler.prototype.getSelectionBorderBounds;
11876		mxVertexHandler.prototype.getSelectionBorderBounds = function()
11877		{
11878			return vertexHandlerGetSelectionBorderBounds.apply(this, arguments).grow(
11879					-this.getSelectionBorderInset());
11880		};
11881
11882		/**
11883		 * Adds custom handles for table cells.
11884		 */
11885		var vertexHandlerCreateCustomHandles = mxVertexHandler.prototype.createCustomHandles;
11886		mxVertexHandler.prototype.createCustomHandles = function()
11887		{
11888			var handles = vertexHandlerCreateCustomHandles.apply(this, arguments);
11889
11890			if (this.graph.isTable(this.state.cell))
11891			{
11892				var graph = this.graph;
11893				var model = graph.model;
11894				var tableState = this.state;
11895				var sel = this.selectionBorder;
11896				var self = this;
11897
11898				if (handles == null)
11899				{
11900					handles = [];
11901				}
11902
11903				// Adds handles for rows and columns
11904				var rows = graph.view.getCellStates(model.getChildCells(this.state.cell, true));
11905
11906				if (rows.length > 0)
11907				{
11908					var cols = model.getChildCells(rows[0].cell, true);
11909
11910					// Adds column width handles
11911					for (var i = 0; i < cols.length; i++)
11912					{
11913						(mxUtils.bind(this, function(index)
11914						{
11915							var colState = graph.view.getState(cols[index]);
11916							var geo = graph.getCellGeometry(cols[index]);
11917							var g = (geo.alternateBounds != null) ? geo.alternateBounds : geo;
11918
11919							if (colState == null)
11920							{
11921								colState = new mxCellState(graph.view, cols[index],
11922									graph.getCellStyle(cols[index]));
11923								colState.x = tableState.x + geo.x * graph.view.scale;
11924								colState.y = tableState.y + geo.y * graph.view.scale;
11925								colState.width = g.width * graph.view.scale;
11926								colState.height = g.height * graph.view.scale;
11927								colState.updateCachedBounds();
11928							}
11929
11930							var nextCol = (index < cols.length - 1) ? cols[index + 1] : null;
11931							var ngeo = (nextCol != null) ? graph.getCellGeometry(nextCol) : null;
11932							var ng = (ngeo != null && ngeo.alternateBounds != null) ? ngeo.alternateBounds : ngeo;
11933
11934							var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1, true);
11935							shape.isDashed = sel.isDashed;
11936
11937							// Workaround for event handling on overlapping cells with tolerance
11938							shape.svgStrokeTolerance++;
11939							var handle = new mxHandle(colState, 'col-resize', null, shape);
11940							handle.tableHandle = true;
11941							var dx = 0;
11942
11943							handle.shape.node.parentNode.insertBefore(handle.shape.node,
11944								handle.shape.node.parentNode.firstChild);
11945
11946							handle.redraw = function()
11947							{
11948								if (this.shape != null)
11949								{
11950									var start = graph.getActualStartSize(tableState.cell);
11951									this.shape.stroke = (dx == 0) ? mxConstants.NONE : sel.stroke;
11952									this.shape.bounds.x = this.state.x + (g.width +
11953										dx) * this.graph.view.scale;
11954									this.shape.bounds.width = 1;
11955									this.shape.bounds.y = tableState.y + ((index == cols.length - 1) ?
11956										0 : start.y * this.graph.view.scale);
11957									this.shape.bounds.height = tableState.height -
11958										((index == cols.length - 1) ? 0 :
11959										(start.height + start.y) * this.graph.view.scale);
11960									this.shape.redraw();
11961								}
11962							};
11963
11964							var shiftPressed = false;
11965
11966							handle.setPosition = function(bounds, pt, me)
11967							{
11968								dx = Math.max(Graph.minTableColumnWidth - g.width,
11969									pt.x - bounds.x - g.width);
11970								shiftPressed = mxEvent.isShiftDown(me.getEvent());
11971
11972								if (ng != null && !shiftPressed)
11973								{
11974									dx = Math.min(dx, ng.width - Graph.minTableColumnWidth);
11975								}
11976							};
11977
11978							handle.execute = function(me)
11979							{
11980								if (dx != 0)
11981								{
11982									graph.setTableColumnWidth(this.state.cell,
11983										dx, shiftPressed);
11984								}
11985								else if (!self.blockDelayedSelection)
11986								{
11987									var temp = graph.getCellAt(me.getGraphX(), me.getGraphY()) || tableState.cell;
11988									graph.graphHandler.selectCellForEvent(temp, me);
11989								}
11990
11991								dx = 0;
11992							};
11993
11994							// Stops repaint of text label via vertex handler
11995							handle.positionChanged = function()
11996							{
11997								// do nothing
11998							};
11999
12000							handle.reset = function()
12001							{
12002								dx = 0;
12003							};
12004
12005							handles.push(handle);
12006						}))(i);
12007					}
12008
12009					// Adds row height handles
12010					for (var i = 0; i < rows.length; i++)
12011					{
12012						(mxUtils.bind(this, function(index)
12013						{
12014							var rowState = rows[index];
12015
12016							var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1);
12017							shape.isDashed = sel.isDashed;
12018							shape.svgStrokeTolerance++;
12019
12020							var handle = new mxHandle(rowState, 'row-resize', null, shape);
12021							handle.tableHandle = true;
12022							var dy = 0;
12023
12024							handle.shape.node.parentNode.insertBefore(handle.shape.node,
12025								handle.shape.node.parentNode.firstChild);
12026
12027							handle.redraw = function()
12028							{
12029								if (this.shape != null && this.state.shape != null)
12030								{
12031									this.shape.stroke = (dy == 0) ? mxConstants.NONE : sel.stroke;
12032									this.shape.bounds.x = this.state.x;
12033									this.shape.bounds.width = this.state.width;
12034									this.shape.bounds.y = this.state.y + this.state.height +
12035										dy * this.graph.view.scale;
12036									this.shape.bounds.height = 1;
12037									this.shape.redraw();
12038								}
12039							};
12040
12041							handle.setPosition = function(bounds, pt, me)
12042							{
12043								dy = Math.max(Graph.minTableRowHeight - bounds.height,
12044									pt.y - bounds.y - bounds.height);
12045							};
12046
12047							handle.execute = function(me)
12048							{
12049								if (dy != 0)
12050								{
12051									graph.setTableRowHeight(this.state.cell, dy,
12052										!mxEvent.isShiftDown(me.getEvent()));
12053								}
12054								else if (!self.blockDelayedSelection)
12055								{
12056									var temp = graph.getCellAt(me.getGraphX(), me.getGraphY()) || tableState.cell;
12057									graph.graphHandler.selectCellForEvent(temp, me);
12058								}
12059
12060								dy = 0;
12061							};
12062
12063							handle.reset = function()
12064							{
12065								dy = 0;
12066							};
12067
12068							handles.push(handle);
12069						}))(i);
12070					}
12071				}
12072			}
12073
12074			// Reserve gives point handles precedence over line handles
12075			return (handles != null) ? handles.reverse() : null;
12076		};
12077
12078		var vertexHandlerSetHandlesVisible = mxVertexHandler.prototype.setHandlesVisible;
12079
12080		mxVertexHandler.prototype.setHandlesVisible = function(visible)
12081		{
12082			vertexHandlerSetHandlesVisible.apply(this, arguments);
12083
12084			if (this.moveHandles != null)
12085			{
12086				for (var i = 0; i < this.moveHandles.length; i++)
12087				{
12088					this.moveHandles[i].style.visibility = (visible) ? '' : 'hidden';
12089				}
12090			}
12091
12092			if (this.cornerHandles != null)
12093			{
12094				for (var i = 0; i < this.cornerHandles.length; i++)
12095				{
12096					this.cornerHandles[i].node.style.visibility = (visible) ? '' : 'hidden';
12097				}
12098			}
12099		};
12100
12101		/**
12102		 * Creates or updates special handles for moving rows.
12103		 */
12104		mxVertexHandler.prototype.refreshMoveHandles = function()
12105		{
12106			var graph = this.graph;
12107			var model = graph.model;
12108
12109			// Destroys existing handles
12110			if (this.moveHandles != null)
12111			{
12112				for (var i = 0; i < this.moveHandles.length; i++)
12113				{
12114					this.moveHandles[i].parentNode.removeChild(this.moveHandles[i]);
12115				}
12116
12117				this.moveHandles = null;
12118			}
12119
12120			// Creates new handles
12121			this.moveHandles = [];
12122
12123			for (var i = 0; i < model.getChildCount(this.state.cell); i++)
12124			{
12125				(mxUtils.bind(this, function(rowState)
12126				{
12127					if (rowState != null && model.isVertex(rowState.cell))
12128					{
12129						// Adds handle to move row
12130						// LATER: Move to overlay pane to hide during zoom but keep padding
12131						var moveHandle = mxUtils.createImage(Editor.rowMoveImage);
12132						moveHandle.style.position = 'absolute';
12133						moveHandle.style.cursor = 'pointer';
12134						moveHandle.style.width = '7px';
12135						moveHandle.style.height = '4px';
12136						moveHandle.style.padding = '4px 2px 4px 2px';
12137						moveHandle.rowState = rowState;
12138
12139						mxEvent.addGestureListeners(moveHandle, mxUtils.bind(this, function(evt)
12140						{
12141							this.graph.popupMenuHandler.hideMenu();
12142							this.graph.stopEditing(false);
12143
12144							if (this.graph.isToggleEvent(evt) ||
12145								!this.graph.isCellSelected(rowState.cell))
12146							{
12147								this.graph.selectCellForEvent(rowState.cell, evt);
12148							}
12149
12150							if (!mxEvent.isPopupTrigger(evt))
12151							{
12152								this.graph.graphHandler.start(this.state.cell,
12153									mxEvent.getClientX(evt), mxEvent.getClientY(evt),
12154									this.graph.getSelectionCells());
12155								this.graph.graphHandler.cellWasClicked = true;
12156								this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
12157								this.graph.isMouseDown = true;
12158							}
12159
12160							mxEvent.consume(evt);
12161						}), null, mxUtils.bind(this, function(evt)
12162						{
12163							if (mxEvent.isPopupTrigger(evt))
12164							{
12165								this.graph.popupMenuHandler.popup(mxEvent.getClientX(evt),
12166									mxEvent.getClientY(evt), rowState.cell, evt);
12167								mxEvent.consume(evt);
12168							}
12169						}));
12170
12171						this.moveHandles.push(moveHandle);
12172						this.graph.container.appendChild(moveHandle);
12173
12174					}
12175				}))(this.graph.view.getState(model.getChildAt(this.state.cell, i)));
12176			}
12177		};
12178
12179		/**
12180		 * Adds handle padding for editing cells and exceptions.
12181		 */
12182		mxVertexHandler.prototype.refresh = function()
12183		{
12184			if (this.customHandles != null)
12185			{
12186				for (var i = 0; i < this.customHandles.length; i++)
12187				{
12188					this.customHandles[i].destroy();
12189				}
12190
12191				this.customHandles = this.createCustomHandles();
12192			}
12193
12194			if (this.graph.isTable(this.state.cell))
12195			{
12196				this.refreshMoveHandles();
12197			}
12198		};
12199
12200		/**
12201		 * Adds handle padding for editing cells and exceptions.
12202		 */
12203		var vertexHandlerGetHandlePadding = mxVertexHandler.prototype.getHandlePadding;
12204		mxVertexHandler.prototype.getHandlePadding = function()
12205		{
12206			var result = new mxPoint(0, 0);
12207			var tol = this.tolerance;
12208			var name = this.state.style['shape'];
12209
12210			if (mxCellRenderer.defaultShapes[name] == null &&
12211				mxStencilRegistry.getStencil(name) == null)
12212			{
12213				name = mxConstants.SHAPE_RECTANGLE;
12214			}
12215
12216			// Checks if custom handles are overlapping with the shape border
12217			var handlePadding = this.graph.isTable(this.state.cell) ||
12218				this.graph.cellEditor.getEditingCell() == this.state.cell;
12219
12220			if (!handlePadding)
12221			{
12222				if (this.customHandles != null)
12223				{
12224					for (var i = 0; i < this.customHandles.length; i++)
12225					{
12226						if (this.customHandles[i].shape != null &&
12227							this.customHandles[i].shape.bounds != null)
12228						{
12229							var b = this.customHandles[i].shape.bounds;
12230							var px = b.getCenterX();
12231							var py = b.getCenterY();
12232
12233							if ((Math.abs(this.state.x - px) < b.width / 2) ||
12234								(Math.abs(this.state.y - py) < b.height / 2) ||
12235								(Math.abs(this.state.x + this.state.width - px) < b.width / 2) ||
12236								(Math.abs(this.state.y + this.state.height - py) < b.height / 2))
12237							{
12238								handlePadding = true;
12239								break;
12240							}
12241						}
12242					}
12243				}
12244			}
12245
12246			if (handlePadding && this.sizers != null &&
12247				this.sizers.length > 0 && this.sizers[0] != null)
12248			{
12249				tol /= 2;
12250
12251				// Makes room for row move handle
12252				if (this.graph.isTable(this.state.cell))
12253				{
12254					tol += 7;
12255				}
12256
12257				result.x = this.sizers[0].bounds.width + tol;
12258				result.y = this.sizers[0].bounds.height + tol;
12259			}
12260			else
12261			{
12262				result = vertexHandlerGetHandlePadding.apply(this, arguments);
12263			}
12264
12265			return result;
12266		};
12267
12268		/**
12269		 * Updates the hint for the current operation.
12270		 */
12271		mxVertexHandler.prototype.updateHint = function(me)
12272		{
12273			if (this.index != mxEvent.LABEL_HANDLE)
12274			{
12275				if (this.hint == null)
12276				{
12277					this.hint = createHint();
12278					this.state.view.graph.container.appendChild(this.hint);
12279				}
12280
12281				if (this.index == mxEvent.ROTATION_HANDLE)
12282				{
12283					this.hint.innerHTML = this.currentAlpha + '&deg;';
12284				}
12285				else
12286				{
12287					var s = this.state.view.scale;
12288					var unit = this.state.view.unit;
12289					this.hint.innerHTML = formatHintText(this.roundLength(this.bounds.width / s), unit) + ' x ' +
12290						formatHintText(this.roundLength(this.bounds.height / s), unit);
12291				}
12292
12293				var rot = (this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0';
12294				var bb = mxUtils.getBoundingBox(this.bounds, rot);
12295
12296				if (bb == null)
12297				{
12298					bb = this.bounds;
12299				}
12300
12301				this.hint.style.left = bb.x + Math.round((bb.width - this.hint.clientWidth) / 2) + 'px';
12302				this.hint.style.top = (bb.y + bb.height + Editor.hintOffset) + 'px';
12303
12304				if (this.linkHint != null)
12305				{
12306					this.linkHint.style.display = 'none';
12307				}
12308			}
12309		};
12310
12311		/**
12312		 * Updates the hint for the current operation.
12313		 */
12314		mxVertexHandler.prototype.removeHint = function()
12315		{
12316			mxGraphHandler.prototype.removeHint.apply(this, arguments);
12317
12318			if (this.linkHint != null)
12319			{
12320				this.linkHint.style.display = '';
12321			}
12322		};
12323
12324		/**
12325		 * Hides link hint while moving cells.
12326		 */
12327		var edgeHandlerMouseMove = mxEdgeHandler.prototype.mouseMove;
12328
12329		mxEdgeHandler.prototype.mouseMove = function(sender, me)
12330		{
12331			edgeHandlerMouseMove.apply(this, arguments);
12332
12333			if (this.linkHint != null && this.linkHint.style.display != 'none' &&
12334				this.graph.graphHandler != null && this.graph.graphHandler.first != null)
12335			{
12336				this.linkHint.style.display = 'none';
12337			}
12338		}
12339
12340		/**
12341		 * Hides link hint while moving cells.
12342		 */
12343		var edgeHandlerMouseUp = mxEdgeHandler.prototype.mouseUp;
12344
12345		mxEdgeHandler.prototype.mouseUp = function(sender, me)
12346		{
12347			edgeHandlerMouseUp.apply(this, arguments);
12348
12349			if (this.linkHint != null && this.linkHint.style.display == 'none')
12350			{
12351				this.linkHint.style.display = '';
12352			}
12353		}
12354
12355		/**
12356		 * Updates the hint for the current operation.
12357		 */
12358		mxEdgeHandler.prototype.updateHint = function(me, point)
12359		{
12360			if (this.hint == null)
12361			{
12362				this.hint = createHint();
12363				this.state.view.graph.container.appendChild(this.hint);
12364			}
12365
12366			var t = this.graph.view.translate;
12367			var s = this.graph.view.scale;
12368			var x = this.roundLength(point.x / s - t.x);
12369			var y = this.roundLength(point.y / s - t.y);
12370			var unit = this.graph.view.unit;
12371
12372			this.hint.innerHTML = formatHintText(x, unit) + ', ' + formatHintText(y, unit);
12373			this.hint.style.visibility = 'visible';
12374
12375			if (this.isSource || this.isTarget)
12376			{
12377				if (this.constraintHandler.currentConstraint != null &&
12378					this.constraintHandler.currentFocus != null)
12379				{
12380					var pt = this.constraintHandler.currentConstraint.point;
12381					this.hint.innerHTML = '[' + Math.round(pt.x * 100) + '%, '+ Math.round(pt.y * 100) + '%]';
12382				}
12383				else if (this.marker.hasValidState())
12384				{
12385					this.hint.style.visibility = 'hidden';
12386				}
12387			}
12388
12389			this.hint.style.left = Math.round(me.getGraphX() - this.hint.clientWidth / 2) + 'px';
12390			this.hint.style.top = (Math.max(me.getGraphY(), point.y) + Editor.hintOffset) + 'px';
12391
12392			if (this.linkHint != null)
12393			{
12394				this.linkHint.style.display = 'none';
12395			}
12396		};
12397
12398		/**
12399		 * Updates the hint for the current operation.
12400		 */
12401		mxEdgeHandler.prototype.removeHint = mxVertexHandler.prototype.removeHint;
12402
12403		/**
12404		 * Defines the handles for the UI. Uses data-URIs to speed-up loading time where supported.
12405		 */
12406		// TODO: Increase handle padding
12407		HoverIcons.prototype.mainHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-main.png', 17, 17) :
12408			Graph.createSvgImage(18, 18, '<circle cx="9" cy="9" r="5" stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '" stroke-width="1"/>');
12409		HoverIcons.prototype.secondaryHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-secondary.png', 17, 17) :
12410			Graph.createSvgImage(16, 16, '<path d="m 8 3 L 13 8 L 8 13 L 3 8 z" stroke="#fff" fill="#fca000"/>');
12411		HoverIcons.prototype.fixedHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-fixed.png', 17, 17) :
12412			Graph.createSvgImage(22, 22, '<circle cx="11" cy="11" r="6" stroke="#fff" fill="#01bd22" stroke-width="1"/><path d="m 8 8 L 14 14M 8 14 L 14 8" stroke="#fff"/>');
12413		HoverIcons.prototype.terminalHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-terminal.png', 17, 17) :
12414			Graph.createSvgImage(22, 22, '<circle cx="11" cy="11" r="6" stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '" stroke-width="1"/><circle cx="11" cy="11" r="3" stroke="#fff" fill="transparent"/>');
12415		HoverIcons.prototype.rotationHandle = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/handle-rotate.png', 16, 16) :
12416			Graph.createSvgImage(16, 16, '<path stroke="' + HoverIcons.prototype.arrowFill +
12417				'" fill="' + HoverIcons.prototype.arrowFill +
12418				'" d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"/>',
12419				24, 24);
12420
12421		if (mxClient.IS_SVG)
12422		{
12423			mxConstraintHandler.prototype.pointImage = Graph.createSvgImage(5, 5,
12424				'<path d="m 0 0 L 5 5 M 0 5 L 5 0" stroke-width="2" style="stroke-opacity:0.4" stroke="#ffffff"/>' +
12425				'<path d="m 0 0 L 5 5 M 0 5 L 5 0" stroke="' + HoverIcons.prototype.arrowFill + '"/>');
12426		}
12427
12428		mxVertexHandler.TABLE_HANDLE_COLOR = '#fca000';
12429		mxVertexHandler.prototype.handleImage = HoverIcons.prototype.mainHandle;
12430		mxVertexHandler.prototype.secondaryHandleImage = HoverIcons.prototype.secondaryHandle;
12431		mxEdgeHandler.prototype.handleImage = HoverIcons.prototype.mainHandle;
12432		mxEdgeHandler.prototype.terminalHandleImage = HoverIcons.prototype.terminalHandle;
12433		mxEdgeHandler.prototype.fixedHandleImage = HoverIcons.prototype.fixedHandle;
12434		mxEdgeHandler.prototype.labelHandleImage = HoverIcons.prototype.secondaryHandle;
12435		mxOutline.prototype.sizerImage = HoverIcons.prototype.mainHandle;
12436
12437		if (window.Sidebar != null)
12438		{
12439			Sidebar.prototype.triangleUp = HoverIcons.prototype.triangleUp;
12440			Sidebar.prototype.triangleRight = HoverIcons.prototype.triangleRight;
12441			Sidebar.prototype.triangleDown = HoverIcons.prototype.triangleDown;
12442			Sidebar.prototype.triangleLeft = HoverIcons.prototype.triangleLeft;
12443			Sidebar.prototype.refreshTarget = HoverIcons.prototype.refreshTarget;
12444			Sidebar.prototype.roundDrop = HoverIcons.prototype.roundDrop;
12445		}
12446
12447		// Pre-fetches images (only needed for non data-uris)
12448		if (!mxClient.IS_SVG)
12449		{
12450			new Image().src = HoverIcons.prototype.mainHandle.src;
12451			new Image().src = HoverIcons.prototype.fixedHandle.src;
12452			new Image().src = HoverIcons.prototype.terminalHandle.src;
12453			new Image().src = HoverIcons.prototype.secondaryHandle.src;
12454			new Image().src = HoverIcons.prototype.rotationHandle.src;
12455
12456			new Image().src = HoverIcons.prototype.triangleUp.src;
12457			new Image().src = HoverIcons.prototype.triangleRight.src;
12458			new Image().src = HoverIcons.prototype.triangleDown.src;
12459			new Image().src = HoverIcons.prototype.triangleLeft.src;
12460			new Image().src = HoverIcons.prototype.refreshTarget.src;
12461			new Image().src = HoverIcons.prototype.roundDrop.src;
12462		}
12463
12464		// Adds rotation handle and live preview
12465		mxVertexHandler.prototype.rotationEnabled = true;
12466		mxVertexHandler.prototype.manageSizers = true;
12467		mxVertexHandler.prototype.livePreview = true;
12468		mxGraphHandler.prototype.maxLivePreview = 16;
12469
12470		// Increases default rubberband opacity (default is 20)
12471		mxRubberband.prototype.defaultOpacity = 30;
12472
12473		// Enables connections along the outline, virtual waypoints, parent highlight etc
12474		mxConnectionHandler.prototype.outlineConnect = true;
12475		mxCellHighlight.prototype.keepOnTop = true;
12476		mxVertexHandler.prototype.parentHighlightEnabled = true;
12477
12478		mxEdgeHandler.prototype.parentHighlightEnabled = true;
12479		mxEdgeHandler.prototype.dblClickRemoveEnabled = true;
12480		mxEdgeHandler.prototype.straightRemoveEnabled = true;
12481		mxEdgeHandler.prototype.virtualBendsEnabled = true;
12482		mxEdgeHandler.prototype.mergeRemoveEnabled = true;
12483		mxEdgeHandler.prototype.manageLabelHandle = true;
12484		mxEdgeHandler.prototype.outlineConnect = true;
12485
12486		// Disables adding waypoints if shift is pressed
12487		mxEdgeHandler.prototype.isAddVirtualBendEvent = function(me)
12488		{
12489			return !mxEvent.isShiftDown(me.getEvent());
12490		};
12491
12492		// Disables custom handles if shift is pressed
12493		mxEdgeHandler.prototype.isCustomHandleEvent = function(me)
12494		{
12495			return !mxEvent.isShiftDown(me.getEvent());
12496		};
12497
12498		/**
12499		 * Implements touch style
12500		 */
12501		if (Graph.touchStyle)
12502		{
12503			// Larger tolerance for real touch devices
12504			if (mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0)
12505			{
12506				mxShape.prototype.svgStrokeTolerance = 18;
12507				mxVertexHandler.prototype.tolerance = 12;
12508				mxEdgeHandler.prototype.tolerance = 12;
12509				Graph.prototype.tolerance = 12;
12510
12511				mxVertexHandler.prototype.rotationHandleVSpacing = -16;
12512
12513				// Implements a smaller tolerance for mouse events and a larger tolerance for touch
12514				// events on touch devices. The default tolerance (4px) is used for mouse events.
12515				mxConstraintHandler.prototype.getTolerance = function(me)
12516				{
12517					return (mxEvent.isMouseEvent(me.getEvent())) ? 4 : this.graph.getTolerance();
12518				};
12519			}
12520
12521			// One finger pans (no rubberband selection) must start regardless of mouse button
12522			mxPanningHandler.prototype.isPanningTrigger = function(me)
12523			{
12524				var evt = me.getEvent();
12525
12526			 	return (me.getState() == null && !mxEvent.isMouseEvent(evt)) ||
12527			 		(mxEvent.isPopupTrigger(evt) && (me.getState() == null ||
12528			 		mxEvent.isControlDown(evt) || mxEvent.isShiftDown(evt)));
12529			};
12530
12531			// Don't clear selection if multiple cells selected
12532			var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown;
12533			mxGraphHandler.prototype.mouseDown = function(sender, me)
12534			{
12535				graphHandlerMouseDown.apply(this, arguments);
12536
12537				if (mxEvent.isTouchEvent(me.getEvent()) && this.graph.isCellSelected(me.getCell()) &&
12538					this.graph.getSelectionCount() > 1)
12539				{
12540					this.delayedSelection = false;
12541				}
12542			};
12543		}
12544		else
12545		{
12546			// Removes ctrl+shift as panning trigger for space splitting
12547			mxPanningHandler.prototype.isPanningTrigger = function(me)
12548			{
12549				var evt = me.getEvent();
12550
12551				return (mxEvent.isLeftMouseButton(evt) && ((this.useLeftButtonForPanning &&
12552						me.getState() == null) || (mxEvent.isControlDown(evt) &&
12553						!mxEvent.isShiftDown(evt)))) || (this.usePopupTrigger &&
12554						mxEvent.isPopupTrigger(evt));
12555			};
12556		}
12557
12558		// Overrides/extends rubberband for space handling with Ctrl+Shift(+Alt) drag ("scissors tool")
12559		mxRubberband.prototype.isSpaceEvent = function(me)
12560		{
12561			return this.graph.isEnabled() && !this.graph.isCellLocked(this.graph.getDefaultParent()) &&
12562				mxEvent.isControlDown(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) &&
12563				mxEvent.isAltDown(me.getEvent());
12564		};
12565
12566		// Cancelled state
12567		mxRubberband.prototype.cancelled = false;
12568
12569		// Cancels ongoing rubberband selection but consumed event to avoid reset of selection
12570		mxRubberband.prototype.cancel = function()
12571		{
12572			if (this.isActive())
12573			{
12574				this.cancelled = true;
12575				this.reset();
12576			}
12577		};
12578
12579		// Handles moving of cells in both half panes
12580		mxRubberband.prototype.mouseUp = function(sender, me)
12581		{
12582			if (this.cancelled)
12583			{
12584				this.cancelled = false;
12585				me.consume();
12586			}
12587			else
12588			{
12589				var execute = this.div != null && this.div.style.display != 'none';
12590
12591				var x0 = null;
12592				var y0 = null;
12593				var dx = null;
12594				var dy = null;
12595
12596				if (this.first != null && this.currentX != null && this.currentY != null)
12597				{
12598					x0 = this.first.x;
12599					y0 = this.first.y;
12600					dx = (this.currentX - x0) / this.graph.view.scale;
12601					dy = (this.currentY - y0) / this.graph.view.scale;
12602
12603					if (!mxEvent.isAltDown(me.getEvent()))
12604					{
12605						dx = this.graph.snap(dx);
12606						dy = this.graph.snap(dy);
12607
12608						if (!this.graph.isGridEnabled())
12609						{
12610							if (Math.abs(dx) < this.graph.tolerance)
12611							{
12612								dx = 0;
12613							}
12614
12615							if (Math.abs(dy) < this.graph.tolerance)
12616							{
12617								dy = 0;
12618							}
12619						}
12620					}
12621				}
12622
12623				this.reset();
12624
12625				if (execute)
12626				{
12627					if (this.isSpaceEvent(me))
12628					{
12629						this.graph.model.beginUpdate();
12630						try
12631						{
12632							var cells = this.graph.getCellsBeyond(x0, y0, this.graph.getDefaultParent(), true, true);
12633
12634							for (var i = 0; i < cells.length; i++)
12635							{
12636								if (this.graph.isCellMovable(cells[i]))
12637								{
12638									var tmp = this.graph.view.getState(cells[i]);
12639									var geo = this.graph.getCellGeometry(cells[i]);
12640
12641									if (tmp != null && geo != null)
12642									{
12643										geo = geo.clone();
12644										geo.translate(dx, dy);
12645										this.graph.model.setGeometry(cells[i], geo);
12646									}
12647								}
12648							}
12649						}
12650						finally
12651						{
12652							this.graph.model.endUpdate();
12653						}
12654					}
12655					else
12656					{
12657						var rect = new mxRectangle(this.x, this.y, this.width, this.height);
12658						this.graph.selectRegion(rect, me.getEvent());
12659					}
12660
12661					me.consume();
12662				}
12663			}
12664		};
12665
12666		// Handles preview for creating/removing space in diagram
12667		mxRubberband.prototype.mouseMove = function(sender, me)
12668		{
12669			if (!me.isConsumed() && this.first != null)
12670			{
12671				var origin = mxUtils.getScrollOrigin(this.graph.container);
12672				var offset = mxUtils.getOffset(this.graph.container);
12673				origin.x -= offset.x;
12674				origin.y -= offset.y;
12675				var x = me.getX() + origin.x;
12676				var y = me.getY() + origin.y;
12677				var dx = this.first.x - x;
12678				var dy = this.first.y - y;
12679				var tol = this.graph.tolerance;
12680
12681				if (this.div != null || Math.abs(dx) > tol ||  Math.abs(dy) > tol)
12682				{
12683					if (this.div == null)
12684					{
12685						this.div = this.createShape();
12686					}
12687
12688					// Clears selection while rubberbanding. This is required because
12689					// the event is not consumed in mouseDown.
12690					mxUtils.clearSelection();
12691					this.update(x, y);
12692
12693					if (this.isSpaceEvent(me))
12694					{
12695						var right = this.x + this.width;
12696						var bottom = this.y + this.height;
12697						var scale = this.graph.view.scale;
12698
12699						if (!mxEvent.isAltDown(me.getEvent()))
12700						{
12701							this.width = this.graph.snap(this.width / scale) * scale;
12702							this.height = this.graph.snap(this.height / scale) * scale;
12703
12704							if (!this.graph.isGridEnabled())
12705							{
12706								if (this.width < this.graph.tolerance)
12707								{
12708									this.width = 0;
12709								}
12710
12711								if (this.height < this.graph.tolerance)
12712								{
12713									this.height = 0;
12714								}
12715							}
12716
12717							if (this.x < this.first.x)
12718							{
12719								this.x = right - this.width;
12720							}
12721
12722							if (this.y < this.first.y)
12723							{
12724								this.y = bottom - this.height;
12725							}
12726						}
12727
12728						this.div.style.borderStyle = 'dashed';
12729						this.div.style.backgroundColor = 'white';
12730						this.div.style.left = this.x + 'px';
12731						this.div.style.top = this.y + 'px';
12732						this.div.style.width = Math.max(0, this.width) + 'px';
12733						this.div.style.height = this.graph.container.clientHeight + 'px';
12734						this.div.style.borderWidth = (this.width <= 0) ? '0px 1px 0px 0px' : '0px 1px 0px 1px';
12735
12736						if (this.secondDiv == null)
12737						{
12738							this.secondDiv = this.div.cloneNode(true);
12739							this.div.parentNode.appendChild(this.secondDiv);
12740						}
12741
12742						this.secondDiv.style.left = this.x + 'px';
12743						this.secondDiv.style.top = this.y + 'px';
12744						this.secondDiv.style.width = this.graph.container.clientWidth + 'px';
12745						this.secondDiv.style.height = Math.max(0, this.height) + 'px';
12746						this.secondDiv.style.borderWidth = (this.height <= 0) ? '1px 0px 0px 0px' : '1px 0px 1px 0px';
12747					}
12748					else
12749					{
12750						// Hides second div and restores style
12751						this.div.style.backgroundColor = '';
12752						this.div.style.borderWidth = '';
12753						this.div.style.borderStyle = '';
12754
12755						if (this.secondDiv != null)
12756						{
12757							this.secondDiv.parentNode.removeChild(this.secondDiv);
12758							this.secondDiv = null;
12759						}
12760					}
12761
12762					me.consume();
12763				}
12764			}
12765		};
12766
12767		// Removes preview
12768		var mxRubberbandReset = mxRubberband.prototype.reset;
12769		mxRubberband.prototype.reset = function()
12770		{
12771			if (this.secondDiv != null)
12772			{
12773				this.secondDiv.parentNode.removeChild(this.secondDiv);
12774				this.secondDiv = null;
12775			}
12776
12777			mxRubberbandReset.apply(this, arguments);
12778		};
12779
12780	    // Timer-based activation of outline connect in connection handler
12781	    var startTime = new Date().getTime();
12782	    var timeOnTarget = 0;
12783
12784		var mxEdgeHandlerUpdatePreviewState = mxEdgeHandler.prototype.updatePreviewState;
12785
12786		mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState, me)
12787		{
12788			mxEdgeHandlerUpdatePreviewState.apply(this, arguments);
12789
12790	    	if (terminalState != this.currentTerminalState)
12791	    	{
12792	    		startTime = new Date().getTime();
12793	    		timeOnTarget = 0;
12794	    	}
12795	    	else
12796	    	{
12797		    	timeOnTarget = new Date().getTime() - startTime;
12798	    	}
12799
12800			this.currentTerminalState = terminalState;
12801		};
12802
12803		// Timer-based outline connect
12804		var mxEdgeHandlerIsOutlineConnectEvent = mxEdgeHandler.prototype.isOutlineConnectEvent;
12805
12806		mxEdgeHandler.prototype.isOutlineConnectEvent = function(me)
12807		{
12808			return (this.currentTerminalState != null && me.getState() == this.currentTerminalState && timeOnTarget > 2000) ||
12809				((this.currentTerminalState == null || mxUtils.getValue(this.currentTerminalState.style, 'outlineConnect', '1') != '0') &&
12810				mxEdgeHandlerIsOutlineConnectEvent.apply(this, arguments));
12811		};
12812
12813		// Shows secondary handle for fixed connection points
12814		mxEdgeHandler.prototype.createHandleShape = function(index, virtual)
12815		{
12816			var source = index != null && index == 0;
12817			var terminalState = this.state.getVisibleTerminalState(source);
12818			var c = (index != null && (index == 0 || index >= this.state.absolutePoints.length - 1 ||
12819				(this.constructor == mxElbowEdgeHandler && index == 2))) ?
12820				this.graph.getConnectionConstraint(this.state, terminalState, source) : null;
12821			var pt = (c != null) ? this.graph.getConnectionPoint(this.state.getVisibleTerminalState(source), c) : null;
12822			var img = (pt != null) ? this.fixedHandleImage : ((c != null && terminalState != null) ?
12823				this.terminalHandleImage : this.handleImage);
12824
12825			if (img != null)
12826			{
12827				var shape = new mxImageShape(new mxRectangle(0, 0, img.width, img.height), img.src);
12828
12829				// Allows HTML rendering of the images
12830				shape.preserveImageAspect = false;
12831
12832				return shape;
12833			}
12834			else
12835			{
12836				var s = mxConstants.HANDLE_SIZE;
12837
12838				if (this.preferHtml)
12839				{
12840					s -= 1;
12841				}
12842
12843				return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
12844			}
12845		};
12846
12847		var vertexHandlerCreateSizerShape = mxVertexHandler.prototype.createSizerShape;
12848		mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor)
12849		{
12850			this.handleImage = (index == mxEvent.ROTATION_HANDLE) ? HoverIcons.prototype.rotationHandle : (index == mxEvent.LABEL_HANDLE) ? this.secondaryHandleImage : this.handleImage;
12851
12852			return vertexHandlerCreateSizerShape.apply(this, arguments);
12853		};
12854
12855		// Special case for single edge label handle moving in which case the text bounding box is used
12856		var mxGraphHandlerGetBoundingBox = mxGraphHandler.prototype.getBoundingBox;
12857		mxGraphHandler.prototype.getBoundingBox = function(cells)
12858		{
12859			if (cells != null && cells.length == 1)
12860			{
12861				var model = this.graph.getModel();
12862				var parent = model.getParent(cells[0]);
12863				var geo = this.graph.getCellGeometry(cells[0]);
12864
12865				if (model.isEdge(parent) && geo != null && geo.relative)
12866				{
12867					var state = this.graph.view.getState(cells[0]);
12868
12869					if (state != null && state.width < 2 && state.height < 2 && state.text != null &&
12870						state.text.boundingBox != null)
12871					{
12872						return mxRectangle.fromRectangle(state.text.boundingBox);
12873					}
12874				}
12875			}
12876
12877			return mxGraphHandlerGetBoundingBox.apply(this, arguments);
12878		};
12879
12880		// Ignores child cells with part style as guides
12881		var mxGraphHandlerGetGuideStates = mxGraphHandler.prototype.getGuideStates;
12882
12883		mxGraphHandler.prototype.getGuideStates = function()
12884		{
12885			var states = mxGraphHandlerGetGuideStates.apply(this, arguments);
12886			var result = [];
12887
12888			// NOTE: Could do via isStateIgnored hook
12889			for (var i = 0; i < states.length; i++)
12890			{
12891				if (mxUtils.getValue(states[i].style, 'part', '0') != '1')
12892				{
12893					result.push(states[i]);
12894				}
12895			}
12896
12897			return result;
12898		};
12899
12900		// Uses text bounding box for edge labels
12901		var mxVertexHandlerGetSelectionBounds = mxVertexHandler.prototype.getSelectionBounds;
12902		mxVertexHandler.prototype.getSelectionBounds = function(state)
12903		{
12904			var model = this.graph.getModel();
12905			var parent = model.getParent(state.cell);
12906			var geo = this.graph.getCellGeometry(state.cell);
12907
12908			if (model.isEdge(parent) && geo != null && geo.relative && state.width < 2 && state.height < 2 && state.text != null && state.text.boundingBox != null)
12909			{
12910				var bbox = state.text.unrotatedBoundingBox || state.text.boundingBox;
12911
12912				return new mxRectangle(Math.round(bbox.x), Math.round(bbox.y), Math.round(bbox.width), Math.round(bbox.height));
12913			}
12914			else
12915			{
12916				return mxVertexHandlerGetSelectionBounds.apply(this, arguments);
12917			}
12918		};
12919
12920		// Redirects moving of edge labels to mxGraphHandler by not starting here.
12921		// This will use the move preview of mxGraphHandler (see above).
12922		var mxVertexHandlerMouseDown = mxVertexHandler.prototype.mouseDown;
12923		mxVertexHandler.prototype.mouseDown = function(sender, me)
12924		{
12925			var model = this.graph.getModel();
12926			var parent = model.getParent(this.state.cell);
12927			var geo = this.graph.getCellGeometry(this.state.cell);
12928
12929			// Lets rotation events through
12930			var handle = this.getHandleForEvent(me);
12931
12932			if (handle == mxEvent.ROTATION_HANDLE || !model.isEdge(parent) || geo == null || !geo.relative ||
12933				this.state == null || this.state.width >= 2 || this.state.height >= 2)
12934			{
12935				mxVertexHandlerMouseDown.apply(this, arguments);
12936			}
12937		};
12938
12939		// Invokes turn on single click on rotation handle
12940		mxVertexHandler.prototype.rotateClick = function()
12941		{
12942			var stroke = mxUtils.getValue(this.state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE);
12943			var fill = mxUtils.getValue(this.state.style, mxConstants.STYLE_FILLCOLOR, mxConstants.NONE);
12944
12945			if (this.state.view.graph.model.isVertex(this.state.cell) &&
12946				stroke == mxConstants.NONE && fill == mxConstants.NONE)
12947			{
12948				var angle = mxUtils.mod(mxUtils.getValue(this.state.style, mxConstants.STYLE_ROTATION, 0) + 90, 360);
12949				this.state.view.graph.setCellStyles(mxConstants.STYLE_ROTATION, angle, [this.state.cell]);
12950			}
12951			else
12952			{
12953				this.state.view.graph.turnShapes([this.state.cell]);
12954			}
12955		};
12956
12957		var vertexHandlerMouseMove = mxVertexHandler.prototype.mouseMove;
12958
12959		// Workaround for "isConsumed not defined" in MS Edge is to use arguments
12960		mxVertexHandler.prototype.mouseMove = function(sender, me)
12961		{
12962			vertexHandlerMouseMove.apply(this, arguments);
12963
12964			if (this.graph.graphHandler.first != null)
12965			{
12966				if (this.rotationShape != null && this.rotationShape.node != null)
12967				{
12968					this.rotationShape.node.style.display = 'none';
12969				}
12970
12971				if (this.linkHint != null && this.linkHint.style.display != 'none')
12972				{
12973					this.linkHint.style.display = 'none';
12974				}
12975			}
12976		};
12977
12978		var vertexHandlerMouseUp = mxVertexHandler.prototype.mouseUp;
12979
12980		mxVertexHandler.prototype.mouseUp = function(sender, me)
12981		{
12982			vertexHandlerMouseUp.apply(this, arguments);
12983
12984			// Shows rotation handle only if one vertex is selected
12985			if (this.rotationShape != null && this.rotationShape.node != null)
12986			{
12987				this.rotationShape.node.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
12988			}
12989
12990			if (this.linkHint != null && this.linkHint.style.display == 'none')
12991			{
12992				this.linkHint.style.display = '';
12993			}
12994
12995			// Resets state after gesture
12996			this.blockDelayedSelection = null;
12997		};
12998
12999		var vertexHandlerInit = mxVertexHandler.prototype.init;
13000		mxVertexHandler.prototype.init = function()
13001		{
13002			vertexHandlerInit.apply(this, arguments);
13003			var redraw = false;
13004
13005			if (this.rotationShape != null)
13006			{
13007				this.rotationShape.node.setAttribute('title', mxResources.get('rotateTooltip'));
13008			}
13009
13010			if (this.graph.isTable(this.state.cell))
13011			{
13012				this.refreshMoveHandles();
13013			}
13014			// Draws corner rectangles for single selected table cells and rows
13015			else if (this.graph.getSelectionCount() == 1 &&
13016				(this.graph.isTableCell(this.state.cell) ||
13017				this.graph.isTableRow(this.state.cell)))
13018			{
13019				this.cornerHandles = [];
13020
13021				for (var i = 0; i < 4; i++)
13022				{
13023					var shape = new mxRectangleShape(new mxRectangle(0, 0, 6, 6),
13024						'#ffffff', mxConstants.HANDLE_STROKECOLOR);
13025					shape.dialect =  mxConstants.DIALECT_SVG;
13026					shape.init(this.graph.view.getOverlayPane());
13027					this.cornerHandles.push(shape);
13028				}
13029			}
13030
13031			var update = mxUtils.bind(this, function()
13032			{
13033				if (this.specialHandle != null)
13034				{
13035					this.specialHandle.node.style.display = (this.graph.isEnabled() &&
13036						this.graph.getSelectionCount() < this.graph.graphHandler.maxCells) ?
13037						'' : 'none';
13038				}
13039
13040				this.redrawHandles();
13041			});
13042
13043			this.changeHandler = mxUtils.bind(this, function(sender, evt)
13044			{
13045				this.updateLinkHint(this.graph.getLinkForCell(this.state.cell),
13046					this.graph.getLinksForState(this.state));
13047				update();
13048			});
13049
13050			this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.changeHandler);
13051			this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
13052
13053			// Repaint needed when editing stops and no change event is fired
13054			this.editingHandler = mxUtils.bind(this, function(sender, evt)
13055			{
13056				this.redrawHandles();
13057			});
13058
13059			this.graph.addListener(mxEvent.EDITING_STOPPED, this.editingHandler);
13060
13061			var link = this.graph.getLinkForCell(this.state.cell);
13062			var links = this.graph.getLinksForState(this.state);
13063			this.updateLinkHint(link, links);
13064
13065			if (link != null || (links != null && links.length > 0))
13066			{
13067				redraw = true;
13068			}
13069
13070			if (redraw)
13071			{
13072				this.redrawHandles();
13073			}
13074		};
13075
13076		mxVertexHandler.prototype.updateLinkHint = function(link, links)
13077		{
13078			try
13079			{
13080				if ((link == null && (links == null || links.length == 0)) ||
13081					this.graph.getSelectionCount() > 1)
13082				{
13083					if (this.linkHint != null)
13084					{
13085						this.linkHint.parentNode.removeChild(this.linkHint);
13086						this.linkHint = null;
13087					}
13088				}
13089				else if (link != null || (links != null && links.length > 0))
13090				{
13091					if (this.linkHint == null)
13092					{
13093						this.linkHint = createHint();
13094						this.linkHint.style.padding = '6px 8px 6px 8px';
13095						this.linkHint.style.opacity = '1';
13096						this.linkHint.style.filter = '';
13097
13098						this.graph.container.appendChild(this.linkHint);
13099
13100						mxEvent.addListener(this.linkHint, 'mouseenter', mxUtils.bind(this, function()
13101						{
13102							this.graph.tooltipHandler.hide();
13103						}));
13104					}
13105
13106					this.linkHint.innerHTML = '';
13107
13108					if (link != null)
13109					{
13110						this.linkHint.appendChild(this.graph.createLinkForHint(link));
13111
13112						if (this.graph.isEnabled() && typeof this.graph.editLink === 'function')
13113						{
13114							var changeLink = document.createElement('img');
13115							changeLink.setAttribute('src', Editor.editImage);
13116							changeLink.setAttribute('title', mxResources.get('editLink'));
13117							changeLink.setAttribute('width', '11');
13118							changeLink.setAttribute('height', '11');
13119							changeLink.style.marginLeft = '10px';
13120							changeLink.style.marginBottom = '-1px';
13121							changeLink.style.cursor = 'pointer';
13122							this.linkHint.appendChild(changeLink);
13123
13124							mxEvent.addListener(changeLink, 'click', mxUtils.bind(this, function(evt)
13125							{
13126								this.graph.setSelectionCell(this.state.cell);
13127								this.graph.editLink();
13128								mxEvent.consume(evt);
13129							}));
13130
13131							this.linkHint.appendChildGraph.createRemoveIcon(mxResources.get('removeIt',
13132								[mxResources.get('link')]), mxUtils.bind(this, function(evt)
13133							{
13134								this.graph.setLinkForCell(this.state.cell, null);
13135								mxEvent.consume(evt);
13136							}));
13137						}
13138					}
13139
13140					if (links != null)
13141					{
13142						for (var i = 0; i < links.length; i++)
13143						{
13144							var div = document.createElement('div');
13145							div.style.marginTop = (link != null || i > 0) ? '6px' : '0px';
13146							div.appendChild(this.graph.createLinkForHint(
13147								links[i].getAttribute('href'),
13148								mxUtils.getTextContent(links[i])));
13149
13150							this.linkHint.appendChild(div);
13151						}
13152					}
13153				}
13154			}
13155			catch (e)
13156			{
13157				// ignore
13158			}
13159		};
13160
13161		mxEdgeHandler.prototype.updateLinkHint = mxVertexHandler.prototype.updateLinkHint;
13162
13163		// Creates special handles
13164		var edgeHandlerInit = mxEdgeHandler.prototype.init;
13165		mxEdgeHandler.prototype.init = function()
13166		{
13167			edgeHandlerInit.apply(this, arguments);
13168
13169			// Disables connection points
13170			this.constraintHandler.isEnabled = mxUtils.bind(this, function()
13171			{
13172				return this.state.view.graph.connectionHandler.isEnabled();
13173			});
13174
13175			var update = mxUtils.bind(this, function()
13176			{
13177				if (this.linkHint != null)
13178				{
13179					this.linkHint.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
13180				}
13181
13182				if (this.labelShape != null)
13183				{
13184					this.labelShape.node.style.display = (this.graph.isEnabled() &&
13185						this.graph.getSelectionCount() < this.graph.graphHandler.maxCells) ?
13186						'' : 'none';
13187				}
13188			});
13189
13190			this.changeHandler = mxUtils.bind(this, function(sender, evt)
13191			{
13192				this.updateLinkHint(this.graph.getLinkForCell(this.state.cell),
13193					this.graph.getLinksForState(this.state));
13194				update();
13195				this.redrawHandles();
13196			});
13197
13198			this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.changeHandler);
13199			this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
13200
13201			var link = this.graph.getLinkForCell(this.state.cell);
13202			var links = this.graph.getLinksForState(this.state);
13203
13204			if (link != null || (links != null && links.length > 0))
13205			{
13206				this.updateLinkHint(link, links);
13207				this.redrawHandles();
13208			}
13209		};
13210
13211		// Disables connection points
13212		var connectionHandlerInit = mxConnectionHandler.prototype.init;
13213
13214		mxConnectionHandler.prototype.init = function()
13215		{
13216			connectionHandlerInit.apply(this, arguments);
13217
13218			this.constraintHandler.isEnabled = mxUtils.bind(this, function()
13219			{
13220				return this.graph.connectionHandler.isEnabled();
13221			});
13222		};
13223
13224		// Updates special handles
13225		var vertexHandlerRedrawHandles = mxVertexHandler.prototype.redrawHandles;
13226		mxVertexHandler.prototype.redrawHandles = function()
13227		{
13228			if (this.moveHandles != null)
13229			{
13230				for (var i = 0; i < this.moveHandles.length; i++)
13231				{
13232					this.moveHandles[i].style.left = (this.moveHandles[i].rowState.x +
13233						this.moveHandles[i].rowState.width - 5) + 'px';
13234					this.moveHandles[i].style.top = (this.moveHandles[i].rowState.y +
13235						this.moveHandles[i].rowState.height / 2 - 6) + 'px';
13236				}
13237			}
13238
13239			if (this.cornerHandles != null)
13240			{
13241				var inset = this.getSelectionBorderInset();
13242				var ch = this.cornerHandles;
13243				var w = ch[0].bounds.width / 2;
13244				var h = ch[0].bounds.height / 2;
13245
13246				ch[0].bounds.x = this.state.x - w + inset;
13247				ch[0].bounds.y = this.state.y - h + inset;
13248				ch[0].redraw();
13249				ch[1].bounds.x = ch[0].bounds.x + this.state.width - 2 * inset;
13250				ch[1].bounds.y = ch[0].bounds.y;
13251				ch[1].redraw();
13252				ch[2].bounds.x = ch[0].bounds.x;
13253				ch[2].bounds.y = this.state.y + this.state.height - 2 * inset;
13254				ch[2].redraw();
13255				ch[3].bounds.x = ch[1].bounds.x;
13256				ch[3].bounds.y = ch[2].bounds.y;
13257				ch[3].redraw();
13258
13259				for (var i = 0; i < this.cornerHandles.length; i++)
13260				{
13261					this.cornerHandles[i].node.style.display = (this.graph.getSelectionCount() == 1) ? '' : 'none';
13262				}
13263			}
13264
13265			// Shows rotation handle only if one vertex is selected
13266			if (this.rotationShape != null && this.rotationShape.node != null)
13267			{
13268				this.rotationShape.node.style.display = (this.moveHandles == null &&
13269					(this.graph.getSelectionCount() == 1 && (this.index == null ||
13270					this.index == mxEvent.ROTATION_HANDLE))) ? '' : 'none';
13271			}
13272
13273			vertexHandlerRedrawHandles.apply(this);
13274
13275			if (this.state != null && this.linkHint != null)
13276			{
13277				var c = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
13278				var tmp = new mxRectangle(this.state.x, this.state.y - 22, this.state.width + 24, this.state.height + 22);
13279				var bb = mxUtils.getBoundingBox(tmp, this.state.style[mxConstants.STYLE_ROTATION] || '0', c);
13280				var rs = (bb != null) ? mxUtils.getBoundingBox(this.state,
13281					this.state.style[mxConstants.STYLE_ROTATION] || '0') : this.state;
13282				var tb = (this.state.text != null) ? this.state.text.boundingBox : null;
13283
13284				if (bb == null)
13285				{
13286					bb = this.state;
13287				}
13288
13289				var b = bb.y + bb.height;
13290
13291				if (tb != null)
13292				{
13293					b = Math.max(b, tb.y + tb.height);
13294				}
13295
13296				this.linkHint.style.left = Math.max(0, Math.round(rs.x + (rs.width - this.linkHint.clientWidth) / 2)) + 'px';
13297				this.linkHint.style.top = Math.round(b + this.verticalOffset / 2 + Editor.hintOffset) + 'px';
13298			}
13299		};
13300
13301		// Destroys special handles
13302		var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
13303		mxVertexHandler.prototype.destroy = function()
13304		{
13305			vertexHandlerDestroy.apply(this, arguments);
13306
13307			if (this.moveHandles != null)
13308			{
13309				for (var i = 0; i < this.moveHandles.length; i++)
13310				{
13311					if (this.moveHandles[i] != null && this.moveHandles[i].parentNode != null)
13312					{
13313						this.moveHandles[i].parentNode.removeChild(this.moveHandles[i]);
13314					}
13315				}
13316
13317				this.moveHandles = null;
13318			}
13319
13320			if (this.cornerHandles != null)
13321			{
13322				for (var i = 0; i < this.cornerHandles.length; i++)
13323				{
13324					if (this.cornerHandles[i] != null && this.cornerHandles[i].node != null &&
13325						this.cornerHandles[i].node.parentNode != null)
13326					{
13327						this.cornerHandles[i].node.parentNode.removeChild(this.cornerHandles[i].node);
13328					}
13329				}
13330
13331				this.cornerHandles = null;
13332			}
13333
13334			if (this.linkHint != null)
13335			{
13336				if (this.linkHint.parentNode != null)
13337				{
13338					this.linkHint.parentNode.removeChild(this.linkHint);
13339				}
13340
13341				this.linkHint = null;
13342			}
13343
13344			if  (this.changeHandler != null)
13345			{
13346				this.graph.getSelectionModel().removeListener(this.changeHandler);
13347				this.graph.getModel().removeListener(this.changeHandler);
13348				this.changeHandler = null;
13349			}
13350
13351			if  (this.editingHandler != null)
13352			{
13353				this.graph.removeListener(this.editingHandler);
13354				this.editingHandler = null;
13355			}
13356		};
13357
13358		var edgeHandlerRedrawHandles = mxEdgeHandler.prototype.redrawHandles;
13359		mxEdgeHandler.prototype.redrawHandles = function()
13360		{
13361			// Workaround for special case where handler
13362			// is reset before this which leads to a NPE
13363			if (this.marker != null)
13364			{
13365				edgeHandlerRedrawHandles.apply(this);
13366
13367				if (this.state != null && this.linkHint != null)
13368				{
13369					var b = this.state;
13370
13371					if (this.state.text != null && this.state.text.bounds != null)
13372					{
13373						b = new mxRectangle(b.x, b.y, b.width, b.height);
13374						b.add(this.state.text.bounds);
13375					}
13376
13377					this.linkHint.style.left = Math.max(0, Math.round(b.x + (b.width - this.linkHint.clientWidth) / 2)) + 'px';
13378					this.linkHint.style.top = Math.round(b.y + b.height + Editor.hintOffset) + 'px';
13379				}
13380			}
13381		};
13382
13383		var edgeHandlerReset = mxEdgeHandler.prototype.reset;
13384		mxEdgeHandler.prototype.reset = function()
13385		{
13386			edgeHandlerReset.apply(this, arguments);
13387
13388			if (this.linkHint != null)
13389			{
13390				this.linkHint.style.visibility = '';
13391			}
13392		};
13393
13394		var edgeHandlerDestroy = mxEdgeHandler.prototype.destroy;
13395		mxEdgeHandler.prototype.destroy = function()
13396		{
13397			edgeHandlerDestroy.apply(this, arguments);
13398
13399			if (this.linkHint != null)
13400			{
13401				this.linkHint.parentNode.removeChild(this.linkHint);
13402				this.linkHint = null;
13403			}
13404
13405			if  (this.changeHandler != null)
13406			{
13407				this.graph.getModel().removeListener(this.changeHandler);
13408				this.graph.getSelectionModel().removeListener(this.changeHandler);
13409				this.changeHandler = null;
13410			}
13411		};
13412	})();
13413}
13414