1/**
2 * Copyright (c) 2006-2012, JGraph Ltd
3 */
4/**
5 * Constructs a new open dialog.
6 */
7var OpenDialog = function()
8{
9	var iframe = document.createElement('iframe');
10	iframe.style.backgroundColor = 'transparent';
11	iframe.allowTransparency = 'true';
12	iframe.style.borderStyle = 'none';
13	iframe.style.borderWidth = '0px';
14	iframe.style.overflow = 'hidden';
15	iframe.frameBorder = '0';
16
17	var dx = 0;
18	iframe.setAttribute('width', (((Editor.useLocalStorage) ? 640 : 320) + dx) + 'px');
19	iframe.setAttribute('height', (((Editor.useLocalStorage) ? 480 : 220) + dx) + 'px');
20	iframe.setAttribute('src', OPEN_FORM);
21
22	this.container = iframe;
23};
24
25/**
26 * Constructs a new color dialog.
27 */
28var ColorDialog = function(editorUi, color, apply, cancelFn)
29{
30	this.editorUi = editorUi;
31
32	var input = document.createElement('input');
33	input.style.marginBottom = '10px';
34
35	// Required for picker to render in IE
36	if (mxClient.IS_IE)
37	{
38		input.style.marginTop = '10px';
39		document.body.appendChild(input);
40	}
41
42	var applyFunction = (apply != null) ? apply : this.createApplyFunction();
43
44	function doApply()
45	{
46		var color = input.value;
47
48		// Blocks any non-alphabetic chars in colors
49		if (/(^#?[a-zA-Z0-9]*$)/.test(color))
50		{
51			if (color != 'none' && color.charAt(0) != '#')
52			{
53				color = '#' + color;
54			}
55
56			ColorDialog.addRecentColor((color != 'none') ? color.substring(1) : color, 12);
57			applyFunction(color);
58			editorUi.hideDialog();
59		}
60		else
61		{
62			editorUi.handleError({message: mxResources.get('invalidInput')});
63		}
64	};
65
66	this.init = function()
67	{
68		if (!mxClient.IS_TOUCH)
69		{
70			input.focus();
71		}
72	};
73
74	var picker = new mxJSColor.color(input);
75	picker.pickerOnfocus = false;
76	picker.showPicker();
77
78	var div = document.createElement('div');
79	mxJSColor.picker.box.style.position = 'relative';
80	mxJSColor.picker.box.style.width = '230px';
81	mxJSColor.picker.box.style.height = '100px';
82	mxJSColor.picker.box.style.paddingBottom = '10px';
83	div.appendChild(mxJSColor.picker.box);
84
85	var center = document.createElement('center');
86
87	function createRecentColorTable()
88	{
89		var table = addPresets((ColorDialog.recentColors.length == 0) ? ['FFFFFF'] :
90					ColorDialog.recentColors, 11, 'FFFFFF', true);
91		table.style.marginBottom = '8px';
92
93		return table;
94	};
95
96	var addPresets = mxUtils.bind(this, function(presets, rowLength, defaultColor, addResetOption)
97	{
98		rowLength = (rowLength != null) ? rowLength : 12;
99		var table = document.createElement('table');
100		table.style.borderCollapse = 'collapse';
101		table.setAttribute('cellspacing', '0');
102		table.style.marginBottom = '20px';
103		table.style.cellSpacing = '0px';
104		var tbody = document.createElement('tbody');
105		table.appendChild(tbody);
106
107		var rows = presets.length / rowLength;
108
109		for (var row = 0; row < rows; row++)
110		{
111			var tr = document.createElement('tr');
112
113			for (var i = 0; i < rowLength; i++)
114			{
115				(mxUtils.bind(this, function(clr)
116				{
117					var td = document.createElement('td');
118					td.style.border = '1px solid black';
119					td.style.padding = '0px';
120					td.style.width = '16px';
121					td.style.height = '16px';
122
123					if (clr == null)
124					{
125						clr = defaultColor;
126					}
127
128					if (clr == 'none')
129					{
130						td.style.background = 'url(\'' + Dialog.prototype.noColorImage + '\')';
131					}
132					else if (clr != null)
133					{
134						td.style.backgroundColor = '#' + clr;
135						var name = this.colorNames[clr.toUpperCase()];
136
137						if (name != null)
138						{
139							td.setAttribute('title', name);
140						}
141					}
142
143					tr.appendChild(td);
144
145					if (clr != null)
146					{
147						td.style.cursor = 'pointer';
148
149						mxEvent.addListener(td, 'click', function()
150						{
151							if (clr == 'none')
152							{
153								picker.fromString('ffffff');
154								input.value = 'none';
155							}
156							else
157							{
158								picker.fromString(clr);
159							}
160						});
161
162						mxEvent.addListener(td, 'dblclick', doApply);
163					}
164				}))(presets[row * rowLength + i]);
165			}
166
167			tbody.appendChild(tr);
168		}
169
170		if (addResetOption)
171		{
172			var td = document.createElement('td');
173			td.setAttribute('title', mxResources.get('reset'));
174			td.style.border = '1px solid black';
175			td.style.padding = '0px';
176			td.style.width = '16px';
177			td.style.height = '16px';
178			td.style.backgroundImage = 'url(\'' + Dialog.prototype.closeImage + '\')';
179			td.style.backgroundPosition = 'center center';
180			td.style.backgroundRepeat = 'no-repeat';
181			td.style.cursor = 'pointer';
182
183			tr.appendChild(td);
184
185			mxEvent.addListener(td, 'click', function()
186			{
187				ColorDialog.resetRecentColors();
188				table.parentNode.replaceChild(createRecentColorTable(), table);
189			});
190		}
191
192		center.appendChild(table);
193
194		return table;
195	});
196
197	div.appendChild(input);
198
199	if (!mxClient.IS_IE && !mxClient.IS_IE11)
200	{
201		input.style.width = '182px';
202
203		var clrInput = document.createElement('input');
204		clrInput.setAttribute('type', 'color');
205		clrInput.style.visibility = 'hidden';
206		clrInput.style.width = '0px';
207		clrInput.style.height = '0px';
208		clrInput.style.border = 'none';
209		clrInput.style.marginLeft = '2px';
210		div.style.whiteSpace = 'nowrap';
211		div.appendChild(clrInput);
212
213		div.appendChild(mxUtils.button('...', function()
214		{
215			// LATER: Check if clrInput is expanded
216			if (document.activeElement == clrInput)
217			{
218				input.focus();
219			}
220			else
221			{
222				clrInput.value = '#' + input.value;
223				clrInput.click();
224			}
225		}));
226
227		mxEvent.addListener(clrInput, 'input', function()
228		{
229			picker.fromString(clrInput.value.substring(1));
230		});
231	}
232	else
233	{
234		input.style.width = '216px';
235	}
236
237	mxUtils.br(div);
238
239	// Adds recent colors
240	createRecentColorTable();
241
242	// Adds presets
243	var table = addPresets(this.presetColors);
244	table.style.marginBottom = '8px';
245	table = addPresets(this.defaultColors);
246	table.style.marginBottom = '16px';
247
248	div.appendChild(center);
249
250	var buttons = document.createElement('div');
251	buttons.style.textAlign = 'right';
252	buttons.style.whiteSpace = 'nowrap';
253
254	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
255	{
256		editorUi.hideDialog();
257
258		if (cancelFn != null)
259		{
260			cancelFn();
261		}
262	});
263	cancelBtn.className = 'geBtn';
264
265	if (editorUi.editor.cancelFirst)
266	{
267		buttons.appendChild(cancelBtn);
268	}
269
270	var applyBtn = mxUtils.button(mxResources.get('apply'), doApply);
271	applyBtn.className = 'geBtn gePrimaryBtn';
272	buttons.appendChild(applyBtn);
273
274	if (!editorUi.editor.cancelFirst)
275	{
276		buttons.appendChild(cancelBtn);
277	}
278
279	if (color != null)
280	{
281		if (color == 'none')
282		{
283			picker.fromString('ffffff');
284			input.value = 'none';
285		}
286		else
287		{
288			picker.fromString(color);
289		}
290	}
291
292	div.appendChild(buttons);
293	this.picker = picker;
294	this.colorInput = input;
295
296	// LATER: Only fires if input if focused, should always
297	// fire if this dialog is showing.
298	mxEvent.addListener(div, 'keydown', function(e)
299	{
300		if (e.keyCode == 27)
301		{
302			editorUi.hideDialog();
303
304			if (cancelFn != null)
305			{
306				cancelFn();
307			}
308
309			mxEvent.consume(e);
310		}
311	});
312
313	this.container = div;
314};
315
316/**
317 * Creates function to apply value
318 */
319ColorDialog.prototype.presetColors = ['E6D0DE', 'CDA2BE', 'B5739D', 'E1D5E7', 'C3ABD0', 'A680B8', 'D4E1F5', 'A9C4EB', '7EA6E0', 'D5E8D4', '9AC7BF', '67AB9F', 'D5E8D4', 'B9E0A5', '97D077', 'FFF2CC', 'FFE599', 'FFD966', 'FFF4C3', 'FFCE9F', 'FFB570', 'F8CECC', 'F19C99', 'EA6B66'];
320
321/**
322 * Creates function to apply value
323 */
324 ColorDialog.prototype.colorNames = {};
325
326/**
327 * Creates function to apply value
328 */
329ColorDialog.prototype.defaultColors = ['none', 'FFFFFF', 'E6E6E6', 'CCCCCC', 'B3B3B3', '999999', '808080', '666666', '4D4D4D', '333333', '1A1A1A', '000000', 'FFCCCC', 'FFE6CC', 'FFFFCC', 'E6FFCC', 'CCFFCC', 'CCFFE6', 'CCFFFF', 'CCE5FF', 'CCCCFF', 'E5CCFF', 'FFCCFF', 'FFCCE6',
330		'FF9999', 'FFCC99', 'FFFF99', 'CCFF99', '99FF99', '99FFCC', '99FFFF', '99CCFF', '9999FF', 'CC99FF', 'FF99FF', 'FF99CC', 'FF6666', 'FFB366', 'FFFF66', 'B3FF66', '66FF66', '66FFB3', '66FFFF', '66B2FF', '6666FF', 'B266FF', 'FF66FF', 'FF66B3', 'FF3333', 'FF9933', 'FFFF33',
331		'99FF33', '33FF33', '33FF99', '33FFFF', '3399FF', '3333FF', '9933FF', 'FF33FF', 'FF3399', 'FF0000', 'FF8000', 'FFFF00', '80FF00', '00FF00', '00FF80', '00FFFF', '007FFF', '0000FF', '7F00FF', 'FF00FF', 'FF0080', 'CC0000', 'CC6600', 'CCCC00', '66CC00', '00CC00', '00CC66',
332		'00CCCC', '0066CC', '0000CC', '6600CC', 'CC00CC', 'CC0066', '990000', '994C00', '999900', '4D9900', '009900', '00994D', '009999', '004C99', '000099', '4C0099', '990099', '99004D', '660000', '663300', '666600', '336600', '006600', '006633', '006666', '003366', '000066',
333		'330066', '660066', '660033', '330000', '331A00', '333300', '1A3300', '003300', '00331A', '003333', '001933', '000033', '190033', '330033', '33001A'];
334
335/**
336 * Creates function to apply value
337 */
338ColorDialog.prototype.createApplyFunction = function()
339{
340	return mxUtils.bind(this, function(color)
341	{
342		var graph = this.editorUi.editor.graph;
343
344		graph.getModel().beginUpdate();
345		try
346		{
347			graph.setCellStyles(this.currentColorKey, color);
348			this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', [this.currentColorKey],
349				'values', [color], 'cells', graph.getSelectionCells()));
350		}
351		finally
352		{
353			graph.getModel().endUpdate();
354		}
355	});
356};
357
358/**
359 *
360 */
361ColorDialog.recentColors = [];
362
363/**
364 * Adds recent color for later use.
365 */
366ColorDialog.addRecentColor = function(color, max)
367{
368	if (color != null)
369	{
370		mxUtils.remove(color, ColorDialog.recentColors);
371		ColorDialog.recentColors.splice(0, 0, color);
372
373		if (ColorDialog.recentColors.length >= max)
374		{
375			ColorDialog.recentColors.pop();
376		}
377	}
378};
379
380/**
381 * Adds recent color for later use.
382 */
383ColorDialog.resetRecentColors = function()
384{
385	ColorDialog.recentColors = [];
386};
387
388/**
389 * Constructs a new about dialog.
390 */
391var AboutDialog = function(editorUi)
392{
393	var div = document.createElement('div');
394	div.setAttribute('align', 'center');
395	var h3 = document.createElement('h3');
396	mxUtils.write(h3, mxResources.get('about') + ' GraphEditor');
397	div.appendChild(h3);
398	var img = document.createElement('img');
399	img.style.border = '0px';
400	img.setAttribute('width', '176');
401	img.setAttribute('width', '151');
402	img.setAttribute('src', IMAGE_PATH + '/logo.png');
403	div.appendChild(img);
404	mxUtils.br(div);
405	mxUtils.write(div, 'Powered by mxGraph ' + mxClient.VERSION);
406	mxUtils.br(div);
407	var link = document.createElement('a');
408	link.setAttribute('href', 'http://www.jgraph.com/');
409	link.setAttribute('target', '_blank');
410	mxUtils.write(link, 'www.jgraph.com');
411	div.appendChild(link);
412	mxUtils.br(div);
413	mxUtils.br(div);
414	var closeBtn = mxUtils.button(mxResources.get('close'), function()
415	{
416		editorUi.hideDialog();
417	});
418	closeBtn.className = 'geBtn gePrimaryBtn';
419	div.appendChild(closeBtn);
420
421	this.container = div;
422};
423
424/**
425 * Constructs a new textarea dialog.
426 */
427var TextareaDialog = function(editorUi, title, url, fn, cancelFn, cancelTitle, w, h,
428	addButtons, noHide, noWrap, applyTitle, helpLink, customButtons)
429{
430	w = (w != null) ? w : 300;
431	h = (h != null) ? h : 120;
432	noHide = (noHide != null) ? noHide : false;
433	var row, td;
434
435	var table = document.createElement('table');
436	var tbody = document.createElement('tbody');
437
438	row = document.createElement('tr');
439
440	td = document.createElement('td');
441	td.style.fontSize = '10pt';
442	td.style.width = '100px';
443	mxUtils.write(td, title);
444
445	row.appendChild(td);
446	tbody.appendChild(row);
447
448	row = document.createElement('tr');
449	td = document.createElement('td');
450
451	var nameInput = document.createElement('textarea');
452
453	if (noWrap)
454	{
455		nameInput.setAttribute('wrap', 'off');
456	}
457
458	nameInput.setAttribute('spellcheck', 'false');
459	nameInput.setAttribute('autocorrect', 'off');
460	nameInput.setAttribute('autocomplete', 'off');
461	nameInput.setAttribute('autocapitalize', 'off');
462
463	mxUtils.write(nameInput, url || '');
464	nameInput.style.resize = 'none';
465	nameInput.style.width = w + 'px';
466	nameInput.style.height = h + 'px';
467
468	this.textarea = nameInput;
469
470	this.init = function()
471	{
472		nameInput.focus();
473		nameInput.scrollTop = 0;
474	};
475
476	td.appendChild(nameInput);
477	row.appendChild(td);
478
479	tbody.appendChild(row);
480
481	row = document.createElement('tr');
482	td = document.createElement('td');
483	td.style.paddingTop = '14px';
484	td.style.whiteSpace = 'nowrap';
485	td.setAttribute('align', 'right');
486
487	if (helpLink != null)
488	{
489		var helpBtn = mxUtils.button(mxResources.get('help'), function()
490		{
491			editorUi.editor.graph.openLink(helpLink);
492		});
493		helpBtn.className = 'geBtn';
494
495		td.appendChild(helpBtn);
496	}
497
498	if (customButtons != null)
499	{
500		for (var i = 0; i < customButtons.length; i++)
501		{
502			(function(label, fn)
503			{
504				var customBtn = mxUtils.button(label, function(e)
505				{
506					fn(e, nameInput);
507				});
508				customBtn.className = 'geBtn';
509
510				td.appendChild(customBtn);
511			})(customButtons[i][0], customButtons[i][1]);
512		}
513	}
514
515	var cancelBtn = mxUtils.button(cancelTitle || mxResources.get('cancel'), function()
516	{
517		editorUi.hideDialog();
518
519		if (cancelFn != null)
520		{
521			cancelFn();
522		}
523	});
524	cancelBtn.className = 'geBtn';
525
526	if (editorUi.editor.cancelFirst)
527	{
528		td.appendChild(cancelBtn);
529	}
530
531	if (addButtons != null)
532	{
533		addButtons(td, nameInput);
534	}
535
536	if (fn != null)
537	{
538		var genericBtn = mxUtils.button(applyTitle || mxResources.get('apply'), function()
539		{
540			if (!noHide)
541			{
542				editorUi.hideDialog();
543			}
544
545			fn(nameInput.value);
546		});
547
548		genericBtn.className = 'geBtn gePrimaryBtn';
549		td.appendChild(genericBtn);
550	}
551
552	if (!editorUi.editor.cancelFirst)
553	{
554		td.appendChild(cancelBtn);
555	}
556
557	row.appendChild(td);
558	tbody.appendChild(row);
559	table.appendChild(tbody);
560	this.container = table;
561};
562
563/**
564 * Constructs a new edit file dialog.
565 */
566var EditDiagramDialog = function(editorUi)
567{
568	var div = document.createElement('div');
569	div.style.textAlign = 'right';
570	var textarea = document.createElement('textarea');
571	textarea.setAttribute('wrap', 'off');
572	textarea.setAttribute('spellcheck', 'false');
573	textarea.setAttribute('autocorrect', 'off');
574	textarea.setAttribute('autocomplete', 'off');
575	textarea.setAttribute('autocapitalize', 'off');
576	textarea.style.overflow = 'auto';
577	textarea.style.resize = 'none';
578	textarea.style.width = '600px';
579	textarea.style.height = '360px';
580	textarea.style.marginBottom = '16px';
581
582	textarea.value = mxUtils.getPrettyXml(editorUi.editor.getGraphXml());
583	div.appendChild(textarea);
584
585	this.init = function()
586	{
587		textarea.focus();
588	};
589
590	// Enables dropping files
591	if (Graph.fileSupport)
592	{
593		function handleDrop(evt)
594		{
595		    evt.stopPropagation();
596		    evt.preventDefault();
597
598		    if (evt.dataTransfer.files.length > 0)
599		    {
600    			var file = evt.dataTransfer.files[0];
601    			var reader = new FileReader();
602
603				reader.onload = function(e)
604				{
605					textarea.value = e.target.result;
606				};
607
608				reader.readAsText(file);
609    		}
610		    else
611		    {
612		    	textarea.value = editorUi.extractGraphModelFromEvent(evt);
613		    }
614		};
615
616		function handleDragOver(evt)
617		{
618			evt.stopPropagation();
619			evt.preventDefault();
620		};
621
622		// Setup the dnd listeners.
623		textarea.addEventListener('dragover', handleDragOver, false);
624		textarea.addEventListener('drop', handleDrop, false);
625	}
626
627	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
628	{
629		editorUi.hideDialog();
630	});
631	cancelBtn.className = 'geBtn';
632
633	if (editorUi.editor.cancelFirst)
634	{
635		div.appendChild(cancelBtn);
636	}
637
638	var select = document.createElement('select');
639	select.style.width = '180px';
640	select.className = 'geBtn';
641
642	if (editorUi.editor.graph.isEnabled())
643	{
644		var replaceOption = document.createElement('option');
645		replaceOption.setAttribute('value', 'replace');
646		mxUtils.write(replaceOption, mxResources.get('replaceExistingDrawing'));
647		select.appendChild(replaceOption);
648	}
649
650	var newOption = document.createElement('option');
651	newOption.setAttribute('value', 'new');
652	mxUtils.write(newOption, mxResources.get('openInNewWindow'));
653
654	if (EditDiagramDialog.showNewWindowOption)
655	{
656		select.appendChild(newOption);
657	}
658
659	if (editorUi.editor.graph.isEnabled())
660	{
661		var importOption = document.createElement('option');
662		importOption.setAttribute('value', 'import');
663		mxUtils.write(importOption, mxResources.get('addToExistingDrawing'));
664		select.appendChild(importOption);
665	}
666
667	div.appendChild(select);
668
669	var okBtn = mxUtils.button(mxResources.get('ok'), function()
670	{
671		// Removes all illegal control characters before parsing
672		var data = Graph.zapGremlins(mxUtils.trim(textarea.value));
673		var error = null;
674
675		if (select.value == 'new')
676		{
677			editorUi.hideDialog();
678			editorUi.editor.editAsNew(data);
679		}
680		else if (select.value == 'replace')
681		{
682			editorUi.editor.graph.model.beginUpdate();
683			try
684			{
685				editorUi.editor.setGraphXml(mxUtils.parseXml(data).documentElement);
686				// LATER: Why is hideDialog between begin-/endUpdate faster?
687				editorUi.hideDialog();
688			}
689			catch (e)
690			{
691				error = e;
692			}
693			finally
694			{
695				editorUi.editor.graph.model.endUpdate();
696			}
697		}
698		else if (select.value == 'import')
699		{
700			editorUi.editor.graph.model.beginUpdate();
701			try
702			{
703				var doc = mxUtils.parseXml(data);
704				var model = new mxGraphModel();
705				var codec = new mxCodec(doc);
706				codec.decode(doc.documentElement, model);
707
708				var children = model.getChildren(model.getChildAt(model.getRoot(), 0));
709				editorUi.editor.graph.setSelectionCells(editorUi.editor.graph.importCells(children));
710
711				// LATER: Why is hideDialog between begin-/endUpdate faster?
712				editorUi.hideDialog();
713			}
714			catch (e)
715			{
716				error = e;
717			}
718			finally
719			{
720				editorUi.editor.graph.model.endUpdate();
721			}
722		}
723
724		if (error != null)
725		{
726			mxUtils.alert(error.message);
727		}
728	});
729	okBtn.className = 'geBtn gePrimaryBtn';
730	div.appendChild(okBtn);
731
732	if (!editorUi.editor.cancelFirst)
733	{
734		div.appendChild(cancelBtn);
735	}
736
737	this.container = div;
738};
739
740/**
741 *
742 */
743EditDiagramDialog.showNewWindowOption = true;
744
745/**
746 * Constructs a new export dialog.
747 */
748var ExportDialog = function(editorUi)
749{
750	var graph = editorUi.editor.graph;
751	var bounds = graph.getGraphBounds();
752	var scale = graph.view.scale;
753
754	var width = Math.ceil(bounds.width / scale);
755	var height = Math.ceil(bounds.height / scale);
756
757	var row, td;
758
759	var table = document.createElement('table');
760	var tbody = document.createElement('tbody');
761	table.setAttribute('cellpadding', (mxClient.IS_SF) ? '0' : '2');
762
763	row = document.createElement('tr');
764
765	td = document.createElement('td');
766	td.style.fontSize = '10pt';
767	td.style.width = '100px';
768	mxUtils.write(td, mxResources.get('filename') + ':');
769
770	row.appendChild(td);
771
772	var nameInput = document.createElement('input');
773	nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
774	nameInput.style.width = '180px';
775
776	td = document.createElement('td');
777	td.appendChild(nameInput);
778	row.appendChild(td);
779
780	tbody.appendChild(row);
781
782	row = document.createElement('tr');
783
784	td = document.createElement('td');
785	td.style.fontSize = '10pt';
786	mxUtils.write(td, mxResources.get('format') + ':');
787
788	row.appendChild(td);
789
790	var imageFormatSelect = document.createElement('select');
791	imageFormatSelect.style.width = '180px';
792
793	var pngOption = document.createElement('option');
794	pngOption.setAttribute('value', 'png');
795	mxUtils.write(pngOption, mxResources.get('formatPng'));
796	imageFormatSelect.appendChild(pngOption);
797
798	var gifOption = document.createElement('option');
799
800	if (ExportDialog.showGifOption)
801	{
802		gifOption.setAttribute('value', 'gif');
803		mxUtils.write(gifOption, mxResources.get('formatGif'));
804		imageFormatSelect.appendChild(gifOption);
805	}
806
807	var jpgOption = document.createElement('option');
808	jpgOption.setAttribute('value', 'jpg');
809	mxUtils.write(jpgOption, mxResources.get('formatJpg'));
810	imageFormatSelect.appendChild(jpgOption);
811
812	var pdfOption = document.createElement('option');
813	pdfOption.setAttribute('value', 'pdf');
814	mxUtils.write(pdfOption, mxResources.get('formatPdf'));
815	imageFormatSelect.appendChild(pdfOption);
816
817	var svgOption = document.createElement('option');
818	svgOption.setAttribute('value', 'svg');
819	mxUtils.write(svgOption, mxResources.get('formatSvg'));
820	imageFormatSelect.appendChild(svgOption);
821
822	if (ExportDialog.showXmlOption)
823	{
824		var xmlOption = document.createElement('option');
825		xmlOption.setAttribute('value', 'xml');
826		mxUtils.write(xmlOption, mxResources.get('formatXml'));
827		imageFormatSelect.appendChild(xmlOption);
828	}
829
830	td = document.createElement('td');
831	td.appendChild(imageFormatSelect);
832	row.appendChild(td);
833
834	tbody.appendChild(row);
835
836	row = document.createElement('tr');
837
838	td = document.createElement('td');
839	td.style.fontSize = '10pt';
840	mxUtils.write(td, mxResources.get('zoom') + ' (%):');
841
842	row.appendChild(td);
843
844	var zoomInput = document.createElement('input');
845	zoomInput.setAttribute('type', 'number');
846	zoomInput.setAttribute('value', '100');
847	zoomInput.style.width = '180px';
848
849	td = document.createElement('td');
850	td.appendChild(zoomInput);
851	row.appendChild(td);
852
853	tbody.appendChild(row);
854
855	row = document.createElement('tr');
856
857	td = document.createElement('td');
858	td.style.fontSize = '10pt';
859	mxUtils.write(td, mxResources.get('width') + ':');
860
861	row.appendChild(td);
862
863	var widthInput = document.createElement('input');
864	widthInput.setAttribute('value', width);
865	widthInput.style.width = '180px';
866
867	td = document.createElement('td');
868	td.appendChild(widthInput);
869	row.appendChild(td);
870
871	tbody.appendChild(row);
872
873	row = document.createElement('tr');
874
875	td = document.createElement('td');
876	td.style.fontSize = '10pt';
877	mxUtils.write(td, mxResources.get('height') + ':');
878
879	row.appendChild(td);
880
881	var heightInput = document.createElement('input');
882	heightInput.setAttribute('value', height);
883	heightInput.style.width = '180px';
884
885	td = document.createElement('td');
886	td.appendChild(heightInput);
887	row.appendChild(td);
888
889	tbody.appendChild(row);
890
891	row = document.createElement('tr');
892
893	td = document.createElement('td');
894	td.style.fontSize = '10pt';
895	mxUtils.write(td, mxResources.get('dpi') + ':');
896
897	row.appendChild(td);
898
899	var dpiSelect = document.createElement('select');
900	dpiSelect.style.width = '180px';
901
902	var dpi100Option = document.createElement('option');
903	dpi100Option.setAttribute('value', '100');
904	mxUtils.write(dpi100Option, '100dpi');
905	dpiSelect.appendChild(dpi100Option);
906
907	var dpi200Option = document.createElement('option');
908	dpi200Option.setAttribute('value', '200');
909	mxUtils.write(dpi200Option, '200dpi');
910	dpiSelect.appendChild(dpi200Option);
911
912	var dpi300Option = document.createElement('option');
913	dpi300Option.setAttribute('value', '300');
914	mxUtils.write(dpi300Option, '300dpi');
915	dpiSelect.appendChild(dpi300Option);
916
917	var dpi400Option = document.createElement('option');
918	dpi400Option.setAttribute('value', '400');
919	mxUtils.write(dpi400Option, '400dpi');
920	dpiSelect.appendChild(dpi400Option);
921
922	var dpiCustOption = document.createElement('option');
923	dpiCustOption.setAttribute('value', 'custom');
924	mxUtils.write(dpiCustOption, mxResources.get('custom'));
925	dpiSelect.appendChild(dpiCustOption);
926
927	var customDpi = document.createElement('input');
928	customDpi.style.width = '180px';
929	customDpi.style.display = 'none';
930	customDpi.setAttribute('value', '100');
931	customDpi.setAttribute('type', 'number');
932	customDpi.setAttribute('min', '50');
933	customDpi.setAttribute('step', '50');
934
935	var zoomUserChanged = false;
936
937	mxEvent.addListener(dpiSelect, 'change', function()
938	{
939		if (this.value == 'custom')
940		{
941			this.style.display = 'none';
942			customDpi.style.display = '';
943			customDpi.focus();
944		}
945		else
946		{
947			customDpi.value = this.value;
948
949			if (!zoomUserChanged)
950			{
951				zoomInput.value = this.value;
952			}
953		}
954	});
955
956	mxEvent.addListener(customDpi, 'change', function()
957	{
958		var dpi = parseInt(customDpi.value);
959
960		if (isNaN(dpi) || dpi <= 0)
961		{
962			customDpi.style.backgroundColor = 'red';
963		}
964		else
965		{
966			customDpi.style.backgroundColor = '';
967
968			if (!zoomUserChanged)
969			{
970				zoomInput.value = dpi;
971			}
972		}
973	});
974
975	td = document.createElement('td');
976	td.appendChild(dpiSelect);
977	td.appendChild(customDpi);
978	row.appendChild(td);
979
980	tbody.appendChild(row);
981
982	row = document.createElement('tr');
983
984	td = document.createElement('td');
985	td.style.fontSize = '10pt';
986	mxUtils.write(td, mxResources.get('background') + ':');
987
988	row.appendChild(td);
989
990	var transparentCheckbox = document.createElement('input');
991	transparentCheckbox.setAttribute('type', 'checkbox');
992	transparentCheckbox.checked = graph.background == null || graph.background == mxConstants.NONE;
993
994	td = document.createElement('td');
995	td.appendChild(transparentCheckbox);
996	mxUtils.write(td, mxResources.get('transparent'));
997
998	row.appendChild(td);
999
1000	tbody.appendChild(row);
1001
1002	row = document.createElement('tr');
1003
1004	td = document.createElement('td');
1005	td.style.fontSize = '10pt';
1006	mxUtils.write(td, mxResources.get('grid') + ':');
1007
1008	row.appendChild(td);
1009
1010	var gridCheckbox = document.createElement('input');
1011	gridCheckbox.setAttribute('type', 'checkbox');
1012	gridCheckbox.checked = false;
1013
1014	td = document.createElement('td');
1015	td.appendChild(gridCheckbox);
1016
1017	row.appendChild(td);
1018
1019	tbody.appendChild(row);
1020
1021	row = document.createElement('tr');
1022
1023	td = document.createElement('td');
1024	td.style.fontSize = '10pt';
1025	mxUtils.write(td, mxResources.get('borderWidth') + ':');
1026
1027	row.appendChild(td);
1028
1029	var borderInput = document.createElement('input');
1030	borderInput.setAttribute('type', 'number');
1031	borderInput.setAttribute('value', ExportDialog.lastBorderValue);
1032	borderInput.style.width = '180px';
1033
1034	td = document.createElement('td');
1035	td.appendChild(borderInput);
1036	row.appendChild(td);
1037
1038	tbody.appendChild(row);
1039	table.appendChild(tbody);
1040
1041	// Handles changes in the export format
1042	function formatChanged()
1043	{
1044		var name = nameInput.value;
1045		var dot = name.lastIndexOf('.');
1046
1047		if (dot > 0)
1048		{
1049			nameInput.value = name.substring(0, dot + 1) + imageFormatSelect.value;
1050		}
1051		else
1052		{
1053			nameInput.value = name + '.' + imageFormatSelect.value;
1054		}
1055
1056		if (imageFormatSelect.value === 'xml')
1057		{
1058			zoomInput.setAttribute('disabled', 'true');
1059			widthInput.setAttribute('disabled', 'true');
1060			heightInput.setAttribute('disabled', 'true');
1061			borderInput.setAttribute('disabled', 'true');
1062		}
1063		else
1064		{
1065			zoomInput.removeAttribute('disabled');
1066			widthInput.removeAttribute('disabled');
1067			heightInput.removeAttribute('disabled');
1068			borderInput.removeAttribute('disabled');
1069		}
1070
1071		if (imageFormatSelect.value === 'png' || imageFormatSelect.value === 'svg' || imageFormatSelect.value === 'pdf')
1072		{
1073			transparentCheckbox.removeAttribute('disabled');
1074		}
1075		else
1076		{
1077			transparentCheckbox.setAttribute('disabled', 'disabled');
1078		}
1079
1080		if (imageFormatSelect.value === 'png' || imageFormatSelect.value === 'jpg' || imageFormatSelect.value === 'pdf')
1081		{
1082			gridCheckbox.removeAttribute('disabled');
1083		}
1084		else
1085		{
1086			gridCheckbox.setAttribute('disabled', 'disabled');
1087		}
1088
1089		if (imageFormatSelect.value === 'png')
1090		{
1091			dpiSelect.removeAttribute('disabled');
1092			customDpi.removeAttribute('disabled');
1093		}
1094		else
1095		{
1096			dpiSelect.setAttribute('disabled', 'disabled');
1097			customDpi.setAttribute('disabled', 'disabled');
1098		}
1099	};
1100
1101	mxEvent.addListener(imageFormatSelect, 'change', formatChanged);
1102	formatChanged();
1103
1104	function checkValues()
1105	{
1106		if (widthInput.value * heightInput.value > MAX_AREA || widthInput.value <= 0)
1107		{
1108			widthInput.style.backgroundColor = 'red';
1109		}
1110		else
1111		{
1112			widthInput.style.backgroundColor = '';
1113		}
1114
1115		if (widthInput.value * heightInput.value > MAX_AREA || heightInput.value <= 0)
1116		{
1117			heightInput.style.backgroundColor = 'red';
1118		}
1119		else
1120		{
1121			heightInput.style.backgroundColor = '';
1122		}
1123	};
1124
1125	mxEvent.addListener(zoomInput, 'change', function()
1126	{
1127		zoomUserChanged = true;
1128		var s = Math.max(0, parseFloat(zoomInput.value) || 100) / 100;
1129		zoomInput.value = parseFloat((s * 100).toFixed(2));
1130
1131		if (width > 0)
1132		{
1133			widthInput.value = Math.floor(width * s);
1134			heightInput.value = Math.floor(height * s);
1135		}
1136		else
1137		{
1138			zoomInput.value = '100';
1139			widthInput.value = width;
1140			heightInput.value = height;
1141		}
1142
1143		checkValues();
1144	});
1145
1146	mxEvent.addListener(widthInput, 'change', function()
1147	{
1148		var s = parseInt(widthInput.value) / width;
1149
1150		if (s > 0)
1151		{
1152			zoomInput.value = parseFloat((s * 100).toFixed(2));
1153			heightInput.value = Math.floor(height * s);
1154		}
1155		else
1156		{
1157			zoomInput.value = '100';
1158			widthInput.value = width;
1159			heightInput.value = height;
1160		}
1161
1162		checkValues();
1163	});
1164
1165	mxEvent.addListener(heightInput, 'change', function()
1166	{
1167		var s = parseInt(heightInput.value) / height;
1168
1169		if (s > 0)
1170		{
1171			zoomInput.value = parseFloat((s * 100).toFixed(2));
1172			widthInput.value = Math.floor(width * s);
1173		}
1174		else
1175		{
1176			zoomInput.value = '100';
1177			widthInput.value = width;
1178			heightInput.value = height;
1179		}
1180
1181		checkValues();
1182	});
1183
1184	row = document.createElement('tr');
1185	td = document.createElement('td');
1186	td.setAttribute('align', 'right');
1187	td.style.paddingTop = '22px';
1188	td.colSpan = 2;
1189
1190	var saveBtn = mxUtils.button(mxResources.get('export'), mxUtils.bind(this, function()
1191	{
1192		if (parseInt(zoomInput.value) <= 0)
1193		{
1194			mxUtils.alert(mxResources.get('drawingEmpty'));
1195		}
1196		else
1197		{
1198	    	var name = nameInput.value;
1199			var format = imageFormatSelect.value;
1200	    	var s = Math.max(0, parseFloat(zoomInput.value) || 100) / 100;
1201			var b = Math.max(0, parseInt(borderInput.value));
1202			var bg = graph.background;
1203			var dpi = Math.max(1, parseInt(customDpi.value));
1204
1205			if ((format == 'svg' || format == 'png' || format == 'pdf') && transparentCheckbox.checked)
1206			{
1207				bg = null;
1208			}
1209			else if (bg == null || bg == mxConstants.NONE)
1210			{
1211				bg = '#ffffff';
1212			}
1213
1214			ExportDialog.lastBorderValue = b;
1215			ExportDialog.exportFile(editorUi, name, format, bg, s, b, dpi, gridCheckbox.checked);
1216		}
1217	}));
1218	saveBtn.className = 'geBtn gePrimaryBtn';
1219
1220	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
1221	{
1222		editorUi.hideDialog();
1223	});
1224	cancelBtn.className = 'geBtn';
1225
1226	if (editorUi.editor.cancelFirst)
1227	{
1228		td.appendChild(cancelBtn);
1229		td.appendChild(saveBtn);
1230	}
1231	else
1232	{
1233		td.appendChild(saveBtn);
1234		td.appendChild(cancelBtn);
1235	}
1236
1237	row.appendChild(td);
1238	tbody.appendChild(row);
1239	table.appendChild(tbody);
1240	this.container = table;
1241};
1242
1243/**
1244 * Remembers last value for border.
1245 */
1246ExportDialog.lastBorderValue = 0;
1247
1248/**
1249 * Global switches for the export dialog.
1250 */
1251ExportDialog.showGifOption = true;
1252
1253/**
1254 * Global switches for the export dialog.
1255 */
1256ExportDialog.showXmlOption = true;
1257
1258/**
1259 * Hook for getting the export format. Returns null for the default
1260 * intermediate XML export format or a function that returns the
1261 * parameter and value to be used in the request in the form
1262 * key=value, where value should be URL encoded.
1263 */
1264ExportDialog.exportFile = function(editorUi, name, format, bg, s, b, dpi, grid)
1265{
1266	var graph = editorUi.editor.graph;
1267
1268	if (format == 'xml')
1269	{
1270    	ExportDialog.saveLocalFile(editorUi, mxUtils.getXml(editorUi.editor.getGraphXml()), name, format);
1271	}
1272    else if (format == 'svg')
1273	{
1274		ExportDialog.saveLocalFile(editorUi, mxUtils.getXml(graph.getSvg(bg, s, b)), name, format);
1275	}
1276    else
1277    {
1278    	var bounds = graph.getGraphBounds();
1279
1280		// New image export
1281		var xmlDoc = mxUtils.createXmlDocument();
1282		var root = xmlDoc.createElement('output');
1283		xmlDoc.appendChild(root);
1284
1285	    // Renders graph. Offset will be multiplied with state's scale when painting state.
1286		var xmlCanvas = new mxXmlCanvas2D(root);
1287		xmlCanvas.translate(Math.floor((b / s - bounds.x) / graph.view.scale),
1288			Math.floor((b / s - bounds.y) / graph.view.scale));
1289		xmlCanvas.scale(s / graph.view.scale);
1290
1291		var imgExport = new mxImageExport()
1292	    imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
1293
1294		// Puts request data together
1295		var param = 'xml=' + encodeURIComponent(mxUtils.getXml(root));
1296		var w = Math.ceil(bounds.width * s / graph.view.scale + 2 * b);
1297		var h = Math.ceil(bounds.height * s / graph.view.scale + 2 * b);
1298
1299		// Requests image if request is valid
1300		if (param.length <= MAX_REQUEST_SIZE && w * h < MAX_AREA)
1301		{
1302			editorUi.hideDialog();
1303			var req = new mxXmlRequest(EXPORT_URL, 'format=' + format +
1304				'&filename=' + encodeURIComponent(name) +
1305				'&bg=' + ((bg != null) ? bg : 'none') +
1306				'&w=' + w + '&h=' + h + '&' + param +
1307				'&dpi=' + dpi);
1308			req.simulate(document, '_blank');
1309		}
1310		else
1311		{
1312			mxUtils.alert(mxResources.get('drawingTooLarge'));
1313		}
1314	}
1315};
1316
1317/**
1318 * Hook for getting the export format. Returns null for the default
1319 * intermediate XML export format or a function that returns the
1320 * parameter and value to be used in the request in the form
1321 * key=value, where value should be URL encoded.
1322 */
1323ExportDialog.saveLocalFile = function(editorUi, data, filename, format)
1324{
1325	if (data.length < MAX_REQUEST_SIZE)
1326	{
1327		editorUi.hideDialog();
1328		var req = new mxXmlRequest(SAVE_URL, 'xml=' + encodeURIComponent(data) + '&filename=' +
1329			encodeURIComponent(filename) + '&format=' + format);
1330		req.simulate(document, '_blank');
1331	}
1332	else
1333	{
1334		mxUtils.alert(mxResources.get('drawingTooLarge'));
1335		mxUtils.popup(xml);
1336	}
1337};
1338
1339/**
1340 * Constructs a new metadata dialog.
1341 */
1342var EditDataDialog = function(ui, cell)
1343{
1344	var div = document.createElement('div');
1345	var graph = ui.editor.graph;
1346
1347	var value = graph.getModel().getValue(cell);
1348
1349	// Converts the value to an XML node
1350	if (!mxUtils.isNode(value))
1351	{
1352		var doc = mxUtils.createXmlDocument();
1353		var obj = doc.createElement('object');
1354		obj.setAttribute('label', value || '');
1355		value = obj;
1356	}
1357
1358	var meta = {};
1359
1360	try
1361	{
1362		var temp = mxUtils.getValue(ui.editor.graph.getCurrentCellStyle(cell), 'metaData', null);
1363
1364		if (temp != null)
1365		{
1366			meta = JSON.parse(temp);
1367		}
1368	}
1369	catch (e)
1370	{
1371		// ignore
1372	}
1373
1374	// Creates the dialog contents
1375	var form = new mxForm('properties');
1376	form.table.style.width = '100%';
1377
1378	var attrs = value.attributes;
1379	var names = [];
1380	var texts = [];
1381	var count = 0;
1382
1383	var id = (EditDataDialog.getDisplayIdForCell != null) ?
1384		EditDataDialog.getDisplayIdForCell(ui, cell) : null;
1385
1386	var addRemoveButton = function(text, name)
1387	{
1388		var wrapper = document.createElement('div');
1389		wrapper.style.position = 'relative';
1390		wrapper.style.paddingRight = '20px';
1391		wrapper.style.boxSizing = 'border-box';
1392		wrapper.style.width = '100%';
1393
1394		var removeAttr = document.createElement('a');
1395		var img = mxUtils.createImage(Dialog.prototype.closeImage);
1396		img.style.height = '9px';
1397		img.style.fontSize = '9px';
1398		img.style.marginBottom = (mxClient.IS_IE11) ? '-1px' : '5px';
1399
1400		removeAttr.className = 'geButton';
1401		removeAttr.setAttribute('title', mxResources.get('delete'));
1402		removeAttr.style.position = 'absolute';
1403		removeAttr.style.top = '4px';
1404		removeAttr.style.right = '0px';
1405		removeAttr.style.margin = '0px';
1406		removeAttr.style.width = '9px';
1407		removeAttr.style.height = '9px';
1408		removeAttr.style.cursor = 'pointer';
1409		removeAttr.appendChild(img);
1410
1411		var removeAttrFn = (function(name)
1412		{
1413			return function()
1414			{
1415				var count = 0;
1416
1417				for (var j = 0; j < names.length; j++)
1418				{
1419					if (names[j] == name)
1420					{
1421						texts[j] = null;
1422						form.table.deleteRow(count + ((id != null) ? 1 : 0));
1423
1424						break;
1425					}
1426
1427					if (texts[j] != null)
1428					{
1429						count++;
1430					}
1431				}
1432			};
1433		})(name);
1434
1435		mxEvent.addListener(removeAttr, 'click', removeAttrFn);
1436
1437		var parent = text.parentNode;
1438		wrapper.appendChild(text);
1439		wrapper.appendChild(removeAttr);
1440		parent.appendChild(wrapper);
1441	};
1442
1443	var addTextArea = function(index, name, value)
1444	{
1445		names[index] = name;
1446		texts[index] = form.addTextarea(names[count] + ':', value, 2);
1447		texts[index].style.width = '100%';
1448
1449		if (value.indexOf('\n') > 0)
1450		{
1451			texts[index].setAttribute('rows', '2');
1452		}
1453
1454		addRemoveButton(texts[index], name);
1455
1456		if (meta[name] != null && meta[name].editable == false)
1457		{
1458			texts[index].setAttribute('disabled', 'disabled');
1459		}
1460	};
1461
1462	var temp = [];
1463	var isLayer = graph.getModel().getParent(cell) == graph.getModel().getRoot();
1464
1465	for (var i = 0; i < attrs.length; i++)
1466	{
1467		if ((isLayer || attrs[i].nodeName != 'label') && attrs[i].nodeName != 'placeholders')
1468		{
1469			temp.push({name: attrs[i].nodeName, value: attrs[i].nodeValue});
1470		}
1471	}
1472
1473	// Sorts by name
1474	temp.sort(function(a, b)
1475	{
1476	    if (a.name < b.name)
1477	    {
1478	    	return -1;
1479	    }
1480	    else if (a.name > b.name)
1481	    {
1482	    	return 1;
1483	    }
1484	    else
1485	    {
1486	    	return 0;
1487	    }
1488	});
1489
1490	if (id != null)
1491	{
1492		var text = document.createElement('div');
1493		text.style.width = '100%';
1494		text.style.fontSize = '11px';
1495		text.style.textAlign = 'center';
1496		mxUtils.write(text, id);
1497
1498		var idInput = form.addField(mxResources.get('id') + ':', text);
1499
1500		mxEvent.addListener(text, 'dblclick', function(evt)
1501		{
1502			if (mxEvent.isShiftDown(evt))
1503			{
1504				var dlg = new FilenameDialog(ui, id, mxResources.get('apply'), mxUtils.bind(this, function(value)
1505				{
1506					if (value != null && value.length > 0 && value != id)
1507					{
1508						if (graph.getModel().getCell(value) == null)
1509						{
1510							graph.getModel().cellRemoved(cell);
1511							cell.setId(value);
1512							id = value;
1513							idInput.innerHTML = mxUtils.htmlEntities(value);
1514							graph.getModel().cellAdded(cell);
1515						}
1516						else
1517						{
1518							ui.handleError({message: mxResources.get('alreadyExst', [value])});
1519						}
1520					}
1521				}), mxResources.get('id'));
1522				ui.showDialog(dlg.container, 300, 80, true, true);
1523				dlg.init();
1524			}
1525		});
1526
1527		text.setAttribute('title', 'Shift+Double Click to Edit ID');
1528	}
1529
1530	for (var i = 0; i < temp.length; i++)
1531	{
1532		addTextArea(count, temp[i].name, temp[i].value);
1533		count++;
1534	}
1535
1536	var top = document.createElement('div');
1537	top.style.position = 'absolute';
1538	top.style.top = '30px';
1539	top.style.left = '30px';
1540	top.style.right = '30px';
1541	top.style.bottom = '80px';
1542	top.style.overflowY = 'auto';
1543
1544	top.appendChild(form.table);
1545
1546	var newProp = document.createElement('div');
1547	newProp.style.boxSizing = 'border-box';
1548	newProp.style.paddingRight = '160px';
1549	newProp.style.whiteSpace = 'nowrap';
1550	newProp.style.marginTop = '6px';
1551	newProp.style.width = '100%';
1552
1553	var nameInput = document.createElement('input');
1554	nameInput.setAttribute('placeholder', mxResources.get('enterPropertyName'));
1555	nameInput.setAttribute('type', 'text');
1556	nameInput.setAttribute('size', (mxClient.IS_IE || mxClient.IS_IE11) ? '36' : '40');
1557	nameInput.style.boxSizing = 'border-box';
1558	nameInput.style.marginLeft = '2px';
1559	nameInput.style.width = '100%';
1560
1561	newProp.appendChild(nameInput);
1562	top.appendChild(newProp);
1563	div.appendChild(top);
1564
1565	var addBtn = mxUtils.button(mxResources.get('addProperty'), function()
1566	{
1567		var name = nameInput.value;
1568
1569		// Avoid ':' in attribute names which seems to be valid in Chrome
1570		if (name.length > 0 && name != 'label' && name != 'placeholders' && name.indexOf(':') < 0)
1571		{
1572			try
1573			{
1574				var idx = mxUtils.indexOf(names, name);
1575
1576				if (idx >= 0 && texts[idx] != null)
1577				{
1578					texts[idx].focus();
1579				}
1580				else
1581				{
1582					// Checks if the name is valid
1583					var clone = value.cloneNode(false);
1584					clone.setAttribute(name, '');
1585
1586					if (idx >= 0)
1587					{
1588						names.splice(idx, 1);
1589						texts.splice(idx, 1);
1590					}
1591
1592					names.push(name);
1593					var text = form.addTextarea(name + ':', '', 2);
1594					text.style.width = '100%';
1595					texts.push(text);
1596					addRemoveButton(text, name);
1597
1598					text.focus();
1599				}
1600
1601				addBtn.setAttribute('disabled', 'disabled');
1602				nameInput.value = '';
1603			}
1604			catch (e)
1605			{
1606				mxUtils.alert(e);
1607			}
1608		}
1609		else
1610		{
1611			mxUtils.alert(mxResources.get('invalidName'));
1612		}
1613	});
1614
1615	this.init = function()
1616	{
1617		if (texts.length > 0)
1618		{
1619			texts[0].focus();
1620		}
1621		else
1622		{
1623			nameInput.focus();
1624		}
1625	};
1626
1627	addBtn.setAttribute('title', mxResources.get('addProperty'));
1628	addBtn.setAttribute('disabled', 'disabled');
1629	addBtn.style.textOverflow = 'ellipsis';
1630	addBtn.style.position = 'absolute';
1631	addBtn.style.overflow = 'hidden';
1632	addBtn.style.width = '144px';
1633	addBtn.style.right = '0px';
1634	addBtn.className = 'geBtn';
1635	newProp.appendChild(addBtn);
1636
1637	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
1638	{
1639		ui.hideDialog.apply(ui, arguments);
1640	});
1641
1642	cancelBtn.className = 'geBtn';
1643
1644	var applyBtn = mxUtils.button(mxResources.get('apply'), function()
1645	{
1646		try
1647		{
1648			ui.hideDialog.apply(ui, arguments);
1649
1650			// Clones and updates the value
1651			value = value.cloneNode(true);
1652			var removeLabel = false;
1653
1654			for (var i = 0; i < names.length; i++)
1655			{
1656				if (texts[i] == null)
1657				{
1658					value.removeAttribute(names[i]);
1659				}
1660				else
1661				{
1662					value.setAttribute(names[i], texts[i].value);
1663					removeLabel = removeLabel || (names[i] == 'placeholder' &&
1664						value.getAttribute('placeholders') == '1');
1665				}
1666			}
1667
1668			// Removes label if placeholder is assigned
1669			if (removeLabel)
1670			{
1671				value.removeAttribute('label');
1672			}
1673
1674			// Updates the value of the cell (undoable)
1675			graph.getModel().setValue(cell, value);
1676		}
1677		catch (e)
1678		{
1679			mxUtils.alert(e);
1680		}
1681	});
1682	applyBtn.className = 'geBtn gePrimaryBtn';
1683
1684	function updateAddBtn()
1685	{
1686		if (nameInput.value.length > 0)
1687		{
1688			addBtn.removeAttribute('disabled');
1689		}
1690		else
1691		{
1692			addBtn.setAttribute('disabled', 'disabled');
1693		}
1694	};
1695
1696	mxEvent.addListener(nameInput, 'keyup', updateAddBtn);
1697
1698	// Catches all changes that don't fire a keyup (such as paste via mouse)
1699	mxEvent.addListener(nameInput, 'change', updateAddBtn);
1700
1701	var buttons = document.createElement('div');
1702	buttons.style.cssText = 'position:absolute;left:30px;right:30px;text-align:right;bottom:30px;height:40px;'
1703
1704	if (ui.editor.graph.getModel().isVertex(cell) || ui.editor.graph.getModel().isEdge(cell))
1705	{
1706		var replace = document.createElement('span');
1707		replace.style.marginRight = '10px';
1708		var input = document.createElement('input');
1709		input.setAttribute('type', 'checkbox');
1710		input.style.marginRight = '6px';
1711
1712		if (value.getAttribute('placeholders') == '1')
1713		{
1714			input.setAttribute('checked', 'checked');
1715			input.defaultChecked = true;
1716		}
1717
1718		mxEvent.addListener(input, 'click', function()
1719		{
1720			if (value.getAttribute('placeholders') == '1')
1721			{
1722				value.removeAttribute('placeholders');
1723			}
1724			else
1725			{
1726				value.setAttribute('placeholders', '1');
1727			}
1728		});
1729
1730		replace.appendChild(input);
1731		mxUtils.write(replace, mxResources.get('placeholders'));
1732
1733		if (EditDataDialog.placeholderHelpLink != null)
1734		{
1735			var link = document.createElement('a');
1736			link.setAttribute('href', EditDataDialog.placeholderHelpLink);
1737			link.setAttribute('title', mxResources.get('help'));
1738			link.setAttribute('target', '_blank');
1739			link.style.marginLeft = '8px';
1740			link.style.cursor = 'help';
1741
1742			var icon = document.createElement('img');
1743			mxUtils.setOpacity(icon, 50);
1744			icon.style.height = '16px';
1745			icon.style.width = '16px';
1746			icon.setAttribute('border', '0');
1747			icon.setAttribute('valign', 'middle');
1748			icon.style.marginTop = (mxClient.IS_IE11) ? '0px' : '-4px';
1749			icon.setAttribute('src', Editor.helpImage);
1750			link.appendChild(icon);
1751
1752			replace.appendChild(link);
1753		}
1754
1755		buttons.appendChild(replace);
1756	}
1757
1758	if (ui.editor.cancelFirst)
1759	{
1760		buttons.appendChild(cancelBtn);
1761		buttons.appendChild(applyBtn);
1762	}
1763	else
1764	{
1765		buttons.appendChild(applyBtn);
1766		buttons.appendChild(cancelBtn);
1767	}
1768
1769	div.appendChild(buttons);
1770	this.container = div;
1771};
1772
1773/**
1774 * Optional help link.
1775 */
1776EditDataDialog.getDisplayIdForCell = function(ui, cell)
1777{
1778	var id = null;
1779
1780	if (ui.editor.graph.getModel().getParent(cell) != null)
1781	{
1782		id = cell.getId();
1783	}
1784
1785	return id;
1786};
1787
1788/**
1789 * Optional help link.
1790 */
1791EditDataDialog.placeholderHelpLink = null;
1792
1793/**
1794 * Constructs a new link dialog.
1795 */
1796var LinkDialog = function(editorUi, initialValue, btnLabel, fn)
1797{
1798	var div = document.createElement('div');
1799	mxUtils.write(div, mxResources.get('editLink') + ':');
1800
1801	var inner = document.createElement('div');
1802	inner.className = 'geTitle';
1803	inner.style.backgroundColor = 'transparent';
1804	inner.style.borderColor = 'transparent';
1805	inner.style.whiteSpace = 'nowrap';
1806	inner.style.textOverflow = 'clip';
1807	inner.style.cursor = 'default';
1808	inner.style.paddingRight = '20px';
1809
1810	var linkInput = document.createElement('input');
1811	linkInput.setAttribute('value', initialValue);
1812	linkInput.setAttribute('placeholder', 'http://www.example.com/');
1813	linkInput.setAttribute('type', 'text');
1814	linkInput.style.marginTop = '6px';
1815	linkInput.style.width = '400px';
1816	linkInput.style.backgroundImage = 'url(\'' + Dialog.prototype.clearImage + '\')';
1817	linkInput.style.backgroundRepeat = 'no-repeat';
1818	linkInput.style.backgroundPosition = '100% 50%';
1819	linkInput.style.paddingRight = '14px';
1820
1821	var cross = document.createElement('div');
1822	cross.setAttribute('title', mxResources.get('reset'));
1823	cross.style.position = 'relative';
1824	cross.style.left = '-16px';
1825	cross.style.width = '12px';
1826	cross.style.height = '14px';
1827	cross.style.cursor = 'pointer';
1828
1829	// Workaround for inline-block not supported in IE
1830	cross.style.display = 'inline-block';
1831	cross.style.top = '3px';
1832
1833	// Needed to block event transparency in IE
1834	cross.style.background = 'url(' + IMAGE_PATH + '/transparent.gif)';
1835
1836	mxEvent.addListener(cross, 'click', function()
1837	{
1838		linkInput.value = '';
1839		linkInput.focus();
1840	});
1841
1842	inner.appendChild(linkInput);
1843	inner.appendChild(cross);
1844	div.appendChild(inner);
1845
1846	this.init = function()
1847	{
1848		linkInput.focus();
1849
1850		if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
1851		{
1852			linkInput.select();
1853		}
1854		else
1855		{
1856			document.execCommand('selectAll', false, null);
1857		}
1858	};
1859
1860	var btns = document.createElement('div');
1861	btns.style.marginTop = '18px';
1862	btns.style.textAlign = 'right';
1863
1864	mxEvent.addListener(linkInput, 'keypress', function(e)
1865	{
1866		if (e.keyCode == 13)
1867		{
1868			editorUi.hideDialog();
1869			fn(linkInput.value);
1870		}
1871	});
1872
1873	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
1874	{
1875		editorUi.hideDialog();
1876	});
1877	cancelBtn.className = 'geBtn';
1878
1879	if (editorUi.editor.cancelFirst)
1880	{
1881		btns.appendChild(cancelBtn);
1882	}
1883
1884	var mainBtn = mxUtils.button(btnLabel, function()
1885	{
1886		editorUi.hideDialog();
1887		fn(linkInput.value);
1888	});
1889	mainBtn.className = 'geBtn gePrimaryBtn';
1890	btns.appendChild(mainBtn);
1891
1892	if (!editorUi.editor.cancelFirst)
1893	{
1894		btns.appendChild(cancelBtn);
1895	}
1896
1897	div.appendChild(btns);
1898
1899	this.container = div;
1900};
1901
1902/**
1903 *
1904 */
1905var OutlineWindow = function(editorUi, x, y, w, h)
1906{
1907	var graph = editorUi.editor.graph;
1908
1909	var div = document.createElement('div');
1910	div.style.position = 'absolute';
1911	div.style.width = '100%';
1912	div.style.height = '100%';
1913	div.style.overflow = 'hidden';
1914
1915	this.window = new mxWindow(mxResources.get('outline'), div, x, y, w, h, true, true);
1916	this.window.minimumSize = new mxRectangle(0, 0, 80, 80);
1917	this.window.destroyOnClose = false;
1918	this.window.setMaximizable(false);
1919	this.window.setResizable(true);
1920	this.window.setClosable(true);
1921	this.window.setVisible(true);
1922
1923	this.window.setLocation = function(x, y)
1924	{
1925		var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
1926		var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;
1927
1928		x = Math.max(0, Math.min(x, iw - this.table.clientWidth));
1929		y = Math.max(0, Math.min(y, ih - this.table.clientHeight - ((urlParams['sketch'] == '1') ? 3 : 48)));
1930
1931		if (this.getX() != x || this.getY() != y)
1932		{
1933			mxWindow.prototype.setLocation.apply(this, arguments);
1934		}
1935	};
1936
1937	var resizeListener = mxUtils.bind(this, function()
1938	{
1939		var x = this.window.getX();
1940		var y = this.window.getY();
1941
1942		this.window.setLocation(x, y);
1943	});
1944
1945	mxEvent.addListener(window, 'resize', resizeListener);
1946
1947	var outline = editorUi.createOutline(this.window);
1948
1949	this.destroy = function()
1950	{
1951		mxEvent.removeListener(window, 'resize', resizeListener);
1952		this.window.destroy();
1953		outline.destroy();
1954	}
1955
1956	this.window.addListener(mxEvent.SHOW, mxUtils.bind(this, function()
1957	{
1958		this.window.fit();
1959		outline.setSuspended(false);
1960	}));
1961
1962	this.window.addListener(mxEvent.HIDE, mxUtils.bind(this, function()
1963	{
1964		outline.setSuspended(true);
1965	}));
1966
1967	this.window.addListener(mxEvent.NORMALIZE, mxUtils.bind(this, function()
1968	{
1969		outline.setSuspended(false);
1970	}));
1971
1972	this.window.addListener(mxEvent.MINIMIZE, mxUtils.bind(this, function()
1973	{
1974		outline.setSuspended(true);
1975	}));
1976
1977	outline.init(div);
1978
1979	var zoomInAction = editorUi.actions.get('zoomIn');
1980	var zoomOutAction = editorUi.actions.get('zoomOut');
1981	mxEvent.addMouseWheelListener(function(evt, up)
1982	{
1983		var outlineWheel = false;
1984		var source = mxEvent.getSource(evt);
1985
1986		while (source != null)
1987		{
1988			if (source == outline.svg)
1989			{
1990				outlineWheel = true;
1991				break;
1992			}
1993
1994			source = source.parentNode;
1995		}
1996
1997		if (outlineWheel)
1998		{
1999			if (up)
2000			{
2001				zoomInAction.funct();
2002			}
2003			else
2004			{
2005				zoomOutAction.funct();
2006			}
2007		}
2008	});
2009};
2010
2011/**
2012 *
2013 */
2014var LayersWindow = function(editorUi, x, y, w, h)
2015{
2016	var graph = editorUi.editor.graph;
2017
2018	var div = document.createElement('div');
2019	div.style.userSelect = 'none';
2020	div.style.background = (!Editor.isDarkMode()) ? '#fff' : Dialog.backdropColor;
2021	div.style.border = '1px solid whiteSmoke';
2022	div.style.height = '100%';
2023	div.style.marginBottom = '10px';
2024	div.style.overflow = 'auto';
2025
2026	var tbarHeight = (!EditorUi.compactUi) ? '30px' : '26px';
2027
2028	var listDiv = document.createElement('div')
2029	listDiv.style.backgroundColor = (!Editor.isDarkMode()) ? '#fff' : Dialog.backdropColor;
2030	listDiv.style.position = 'absolute';
2031	listDiv.style.overflow = 'auto';
2032	listDiv.style.left = '0px';
2033	listDiv.style.right = '0px';
2034	listDiv.style.top = '0px';
2035	listDiv.style.bottom = (parseInt(tbarHeight) + 7) + 'px';
2036	div.appendChild(listDiv);
2037
2038	var dragSource = null;
2039	var dropIndex = null;
2040
2041	mxEvent.addListener(div, 'dragover', function(evt)
2042	{
2043		evt.dataTransfer.dropEffect = 'move';
2044		dropIndex = 0;
2045		evt.stopPropagation();
2046		evt.preventDefault();
2047	});
2048
2049	// Workaround for "no element found" error in FF
2050	mxEvent.addListener(div, 'drop', function(evt)
2051	{
2052		evt.stopPropagation();
2053		evt.preventDefault();
2054	});
2055
2056	var layerCount = null;
2057	var selectionLayer = null;
2058	var ldiv = document.createElement('div');
2059
2060	ldiv.className = 'geToolbarContainer';
2061	ldiv.style.position = 'absolute';
2062	ldiv.style.bottom = '0px';
2063	ldiv.style.left = '0px';
2064	ldiv.style.right = '0px';
2065	ldiv.style.height = tbarHeight;
2066	ldiv.style.overflow = 'hidden';
2067	ldiv.style.padding = (!EditorUi.compactUi) ? '1px' : '4px 0px 3px 0px';
2068	ldiv.style.backgroundColor = (!Editor.isDarkMode()) ? 'whiteSmoke' : Dialog.backdropColor;
2069	ldiv.style.borderWidth = '1px 0px 0px 0px';
2070	ldiv.style.borderColor = '#c3c3c3';
2071	ldiv.style.borderStyle = 'solid';
2072	ldiv.style.display = 'block';
2073	ldiv.style.whiteSpace = 'nowrap';
2074
2075	var link = document.createElement('a');
2076	link.className = 'geButton';
2077
2078	var removeLink = link.cloneNode(false);
2079	var img = document.createElement('img');
2080	img.setAttribute('border', '0');
2081	img.setAttribute('width', '22');
2082	img.setAttribute('src', Editor.trashImage);
2083	img.style.opacity = '0.9';
2084
2085	if (Editor.isDarkMode())
2086	{
2087		img.style.filter = 'invert(100%)';
2088	}
2089
2090	removeLink.appendChild(img);
2091
2092	mxEvent.addListener(removeLink, 'click', function(evt)
2093	{
2094		if (graph.isEnabled())
2095		{
2096			graph.model.beginUpdate();
2097			try
2098			{
2099				var index = graph.model.root.getIndex(selectionLayer);
2100				graph.removeCells([selectionLayer], false);
2101
2102				// Creates default layer if no layer exists
2103				if (graph.model.getChildCount(graph.model.root) == 0)
2104				{
2105					graph.model.add(graph.model.root, new mxCell());
2106					graph.setDefaultParent(null);
2107				}
2108				else if (index > 0 && index <= graph.model.getChildCount(graph.model.root))
2109				{
2110					graph.setDefaultParent(graph.model.getChildAt(graph.model.root, index - 1));
2111				}
2112				else
2113				{
2114					graph.setDefaultParent(null);
2115				}
2116			}
2117			finally
2118			{
2119				graph.model.endUpdate();
2120			}
2121		}
2122
2123		mxEvent.consume(evt);
2124	});
2125
2126	if (!graph.isEnabled())
2127	{
2128		removeLink.className = 'geButton mxDisabled';
2129	}
2130
2131	ldiv.appendChild(removeLink);
2132
2133	var insertLink = link.cloneNode();
2134	insertLink.setAttribute('title', mxUtils.trim(mxResources.get('moveSelectionTo', ['...'])));
2135
2136	img = img.cloneNode(false);
2137	img.setAttribute('src', Editor.verticalDotsImage);
2138	insertLink.appendChild(img);
2139
2140	mxEvent.addListener(insertLink, 'click', function(evt)
2141	{
2142		if (graph.isEnabled() && !graph.isSelectionEmpty())
2143		{
2144			var offset = mxUtils.getOffset(insertLink);
2145
2146			editorUi.showPopupMenu(mxUtils.bind(this, function(menu, parent)
2147			{
2148				for (var i = layerCount - 1; i >= 0; i--)
2149				{
2150					(mxUtils.bind(this, function(child)
2151					{
2152						var item = menu.addItem(graph.convertValueToString(child) ||
2153								mxResources.get('background'), null, mxUtils.bind(this, function()
2154						{
2155							graph.moveCells(graph.getSelectionCells(), 0, 0, false, child);
2156						}), parent);
2157
2158						if (graph.getSelectionCount() == 1 && graph.model.isAncestor(child, graph.getSelectionCell()))
2159						{
2160							menu.addCheckmark(item, Editor.checkmarkImage);
2161						}
2162
2163					}))(graph.model.getChildAt(graph.model.root, i));
2164				}
2165			}), offset.x, offset.y + insertLink.offsetHeight, evt);
2166		}
2167	});
2168
2169	ldiv.appendChild(insertLink);
2170
2171	var dataLink = link.cloneNode(false);
2172	dataLink.setAttribute('title', mxResources.get('editData'));
2173
2174	img = img.cloneNode(false);
2175	img.setAttribute('src', Editor.editImage);
2176	dataLink.appendChild(img);
2177
2178	mxEvent.addListener(dataLink, 'click', function(evt)
2179	{
2180		if (graph.isEnabled())
2181		{
2182			editorUi.showDataDialog(selectionLayer);
2183		}
2184
2185		mxEvent.consume(evt);
2186	});
2187
2188	if (!graph.isEnabled())
2189	{
2190		dataLink.className = 'geButton mxDisabled';
2191	}
2192
2193	ldiv.appendChild(dataLink);
2194
2195	function renameLayer(layer)
2196	{
2197		if (graph.isEnabled() && layer != null)
2198		{
2199			var label = graph.convertValueToString(layer);
2200			var dlg = new FilenameDialog(editorUi, label || mxResources.get('background'), mxResources.get('rename'), mxUtils.bind(this, function(newValue)
2201			{
2202				if (newValue != null)
2203				{
2204					graph.cellLabelChanged(layer, newValue);
2205				}
2206			}), mxResources.get('enterName'));
2207			editorUi.showDialog(dlg.container, 300, 100, true, true);
2208			dlg.init();
2209		}
2210	};
2211
2212	var duplicateLink = link.cloneNode(false);
2213	duplicateLink.setAttribute('title', mxResources.get('duplicate'));
2214
2215	img = img.cloneNode(false);
2216	img.setAttribute('src', Editor.duplicateImage);
2217	duplicateLink.appendChild(img);
2218
2219	mxEvent.addListener(duplicateLink, 'click', function(evt)
2220	{
2221		if (graph.isEnabled())
2222		{
2223			var newCell = null;
2224			graph.model.beginUpdate();
2225			try
2226			{
2227				newCell = graph.cloneCell(selectionLayer);
2228				graph.cellLabelChanged(newCell, mxResources.get('untitledLayer'));
2229				newCell.setVisible(true);
2230				newCell = graph.addCell(newCell, graph.model.root);
2231				graph.setDefaultParent(newCell);
2232			}
2233			finally
2234			{
2235				graph.model.endUpdate();
2236			}
2237
2238			if (newCell != null && !graph.isCellLocked(newCell))
2239			{
2240				graph.selectAll(newCell);
2241			}
2242		}
2243	});
2244
2245	if (!graph.isEnabled())
2246	{
2247		duplicateLink.className = 'geButton mxDisabled';
2248	}
2249
2250	ldiv.appendChild(duplicateLink);
2251
2252	var addLink = link.cloneNode(false);
2253	addLink.setAttribute('title', mxResources.get('addLayer'));
2254
2255	img = img.cloneNode(false);
2256	img.setAttribute('src', Editor.addImage);
2257	addLink.appendChild(img);
2258
2259	mxEvent.addListener(addLink, 'click', function(evt)
2260	{
2261		if (graph.isEnabled())
2262		{
2263			graph.model.beginUpdate();
2264
2265			try
2266			{
2267				var cell = graph.addCell(new mxCell(mxResources.get('untitledLayer')), graph.model.root);
2268				graph.setDefaultParent(cell);
2269			}
2270			finally
2271			{
2272				graph.model.endUpdate();
2273			}
2274		}
2275
2276		mxEvent.consume(evt);
2277	});
2278
2279	if (!graph.isEnabled())
2280	{
2281		addLink.className = 'geButton mxDisabled';
2282	}
2283
2284	ldiv.appendChild(addLink);
2285	div.appendChild(ldiv);
2286
2287	var layerDivs = new mxDictionary();
2288
2289	var dot = document.createElement('span');
2290	dot.setAttribute('title', mxResources.get('selectionOnly'));
2291	dot.innerHTML = '&#8226;';
2292	dot.style.position = 'absolute';
2293	dot.style.fontWeight = 'bold';
2294	dot.style.fontSize = '16pt';
2295	dot.style.right = '2px';
2296	dot.style.top = '2px';
2297
2298	function updateLayerDot()
2299	{
2300		var div = layerDivs.get(graph.getLayerForCells(graph.getSelectionCells()));
2301
2302		if (div != null)
2303		{
2304			div.appendChild(dot);
2305		}
2306		else if (dot.parentNode != null)
2307		{
2308			dot.parentNode.removeChild(dot);
2309		}
2310	};
2311
2312	function refresh()
2313	{
2314		layerCount = graph.model.getChildCount(graph.model.root)
2315		listDiv.innerHTML = '';
2316		layerDivs.clear();
2317
2318		function addLayer(index, label, child, defaultParent)
2319		{
2320			var ldiv = document.createElement('div');
2321			ldiv.className = 'geToolbarContainer';
2322			layerDivs.put(child, ldiv);
2323
2324			ldiv.style.overflow = 'hidden';
2325			ldiv.style.position = 'relative';
2326			ldiv.style.padding = '4px';
2327			ldiv.style.height = '22px';
2328			ldiv.style.display = 'block';
2329			ldiv.style.backgroundColor = (!Editor.isDarkMode()) ? 'whiteSmoke' : Dialog.backdropColor;
2330			ldiv.style.borderWidth = '0px 0px 1px 0px';
2331			ldiv.style.borderColor = '#c3c3c3';
2332			ldiv.style.borderStyle = 'solid';
2333			ldiv.style.whiteSpace = 'nowrap';
2334			ldiv.setAttribute('title', label);
2335
2336			var left = document.createElement('div');
2337			left.style.display = 'inline-block';
2338			left.style.width = '100%';
2339			left.style.textOverflow = 'ellipsis';
2340			left.style.overflow = 'hidden';
2341
2342			mxEvent.addListener(ldiv, 'dragover', function(evt)
2343			{
2344				evt.dataTransfer.dropEffect = 'move';
2345				dropIndex = index;
2346				evt.stopPropagation();
2347				evt.preventDefault();
2348			});
2349
2350			mxEvent.addListener(ldiv, 'dragstart', function(evt)
2351			{
2352				dragSource = ldiv;
2353
2354				// Workaround for no DnD on DIV in FF
2355				if (mxClient.IS_FF)
2356				{
2357					// LATER: Check what triggers a parse as XML on this in FF after drop
2358					evt.dataTransfer.setData('Text', '<layer/>');
2359				}
2360			});
2361
2362			mxEvent.addListener(ldiv, 'dragend', function(evt)
2363			{
2364				if (dragSource != null && dropIndex != null)
2365				{
2366					graph.addCell(child, graph.model.root, dropIndex);
2367				}
2368
2369				dragSource = null;
2370				dropIndex = null;
2371				evt.stopPropagation();
2372				evt.preventDefault();
2373			});
2374
2375			var inp = document.createElement('img');
2376			inp.setAttribute('draggable', 'false');
2377			inp.setAttribute('align', 'top');
2378			inp.setAttribute('border', '0');
2379			inp.style.width = '16px';
2380			inp.style.padding = '0px 6px 0 4px';
2381			inp.style.marginTop = '2px';
2382			inp.style.cursor = 'pointer';
2383			inp.setAttribute('title', mxResources.get(
2384				graph.model.isVisible(child) ?
2385				'hide' : 'show'));
2386
2387			if (graph.model.isVisible(child))
2388			{
2389				inp.setAttribute('src', Editor.visibleImage);
2390				mxUtils.setOpacity(ldiv, 75);
2391			}
2392			else
2393			{
2394				inp.setAttribute('src', Editor.hiddenImage);
2395				mxUtils.setOpacity(ldiv, 25);
2396			}
2397
2398			if (Editor.isDarkMode())
2399			{
2400				inp.style.filter = 'invert(100%)';
2401			}
2402
2403			left.appendChild(inp);
2404
2405			mxEvent.addListener(inp, 'click', function(evt)
2406			{
2407				graph.model.setVisible(child, !graph.model.isVisible(child));
2408				mxEvent.consume(evt);
2409			});
2410
2411			var btn = document.createElement('img');
2412			btn.setAttribute('draggable', 'false');
2413			btn.setAttribute('align', 'top');
2414			btn.setAttribute('border', '0');
2415			btn.style.width = '16px';
2416			btn.style.padding = '0px 6px 0 0';
2417			btn.style.marginTop = '2px';
2418			btn.setAttribute('title', mxResources.get('lockUnlock'));
2419
2420			var style = graph.getCurrentCellStyle(child);
2421
2422			if (mxUtils.getValue(style, 'locked', '0') == '1')
2423			{
2424				btn.setAttribute('src', Editor.lockedImage);
2425				mxUtils.setOpacity(btn, 75);
2426			}
2427			else
2428			{
2429				btn.setAttribute('src', Editor.unlockedImage);
2430				mxUtils.setOpacity(btn, 25);
2431			}
2432
2433			if (Editor.isDarkMode())
2434			{
2435				btn.style.filter = 'invert(100%)';
2436			}
2437
2438			if (graph.isEnabled())
2439			{
2440				btn.style.cursor = 'pointer';
2441			}
2442
2443			mxEvent.addListener(btn, 'click', function(evt)
2444			{
2445				if (graph.isEnabled())
2446				{
2447					var value = null;
2448
2449					graph.getModel().beginUpdate();
2450					try
2451					{
2452			    		value = (mxUtils.getValue(style, 'locked', '0') == '1') ? null : '1';
2453			    		graph.setCellStyles('locked', value, [child]);
2454					}
2455					finally
2456					{
2457						graph.getModel().endUpdate();
2458					}
2459
2460					if (value == '1')
2461					{
2462						graph.removeSelectionCells(graph.getModel().getDescendants(child));
2463					}
2464
2465					mxEvent.consume(evt);
2466				}
2467			});
2468
2469			left.appendChild(btn);
2470
2471			var span = document.createElement('span');
2472			mxUtils.write(span, label);
2473			span.style.display = 'block';
2474			span.style.whiteSpace = 'nowrap';
2475			span.style.overflow = 'hidden';
2476			span.style.textOverflow = 'ellipsis';
2477			span.style.position = 'absolute';
2478			span.style.left = '52px';
2479			span.style.right = '8px';
2480			span.style.top = '8px';
2481
2482			left.appendChild(span);
2483			ldiv.appendChild(left);
2484
2485			if (graph.isEnabled())
2486			{
2487				// Fallback if no drag and drop is available
2488				if (mxClient.IS_TOUCH || mxClient.IS_POINTER ||
2489					(mxClient.IS_IE && document.documentMode < 10))
2490				{
2491					var right = document.createElement('div');
2492					right.style.display = 'block';
2493					right.style.textAlign = 'right';
2494					right.style.whiteSpace = 'nowrap';
2495					right.style.position = 'absolute';
2496					right.style.right = '16px';
2497					right.style.top = '6px';
2498
2499					// Poor man's change layer order
2500					if (index > 0)
2501					{
2502						var img2 = document.createElement('a');
2503
2504						img2.setAttribute('title', mxResources.get('toBack'));
2505
2506						img2.className = 'geButton';
2507						img2.style.cssFloat = 'none';
2508						img2.innerHTML = '&#9660;';
2509						img2.style.width = '14px';
2510						img2.style.height = '14px';
2511						img2.style.fontSize = '14px';
2512						img2.style.margin = '0px';
2513						img2.style.marginTop = '-1px';
2514						right.appendChild(img2);
2515
2516						mxEvent.addListener(img2, 'click', function(evt)
2517						{
2518							if (graph.isEnabled())
2519							{
2520								graph.addCell(child, graph.model.root, index - 1);
2521							}
2522
2523							mxEvent.consume(evt);
2524						});
2525					}
2526
2527					if (index >= 0 && index < layerCount - 1)
2528					{
2529						var img1 = document.createElement('a');
2530
2531						img1.setAttribute('title', mxResources.get('toFront'));
2532
2533						img1.className = 'geButton';
2534						img1.style.cssFloat = 'none';
2535						img1.innerHTML = '&#9650;';
2536						img1.style.width = '14px';
2537						img1.style.height = '14px';
2538						img1.style.fontSize = '14px';
2539						img1.style.margin = '0px';
2540						img1.style.marginTop = '-1px';
2541						right.appendChild(img1);
2542
2543						mxEvent.addListener(img1, 'click', function(evt)
2544						{
2545							if (graph.isEnabled())
2546							{
2547								graph.addCell(child, graph.model.root, index + 1);
2548							}
2549
2550							mxEvent.consume(evt);
2551						});
2552					}
2553
2554					ldiv.appendChild(right);
2555				}
2556
2557				if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10))
2558				{
2559					ldiv.setAttribute('draggable', 'true');
2560					ldiv.style.cursor = 'move';
2561				}
2562			}
2563
2564			mxEvent.addListener(ldiv, 'dblclick', function(evt)
2565			{
2566				var nodeName = mxEvent.getSource(evt).nodeName;
2567
2568				if (nodeName != 'INPUT' && nodeName != 'IMG')
2569				{
2570					renameLayer(child);
2571					mxEvent.consume(evt);
2572				}
2573			});
2574
2575			if (graph.getDefaultParent() == child)
2576			{
2577				ldiv.style.background = (!Editor.isDarkMode()) ? '#e6eff8' : '#505759';
2578				ldiv.style.fontWeight = (graph.isEnabled()) ? 'bold' : '';
2579				selectionLayer = child;
2580			}
2581
2582			mxEvent.addListener(ldiv, 'click', function(evt)
2583			{
2584				if (graph.isEnabled())
2585				{
2586					graph.setDefaultParent(defaultParent);
2587					graph.view.setCurrentRoot(null);
2588
2589					if (mxEvent.isShiftDown(evt))
2590					{
2591						graph.setSelectionCells(child.children);
2592					}
2593
2594					mxEvent.consume(evt);
2595				}
2596			});
2597
2598			listDiv.appendChild(ldiv);
2599		};
2600
2601		// Cannot be moved or deleted
2602		for (var i = layerCount - 1; i >= 0; i--)
2603		{
2604			(mxUtils.bind(this, function(child)
2605			{
2606				addLayer(i, graph.convertValueToString(child) ||
2607					mxResources.get('background'), child, child);
2608			}))(graph.model.getChildAt(graph.model.root, i));
2609		}
2610
2611		var label = graph.convertValueToString(selectionLayer) || mxResources.get('background');
2612		removeLink.setAttribute('title', mxResources.get('removeIt', [label]));
2613		duplicateLink.setAttribute('title', mxResources.get('duplicateIt', [label]));
2614
2615		if (graph.isSelectionEmpty())
2616		{
2617			insertLink.className = 'geButton mxDisabled';
2618		}
2619
2620		updateLayerDot();
2621	};
2622
2623	refresh();
2624	graph.model.addListener(mxEvent.CHANGE, refresh);
2625	graph.addListener('defaultParentChanged', refresh);
2626
2627	graph.selectionModel.addListener(mxEvent.CHANGE, function()
2628	{
2629		if (graph.isSelectionEmpty())
2630		{
2631			insertLink.className = 'geButton mxDisabled';
2632		}
2633		else
2634		{
2635			insertLink.className = 'geButton';
2636		}
2637
2638		updateLayerDot();
2639	});
2640
2641	this.window = new mxWindow(mxResources.get('layers'), div, x, y, w, h, true, true);
2642	this.window.minimumSize = new mxRectangle(0, 0, 150, 120);
2643	this.window.destroyOnClose = false;
2644	this.window.setMaximizable(false);
2645	this.window.setResizable(true);
2646	this.window.setClosable(true);
2647	this.window.setVisible(true);
2648
2649	this.init = function()
2650	{
2651		listDiv.scrollTop = listDiv.scrollHeight - listDiv.clientHeight;
2652	};
2653
2654	this.window.addListener(mxEvent.SHOW, mxUtils.bind(this, function()
2655	{
2656		this.window.fit();
2657	}));
2658
2659	// Make refresh available via instance
2660	this.refreshLayers = refresh;
2661
2662	this.window.setLocation = function(x, y)
2663	{
2664		var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
2665		var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;
2666
2667		x = Math.max(0, Math.min(x, iw - this.table.clientWidth));
2668		y = Math.max(0, Math.min(y, ih - this.table.clientHeight - ((urlParams['sketch'] == '1') ? 3 : 48)));
2669
2670		if (this.getX() != x || this.getY() != y)
2671		{
2672			mxWindow.prototype.setLocation.apply(this, arguments);
2673		}
2674	};
2675
2676	var resizeListener = mxUtils.bind(this, function()
2677	{
2678		var x = this.window.getX();
2679		var y = this.window.getY();
2680
2681		this.window.setLocation(x, y);
2682	});
2683
2684	mxEvent.addListener(window, 'resize', resizeListener);
2685
2686	this.destroy = function()
2687	{
2688		mxEvent.removeListener(window, 'resize', resizeListener);
2689		this.window.destroy();
2690	}
2691};
2692