1/**
2 * Copyright (c) 2006-2012, JGraph Ltd
3 */
4/**
5 * Editor constructor executed on page load.
6 */
7Editor = function(chromeless, themes, model, graph, editable)
8{
9	mxEventSource.call(this);
10	this.chromeless = (chromeless != null) ? chromeless : this.chromeless;
11	this.initStencilRegistry();
12	this.graph = graph || this.createGraph(themes, model);
13	this.editable = (editable != null) ? editable : !chromeless;
14	this.undoManager = this.createUndoManager();
15	this.status = '';
16
17	this.getOrCreateFilename = function()
18	{
19		return this.filename || mxResources.get('drawing', [Editor.pageCounter]) + '.xml';
20	};
21
22	this.getFilename = function()
23	{
24		return this.filename;
25	};
26
27	// Sets the status and fires a statusChanged event
28	this.setStatus = function(value)
29	{
30		this.status = value;
31		this.fireEvent(new mxEventObject('statusChanged'));
32	};
33
34	// Returns the current status
35	this.getStatus = function()
36	{
37		return this.status;
38	};
39
40	// Updates modified state if graph changes
41	this.graphChangeListener = function(sender, eventObject)
42	{
43		var edit = (eventObject != null) ? eventObject.getProperty('edit') : null;
44
45		if (edit == null || !edit.ignoreEdit)
46		{
47			this.setModified(true);
48		}
49	};
50
51	this.graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function()
52	{
53		this.graphChangeListener.apply(this, arguments);
54	}));
55
56	// Sets persistent graph state defaults
57	this.graph.resetViewOnRootChange = false;
58	this.init();
59};
60
61/**
62 * Counts open editor tabs (must be global for cross-window access)
63 */
64Editor.pageCounter = 0;
65
66// Cross-domain window access is not allowed in FF, so if we
67// were opened from another domain then this will fail.
68(function()
69{
70	try
71	{
72		var op = window;
73
74		while (op.opener != null && typeof op.opener.Editor !== 'undefined' &&
75			!isNaN(op.opener.Editor.pageCounter) &&
76			// Workaround for possible infinite loop in FF https://drawio.atlassian.net/browse/DS-795
77			op.opener != op)
78		{
79			op = op.opener;
80		}
81
82		// Increments the counter in the first opener in the chain
83		if (op != null)
84		{
85			op.Editor.pageCounter++;
86			Editor.pageCounter = op.Editor.pageCounter;
87		}
88	}
89	catch (e)
90	{
91		// ignore
92	}
93})();
94
95/**
96 *
97 */
98Editor.defaultHtmlFont = '-apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", system-ui, ui-sans-serif, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"';
99
100/**
101 * Specifies if local storage should be used (eg. on the iPad which has no filesystem)
102 */
103Editor.useLocalStorage = typeof(Storage) != 'undefined' && mxClient.IS_IOS;
104
105/**
106 *
107 */
108Editor.rowMoveImage = (mxClient.IS_SVG) ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAEBAMAAACw6DhOAAAAGFBMVEUzMzP///9tbW1QUFCKiopBQUF8fHxfX1/IXlmXAAAAFElEQVQImWNgNVdzYBAUFBRggLMAEzYBy29kEPgAAAAASUVORK5CYII=' :
109	IMAGE_PATH + '/thumb_horz.png';
110
111/**
112 * Sets the default font size.
113 */
114Editor.lightCheckmarkImage = (mxClient.IS_SVG) ? 'data:image/gif;base64,R0lGODlhFQAVAMQfAGxsbHx8fIqKioaGhvb29nJycvr6+sDAwJqamltbW5OTk+np6YGBgeTk5Ly8vJiYmP39/fLy8qWlpa6ursjIyOLi4vj4+N/f3+3t7fT09LCwsHZ2dubm5r6+vmZmZv///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OEY4NTZERTQ5QUFBMTFFMUE5MTVDOTM5MUZGMTE3M0QiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OEY4NTZERTU5QUFBMTFFMUE5MTVDOTM5MUZGMTE3M0QiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Rjg1NkRFMjlBQUExMUUxQTkxNUM5MzkxRkYxMTczRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Rjg1NkRFMzlBQUExMUUxQTkxNUM5MzkxRkYxMTczRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAB8ALAAAAAAVABUAAAVI4CeOZGmeaKqubKtylktSgCOLRyLd3+QJEJnh4VHcMoOfYQXQLBcBD4PA6ngGlIInEHEhPOANRkaIFhq8SuHCE1Hb8Lh8LgsBADs=' :
115	IMAGE_PATH + '/checkmark.gif';
116
117/**
118 *
119 */
120Editor.darkHelpImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAP1BMVEUAAAD///////////////////////////////////////////////////////////////////////////////9Du/pqAAAAFXRSTlMAT30qCJRBboyDZyCgRzUUdF46MJlgXETgAAAAeklEQVQY022O2w4DIQhEQUURda/9/28tUO2+7CQS5sgQ4F1RapX78YUwRqQjTU8ILqQfKerTKTvACJ4nLX3krt+8aS82oI8aQC4KavRgtvEW/mDvsICgA03PSGRr79MqX1YPNIxzjyqtw8ZnnRo4t5a5undtJYRywau+ds4Cyza3E6YAAAAASUVORK5CYII=';
121
122/**
123 *
124 */
125Editor.darkCheckmarkImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAMAAACeyVWkAAAARVBMVEUAAACZmZkICAgEBASNjY2Dg4MYGBiTk5N5eXl1dXVmZmZQUFBCQkI3NzceHh4MDAykpKSJiYl+fn5sbGxaWlo/Pz8SEhK96uPlAAAAAXRSTlMAQObYZgAAAE5JREFUGNPFzTcSgDAQQ1HJGUfy/Y9K7V1qeOUfzQifCQZai1XHaz11LFysbDbzgDSSWMZiETz3+b8yNUc/MMsktxuC8XQBSncdLwz+8gCCggGXzBcozAAAAABJRU5ErkJggg==';
126
127/**
128 *
129 */
130Editor.menuImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTMgMThoMTh2LTJIM3Yyem0wLTVoMTh2LTJIM3Yyem0wLTd2MmgxOFY2SDN6Ii8+PC9zdmc+';
131Editor.lightHelpImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJub25lIiBkPSJNMCAwaDI0djI0SDB6Ii8+PHBhdGggZD0iTTExIDE4aDJ2LTJoLTJ2MnptMS0xNkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6bTAtMTRjLTIuMjEgMC00IDEuNzktNCA0aDJjMC0xLjEuOS0yIDItMnMyIC45IDIgMmMwIDItMyAxLjc1LTMgNWgyYzAtMi4yNSAzLTIuNSAzLTUgMC0yLjIxLTEuNzktNC00LTR6Ii8+PC9zdmc+';
132Editor.moveImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI4cHgiIGhlaWdodD0iMjhweCI+PGc+PC9nPjxnPjxnPjxnPjxwYXRoIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIuNCwyLjQpc2NhbGUoMC44KXJvdGF0ZSg0NSwxMiwxMikiIHN0cm9rZT0iIzI5YjZmMiIgZmlsbD0iIzI5YjZmMiIgZD0iTTE1LDNsMi4zLDIuM2wtMi44OSwyLjg3bDEuNDIsMS40MkwxOC43LDYuN0wyMSw5VjNIMTV6IE0zLDlsMi4zLTIuM2wyLjg3LDIuODlsMS40Mi0xLjQyTDYuNyw1LjNMOSwzSDNWOXogTTksMjEgbC0yLjMtMi4zbDIuODktMi44N2wtMS40Mi0xLjQyTDUuMywxNy4zTDMsMTV2Nkg5eiBNMjEsMTVsLTIuMywyLjNsLTIuODctMi44OWwtMS40MiwxLjQybDIuODksMi44N0wxNSwyMWg2VjE1eiIvPjwvZz48L2c+PC9nPjwvc3ZnPgo=';
133Editor.zoomInImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTUuNSAxNGgtLjc5bC0uMjgtLjI3QzE1LjQxIDEyLjU5IDE2IDExLjExIDE2IDkuNSAxNiA1LjkxIDEzLjA5IDMgOS41IDNTMyA1LjkxIDMgOS41IDUuOTEgMTYgOS41IDE2YzEuNjEgMCAzLjA5LS41OSA0LjIzLTEuNTdsLjI3LjI4di43OWw1IDQuOTlMMjAuNDkgMTlsLTQuOTktNXptLTYgMEM3LjAxIDE0IDUgMTEuOTkgNSA5LjVTNy4wMSA1IDkuNSA1IDE0IDcuMDEgMTQgOS41IDExLjk5IDE0IDkuNSAxNHptMi41LTRoLTJ2Mkg5di0ySDdWOWgyVjdoMXYyaDJ2MXoiLz48L3N2Zz4=';
134Editor.zoomOutImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTUuNSAxNGgtLjc5bC0uMjgtLjI3QzE1LjQxIDEyLjU5IDE2IDExLjExIDE2IDkuNSAxNiA1LjkxIDEzLjA5IDMgOS41IDNTMyA1LjkxIDMgOS41IDUuOTEgMTYgOS41IDE2YzEuNjEgMCAzLjA5LS41OSA0LjIzLTEuNTdsLjI3LjI4di43OWw1IDQuOTlMMjAuNDkgMTlsLTQuOTktNXptLTYgMEM3LjAxIDE0IDUgMTEuOTkgNSA5LjVTNy4wMSA1IDkuNSA1IDE0IDcuMDEgMTQgOS41IDExLjk5IDE0IDkuNSAxNHpNNyA5aDV2MUg3eiIvPjwvc3ZnPg==';
135Editor.fullscreenImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMyA1djRoMlY1aDRWM0g1Yy0xLjEgMC0yIC45LTIgMnptMiAxMEgzdjRjMCAxLjEuOSAyIDIgMmg0di0ySDV2LTR6bTE0IDRoLTR2Mmg0YzEuMSAwIDItLjkgMi0ydi00aC0ydjR6bTAtMTZoLTR2Mmg0djRoMlY1YzAtMS4xLS45LTItMi0yeiIvPjwvc3ZnPg==';
136Editor.fullscreenExitImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTUgMTZoM3YzaDJ2LTVINXYyem0zLThINXYyaDVWNUg4djN6bTYgMTFoMnYtM2gzdi0yaC01djV6bTItMTFWNWgtMnY1aDVWOGgtM3oiLz48L3N2Zz4=';
137Editor.zoomFitImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTUgMTVIM3Y0YzAgMS4xLjkgMiAyIDJoNHYtMkg1di00ek01IDVoNFYzSDVjLTEuMSAwLTIgLjktMiAydjRoMlY1em03IDNjLTIuMjEgMC00IDEuNzktNCA0czEuNzkgNCA0IDQgNC0xLjc5IDQtNC0xLjc5LTQtNC00em0wIDZjLTEuMSAwLTItLjktMi0ycy45LTIgMi0yIDIgLjkgMiAyLS45IDItMiAyem03LTExaC00djJoNHY0aDJWNWMwLTEuMS0uOS0yLTItMnptMCAxNmgtNHYyaDRjMS4xIDAgMi0uOSAyLTJ2LTRoLTJ2NHoiLz48L3N2Zz4=';
138Editor.layersImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTExLjk5IDE4LjU0bC03LjM3LTUuNzNMMyAxNC4wN2w5IDcgOS03LTEuNjMtMS4yN3pNMTIgMTZsNy4zNi01LjczTDIxIDlsLTktNy05IDcgMS42MyAxLjI3TDEyIDE2em0wLTExLjQ3TDE3Ljc0IDkgMTIgMTMuNDcgNi4yNiA5IDEyIDQuNTN6Ii8+PC9zdmc+';
139Editor.previousImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE1LjQxIDcuNDFMMTQgNmwtNiA2IDYgNiAxLjQxLTEuNDFMMTAuODMgMTJsNC41OC00LjU5eiIvPjwvc3ZnPg==';
140Editor.nextImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02LTYtNnoiLz48L3N2Zz4=';
141Editor.editImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE0LjA2IDkuMDJsLjkyLjkyTDUuOTIgMTlINXYtLjkybDkuMDYtOS4wNk0xNy42NiAzYy0uMjUgMC0uNTEuMS0uNy4yOWwtMS44MyAxLjgzIDMuNzUgMy43NSAxLjgzLTEuODNjLjM5LS4zOS4zOS0xLjAyIDAtMS40MWwtMi4zNC0yLjM0Yy0uMi0uMi0uNDUtLjI5LS43MS0uMjl6bS0zLjYgMy4xOUwzIDE3LjI1VjIxaDMuNzVMMTcuODEgOS45NGwtMy43NS0zLjc1eiIvPjwvc3ZnPg==';
142Editor.duplicateImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE2IDFINGMtMS4xIDAtMiAuOS0yIDJ2MTRoMlYzaDEyVjF6bTMgNEg4Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDExYzEuMSAwIDItLjkgMi0yVjdjMC0xLjEtLjktMi0yLTJ6bTAgMTZIOFY3aDExdjE0eiIvPjwvc3ZnPg==';
143Editor.addImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE5IDEzaC02djZoLTJ2LTZINXYtMmg2VjVoMnY2aDZ2MnoiLz48L3N2Zz4=';
144Editor.crossImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE5IDYuNDFMMTcuNTkgNSAxMiAxMC41OSA2LjQxIDUgNSA2LjQxIDEwLjU5IDEyIDUgMTcuNTkgNi40MSAxOSAxMiAxMy40MSAxNy41OSAxOSAxOSAxNy41OSAxMy40MSAxMiAxOSA2LjQxeiIvPjwvc3ZnPg==';
145Editor.verticalDotsImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEyIDhjMS4xIDAgMi0uOSAyLTJzLS45LTItMi0yLTIgLjktMiAyIC45IDIgMiAyem0wIDJjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yem0wIDZjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yeiIvPjwvc3ZnPg==';
146Editor.trashImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE2IDl2MTBIOFY5aDhtLTEuNS02aC01bC0xIDFINXYyaDE0VjRoLTMuNWwtMS0xek0xOCA3SDZ2MTJjMCAxLjEuOSAyIDIgMmg4YzEuMSAwIDItLjkgMi0yVjd6Ii8+PC9zdmc+';
147Editor.hiddenImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6bTAgMGgyNHYyNEgwVjB6bTAgMGgyNHYyNEgwVjB6bTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEyIDZjMy43OSAwIDcuMTcgMi4xMyA4LjgyIDUuNS0uNTkgMS4yMi0xLjQyIDIuMjctMi40MSAzLjEybDEuNDEgMS40MWMxLjM5LTEuMjMgMi40OS0yLjc3IDMuMTgtNC41M0MyMS4yNyA3LjExIDE3IDQgMTIgNGMtMS4yNyAwLTIuNDkuMi0zLjY0LjU3bDEuNjUgMS42NUMxMC42NiA2LjA5IDExLjMyIDYgMTIgNnptLTEuMDcgMS4xNEwxMyA5LjIxYy41Ny4yNSAxLjAzLjcxIDEuMjggMS4yOGwyLjA3IDIuMDdjLjA4LS4zNC4xNC0uNy4xNC0xLjA3QzE2LjUgOS4wMSAxNC40OCA3IDEyIDdjLS4zNyAwLS43Mi4wNS0xLjA3LjE0ek0yLjAxIDMuODdsMi42OCAyLjY4QzMuMDYgNy44MyAxLjc3IDkuNTMgMSAxMS41IDIuNzMgMTUuODkgNyAxOSAxMiAxOWMxLjUyIDAgMi45OC0uMjkgNC4zMi0uODJsMy40MiAzLjQyIDEuNDEtMS40MUwzLjQyIDIuNDUgMi4wMSAzLjg3em03LjUgNy41bDIuNjEgMi42MWMtLjA0LjAxLS4wOC4wMi0uMTIuMDItMS4zOCAwLTIuNS0xLjEyLTIuNS0yLjUgMC0uMDUuMDEtLjA4LjAxLS4xM3ptLTMuNC0zLjRsMS43NSAxLjc1Yy0uMjMuNTUtLjM2IDEuMTUtLjM2IDEuNzggMCAyLjQ4IDIuMDIgNC41IDQuNSA0LjUuNjMgMCAxLjIzLS4xMyAxLjc3LS4zNmwuOTguOThjLS44OC4yNC0xLjguMzgtMi43NS4zOC0zLjc5IDAtNy4xNy0yLjEzLTguODItNS41LjctMS40MyAxLjcyLTIuNjEgMi45My0zLjUzeiIvPjwvc3ZnPg==';
148Editor.visibleImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEyIDZjMy43OSAwIDcuMTcgMi4xMyA4LjgyIDUuNUMxOS4xNyAxNC44NyAxNS43OSAxNyAxMiAxN3MtNy4xNy0yLjEzLTguODItNS41QzQuODMgOC4xMyA4LjIxIDYgMTIgNm0wLTJDNyA0IDIuNzMgNy4xMSAxIDExLjUgMi43MyAxNS44OSA3IDE5IDEyIDE5czkuMjctMy4xMSAxMS03LjVDMjEuMjcgNy4xMSAxNyA0IDEyIDR6bTAgNWMxLjM4IDAgMi41IDEuMTIgMi41IDIuNVMxMy4zOCAxNCAxMiAxNHMtMi41LTEuMTItMi41LTIuNVMxMC42MiA5IDEyIDltMC0yYy0yLjQ4IDAtNC41IDIuMDItNC41IDQuNVM5LjUyIDE2IDEyIDE2czQuNS0yLjAyIDQuNS00LjVTMTQuNDggNyAxMiA3eiIvPjwvc3ZnPg==';
149Editor.lockedImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PGcgZmlsbD0ibm9uZSI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6Ii8+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBvcGFjaXR5PSIuODciLz48L2c+PHBhdGggZD0iTTE4IDhoLTFWNmMwLTIuNzYtMi4yNC01LTUtNVM3IDMuMjQgNyA2djJINmMtMS4xIDAtMiAuOS0yIDJ2MTBjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlYxMGMwLTEuMS0uOS0yLTItMnpNOSA2YzAtMS42NiAxLjM0LTMgMy0zczMgMS4zNCAzIDN2Mkg5VjZ6bTkgMTRINlYxMGgxMnYxMHptLTYtM2MxLjEgMCAyLS45IDItMnMtLjktMi0yLTItMiAuOS0yIDIgLjkgMiAyIDJ6Ii8+PC9zdmc+';
150Editor.unlockedImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE4IDhoLTFWNmMwLTIuNzYtMi4yNC01LTUtNVM3IDMuMjQgNyA2aDJjMC0xLjY2IDEuMzQtMyAzLTNzMyAxLjM0IDMgM3YySDZjLTEuMSAwLTIgLjktMiAydjEwYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWMTBjMC0xLjEtLjktMi0yLTJ6bTAgMTJINlYxMGgxMnYxMHptLTYtM2MxLjEgMCAyLS45IDItMnMtLjktMi0yLTItMiAuOS0yIDIgLjkgMiAyIDJ6Ii8+PC9zdmc+';
151Editor.printImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE5IDhoLTFWM0g2djVINWMtMS42NiAwLTMgMS4zNC0zIDN2Nmg0djRoMTJ2LTRoNHYtNmMwLTEuNjYtMS4zNC0zLTMtM3pNOCA1aDh2M0g4VjV6bTggMTJ2Mkg4di00aDh2MnptMi0ydi0ySDZ2Mkg0di00YzAtLjU1LjQ1LTEgMS0xaDE0Yy41NSAwIDEgLjQ1IDEgMXY0aC0yeiIvPjxjaXJjbGUgY3g9IjE4IiBjeT0iMTEuNSIgcj0iMSIvPjwvc3ZnPg==';
152Editor.refreshImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE3LjY1IDYuMzVDMTYuMiA0LjkgMTQuMjEgNCAxMiA0Yy00LjQyIDAtNy45OSAzLjU4LTcuOTkgOHMzLjU3IDggNy45OSA4YzMuNzMgMCA2Ljg0LTIuNTUgNy43My02aC0yLjA4Yy0uODIgMi4zMy0zLjA0IDQtNS42NSA0LTMuMzEgMC02LTIuNjktNi02czIuNjktNiA2LTZjMS42NiAwIDMuMTQuNjkgNC4yMiAxLjc4TDEzIDExaDdWNGwtMi4zNSAyLjM1eiIvPjwvc3ZnPg==';
153Editor.backImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIiBvcGFjaXR5PSIuODciLz48cGF0aCBkPSJNMTcuNTEgMy44N0wxNS43MyAyLjEgNS44NCAxMmw5LjkgOS45IDEuNzctMS43N0w5LjM4IDEybDguMTMtOC4xM3oiLz48L3N2Zz4=';
154Editor.closeImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIiBvcGFjaXR5PSIuODciLz48cGF0aCBkPSJNMTIgMkM2LjQ3IDIgMiA2LjQ3IDIgMTJzNC40NyAxMCAxMCAxMCAxMC00LjQ3IDEwLTEwUzE3LjUzIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6bTMuNTktMTNMMTIgMTAuNTkgOC40MSA3IDcgOC40MSAxMC41OSAxMiA3IDE1LjU5IDguNDEgMTcgMTIgMTMuNDEgMTUuNTkgMTcgMTcgMTUuNTkgMTMuNDEgMTIgMTcgOC40MXoiLz48L3N2Zz4='
155Editor.closeBlackImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjZweCI+PGVsbGlwc2UgY3g9IjEyIiBjeT0iMTIiIHJ4PSI5IiByeT0iOSIgc3Ryb2tlPSJub25lIiBmaWxsPSIjMDAwIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE0LjU5IDhMMTIgMTAuNTkgOS40MSA4IDggOS40MSAxMC41OSAxMiA4IDE0LjU5IDkuNDEgMTYgMTIgMTMuNDEgMTQuNTkgMTYgMTYgMTQuNTkgMTMuNDEgMTIgMTYgOS40MSAxNC41OSA4ek0xMiAyQzYuNDcgMiAyIDYuNDcgMiAxMnM0LjQ3IDEwIDEwIDEwIDEwLTQuNDcgMTAtMTBTMTcuNTMgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDgtMy41OSA4LTggOHoiLz48L3N2Zz4=';
156Editor.plusImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgMTNoLTZ2NmgtMnYtNkg1di0yaDZWNWgydjZoNnYyeiIvPjwvc3ZnPg==';
157Editor.shapesImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0cHgiIGZpbGw9IiMwMDAwMDAiPjxnPjxwYXRoIGQ9Ik0wLDBoMjR2MjRIMFYweiIgZmlsbD0ibm9uZSIvPjwvZz48Zz48Zz48cGF0aCBkPSJNMywxMWg4VjNIM1YxMXogTTUsNWg0djRINVY1eiIvPjxwYXRoIGQ9Ik0xMywzdjhoOFYzSDEzeiBNMTksOWgtNFY1aDRWOXoiLz48cGF0aCBkPSJNMywyMWg4di04SDNWMjF6IE01LDE1aDR2NEg1VjE1eiIvPjxwb2x5Z29uIHBvaW50cz0iMTgsMTMgMTYsMTMgMTYsMTYgMTMsMTYgMTMsMTggMTYsMTggMTYsMjEgMTgsMjEgMTgsMTggMjEsMTggMjEsMTYgMTgsMTYiLz48L2c+PC9nPjwvc3ZnPg==';
158Editor.formatImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgM2MtNC45NyAwLTkgNC4wMy05IDlzNC4wMyA5IDkgOWMuODMgMCAxLjUtLjY3IDEuNS0xLjUgMC0uMzktLjE1LS43NC0uMzktMS4wMS0uMjMtLjI2LS4zOC0uNjEtLjM4LS45OSAwLS44My42Ny0xLjUgMS41LTEuNUgxNmMyLjc2IDAgNS0yLjI0IDUtNSAwLTQuNDItNC4wMy04LTktOHptLTUuNSA5Yy0uODMgMC0xLjUtLjY3LTEuNS0xLjVTNS42NyA5IDYuNSA5IDggOS42NyA4IDEwLjUgNy4zMyAxMiA2LjUgMTJ6bTMtNEM4LjY3IDggOCA3LjMzIDggNi41UzguNjcgNSA5LjUgNXMxLjUuNjcgMS41IDEuNVMxMC4zMyA4IDkuNSA4em01IDBjLS44MyAwLTEuNS0uNjctMS41LTEuNVMxMy42NyA1IDE0LjUgNXMxLjUuNjcgMS41IDEuNVMxNS4zMyA4IDE0LjUgOHptMyA0Yy0uODMgMC0xLjUtLjY3LTEuNS0xLjVTMTYuNjcgOSAxNy41IDlzMS41LjY3IDEuNSAxLjUtLjY3IDEuNS0xLjUgMS41eiIvPjwvc3ZnPg==';
159Editor.freehandImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIyNCIgd2lkdGg9IjI0Ii8+PHBhdGggZD0iTTQuNSw4YzEuMDQsMCwyLjM0LTEuNSw0LjI1LTEuNWMxLjUyLDAsMi43NSwxLjIzLDIuNzUsMi43NWMwLDIuMDQtMS45OSwzLjE1LTMuOTEsNC4yMkM1LjQyLDE0LjY3LDQsMTUuNTcsNCwxNyBjMCwxLjEsMC45LDIsMiwydjJjLTIuMjEsMC00LTEuNzktNC00YzAtMi43MSwyLjU2LTQuMTQsNC42Mi01LjI4YzEuNDItMC43OSwyLjg4LTEuNiwyLjg4LTIuNDdjMC0wLjQxLTAuMzQtMC43NS0wLjc1LTAuNzUgQzcuNSw4LjUsNi4yNSwxMCw0LjUsMTBDMy4xMiwxMCwyLDguODgsMiw3LjVDMiw1LjQ1LDQuMTcsMi44Myw1LDJsMS40MSwxLjQxQzUuNDEsNC40Miw0LDYuNDMsNCw3LjVDNCw3Ljc4LDQuMjIsOCw0LjUsOHogTTgsMjEgbDMuNzUsMGw4LjA2LTguMDZsLTMuNzUtMy43NUw4LDE3LjI1TDgsMjF6IE0xMCwxOC4wOGw2LjA2LTYuMDZsMC45MiwwLjkyTDEwLjkyLDE5TDEwLDE5TDEwLDE4LjA4eiBNMjAuMzcsNi4yOSBjLTAuMzktMC4zOS0xLjAyLTAuMzktMS40MSwwbC0xLjgzLDEuODNsMy43NSwzLjc1bDEuODMtMS44M2MwLjM5LTAuMzksMC4zOS0xLjAyLDAtMS40MUwyMC4zNyw2LjI5eiIvPjwvc3ZnPg==';
160Editor.templateImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEzIDExaC0ydjNIOHYyaDN2M2gydi0zaDN2LTJoLTN6bTEtOUg2Yy0xLjEgMC0yIC45LTIgMnYxNmMwIDEuMS44OSAyIDEuOTkgMkgxOGMxLjEgMCAyLS45IDItMlY4bC02LTZ6bTQgMThINlY0aDd2NWg1djExeiIvPjwvc3ZnPg==';
161Editor.darkImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik05LjM3LDUuNTFDOS4xOSw2LjE1LDkuMSw2LjgyLDkuMSw3LjVjMCw0LjA4LDMuMzIsNy40LDcuNCw3LjRjMC42OCwwLDEuMzUtMC4wOSwxLjk5LTAuMjdDMTcuNDUsMTcuMTksMTQuOTMsMTksMTIsMTkgYy0zLjg2LDAtNy0zLjE0LTctN0M1LDkuMDcsNi44MSw2LjU1LDkuMzcsNS41MXogTTEyLDNjLTQuOTcsMC05LDQuMDMtOSw5czQuMDMsOSw5LDlzOS00LjAzLDktOWMwLTAuNDYtMC4wNC0wLjkyLTAuMS0xLjM2IGMtMC45OCwxLjM3LTIuNTgsMi4yNi00LjQsMi4yNmMtMi45OCwwLTUuNC0yLjQyLTUuNC01LjRjMC0xLjgxLDAuODktMy40MiwyLjI2LTQuNEMxMi45MiwzLjA0LDEyLjQ2LDMsMTIsM0wxMiwzeiIvPjwvc3ZnPg==';
162Editor.lightImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0xMiw5YzEuNjUsMCwzLDEuMzUsMywzcy0xLjM1LDMtMywzcy0zLTEuMzUtMy0zUzEwLjM1LDksMTIsOSBNMTIsN2MtMi43NiwwLTUsMi4yNC01LDVzMi4yNCw1LDUsNXM1LTIuMjQsNS01IFMxNC43Niw3LDEyLDdMMTIsN3ogTTIsMTNsMiwwYzAuNTUsMCwxLTAuNDUsMS0xcy0wLjQ1LTEtMS0xbC0yLDBjLTAuNTUsMC0xLDAuNDUtMSwxUzEuNDUsMTMsMiwxM3ogTTIwLDEzbDIsMGMwLjU1LDAsMS0wLjQ1LDEtMSBzLTAuNDUtMS0xLTFsLTIsMGMtMC41NSwwLTEsMC40NS0xLDFTMTkuNDUsMTMsMjAsMTN6IE0xMSwydjJjMCwwLjU1LDAuNDUsMSwxLDFzMS0wLjQ1LDEtMVYyYzAtMC41NS0wLjQ1LTEtMS0xUzExLDEuNDUsMTEsMnogTTExLDIwdjJjMCwwLjU1LDAuNDUsMSwxLDFzMS0wLjQ1LDEtMXYtMmMwLTAuNTUtMC40NS0xLTEtMUMxMS40NSwxOSwxMSwxOS40NSwxMSwyMHogTTUuOTksNC41OGMtMC4zOS0wLjM5LTEuMDMtMC4zOS0xLjQxLDAgYy0wLjM5LDAuMzktMC4zOSwxLjAzLDAsMS40MWwxLjA2LDEuMDZjMC4zOSwwLjM5LDEuMDMsMC4zOSwxLjQxLDBzMC4zOS0xLjAzLDAtMS40MUw1Ljk5LDQuNTh6IE0xOC4zNiwxNi45NSBjLTAuMzktMC4zOS0xLjAzLTAuMzktMS40MSwwYy0wLjM5LDAuMzktMC4zOSwxLjAzLDAsMS40MWwxLjA2LDEuMDZjMC4zOSwwLjM5LDEuMDMsMC4zOSwxLjQxLDBjMC4zOS0wLjM5LDAuMzktMS4wMywwLTEuNDEgTDE4LjM2LDE2Ljk1eiBNMTkuNDIsNS45OWMwLjM5LTAuMzksMC4zOS0xLjAzLDAtMS40MWMtMC4zOS0wLjM5LTEuMDMtMC4zOS0xLjQxLDBsLTEuMDYsMS4wNmMtMC4zOSwwLjM5LTAuMzksMS4wMywwLDEuNDEgczEuMDMsMC4zOSwxLjQxLDBMMTkuNDIsNS45OXogTTcuMDUsMTguMzZjMC4zOS0wLjM5LDAuMzktMS4wMywwLTEuNDFjLTAuMzktMC4zOS0xLjAzLTAuMzktMS40MSwwbC0xLjA2LDEuMDYgYy0wLjM5LDAuMzktMC4zOSwxLjAzLDAsMS40MXMxLjAzLDAuMzksMS40MSwwTDcuMDUsMTguMzZ6Ii8+PC9zdmc+';
163Editor.undoImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIuNSA4Yy0yLjY1IDAtNS4wNS45OS02LjkgMi42TDIgN3Y5aDlsLTMuNjItMy42MmMxLjM5LTEuMTYgMy4xNi0xLjg4IDUuMTItMS44OCAzLjU0IDAgNi41NSAyLjMxIDcuNiA1LjVsMi4zNy0uNzhDMjEuMDggMTEuMDMgMTcuMTUgOCAxMi41IDh6Ii8+PC9zdmc+';
164Editor.redoImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTguNCAxMC42QzE2LjU1IDguOTkgMTQuMTUgOCAxMS41IDhjLTQuNjUgMC04LjU4IDMuMDMtOS45NiA3LjIyTDMuOSAxNmMxLjA1LTMuMTkgNC4wNS01LjUgNy42LTUuNSAxLjk1IDAgMy43My43MiA1LjEyIDEuODhMMTMgMTZoOVY3bC0zLjYgMy42eiIvPjwvc3ZnPg==';
165Editor.outlineImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0yMC41IDNsLS4xNi4wM0wxNSA1LjEgOSAzIDMuMzYgNC45Yy0uMjEuMDctLjM2LjI1LS4zNi40OFYyMC41YzAgLjI4LjIyLjUuNS41bC4xNi0uMDNMOSAxOC45bDYgMi4xIDUuNjQtMS45Yy4yMS0uMDcuMzYtLjI1LjM2LS40OFYzLjVjMC0uMjgtLjIyLS41LS41LS41ek0xNSAxOWwtNi0yLjExVjVsNiAyLjExVjE5eiIvPjwvc3ZnPg==';
166Editor.saveImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE5IDEydjdINXYtN0gzdjdjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMnYtN2gtMnptLTYgLjY3bDIuNTktMi41OEwxNyAxMS41bC01IDUtNS01IDEuNDEtMS40MUwxMSAxMi42N1YzaDJ2OS42N3oiLz48L3N2Zz4=';
167Editor.tableImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTIwIDNINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNWMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yem0wIDJ2M0g1VjVoMTV6bS01IDE0aC01di05aDV2OXpNNSAxMGgzdjlINXYtOXptMTIgOXYtOWgzdjloLTN6Ii8+PC9zdmc+';
168
169/**
170 * All fill styles supported by rough.js.
171 */
172Editor.roughFillStyles = [{val: 'auto', dispName: 'Auto'}, {val: 'hachure', dispName: 'Hachure'}, {val: 'solid', dispName: 'Solid'},
173	{val: 'zigzag', dispName: 'ZigZag'}, {val: 'cross-hatch', dispName: 'Cross Hatch'}, {val: 'dots', dispName: 'Dots'},
174	{val: 'dashed', dispName: 'Dashed'}, {val: 'zigzag-line', dispName: 'ZigZag Line'}];
175
176/**
177 * Graph themes for the format panel.
178 */
179Editor.themes = null;
180
181/**
182 * Specifies the image URL to be used for the transparent background.
183 */
184Editor.ctrlKey = (mxClient.IS_MAC) ? 'Cmd' : 'Ctrl';
185
186/**
187 * Specifies the image URL to be used for the transparent background.
188 */
189Editor.hintOffset = 20;
190
191/**
192 * Delay in ms to show shape picker on hover over blue arrows.
193 */
194Editor.shapePickerHoverDelay = 300;
195
196/**
197 * Specifies the image URL to be used for the transparent background.
198 */
199Editor.fitWindowBorders = null;
200
201/**
202 * Specifies if the diagram should be saved automatically if possible. Default
203 * is true.
204 */
205Editor.popupsAllowed = window.urlParams != null? urlParams['noDevice'] != '1' : true;
206
207/**
208 * Specifies if the html and whiteSpace styles should be removed on inserted cells.
209 */
210Editor.simpleLabels = false;
211
212/**
213 * Specifies if the native clipboard is enabled. Blocked in iframes for possible sandbox attribute.
214 * LATER: Check if actually blocked.
215 */
216Editor.enableNativeCipboard = window == window.top && !mxClient.IS_FF && navigator.clipboard != null;
217
218/**
219 * Dynamic change of dark mode for minimal and sketch theme.
220 */
221Editor.sketchMode = false;
222
223/**
224 * Dynamic change of dark mode for minimal and sketch theme.
225 */
226Editor.darkMode = false;
227
228/**
229 * Dynamic change of dark mode for minimal and sketch theme.
230 */
231Editor.darkColor = '#2a2a2a';
232
233/**
234 * Dynamic change of dark mode for minimal and sketch theme.
235 */
236Editor.lightColor = '#f0f0f0';
237
238/**
239 * Dynamic change of dark mode.
240 */
241Editor.isDarkMode = function(value)
242{
243	return Editor.darkMode || uiTheme == 'dark';
244};
245
246/**
247 * Images below are for lightbox and embedding toolbars.
248 */
249Editor.helpImage = (Editor.isDarkMode() && mxClient.IS_SVG) ? Editor.darkHelpImage : Editor.lightHelpImage;
250
251/**
252 * Images below are for lightbox and embedding toolbars.
253 */
254Editor.checkmarkImage = (Editor.isDarkMode() && mxClient.IS_SVG) ? Editor.darkCheckmarkImage : Editor.lightCheckmarkImage;
255
256/**
257 * Editor inherits from mxEventSource
258 */
259mxUtils.extend(Editor, mxEventSource);
260
261/**
262 * Stores initial state of mxClient.NO_FO.
263 */
264Editor.prototype.originalNoForeignObject = mxClient.NO_FO;
265
266/**
267 * Specifies the image URL to be used for the transparent background.
268 */
269Editor.prototype.transparentImage = (mxClient.IS_SVG) ? 'data:image/gif;base64,R0lGODlhMAAwAIAAAP///wAAACH5BAEAAAAALAAAAAAwADAAAAIxhI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8egpAAA7' :
270	IMAGE_PATH + '/transparent.gif';
271
272/**
273 * Specifies if the canvas should be extended in all directions. Default is true.
274 */
275Editor.prototype.extendCanvas = true;
276
277/**
278 * Specifies if the app should run in chromeless mode. Default is false.
279 * This default is only used if the contructor argument is null.
280 */
281Editor.prototype.chromeless = false;
282
283/**
284 * Specifies the order of OK/Cancel buttons in dialogs. Default is true.
285 * Cancel first is used on Macs, Windows/Confluence uses cancel last.
286 */
287Editor.prototype.cancelFirst = true;
288
289/**
290 * Specifies if the editor is enabled. Default is true.
291 */
292Editor.prototype.enabled = true;
293
294/**
295 * Contains the name which was used for the last save. Default value is null.
296 */
297Editor.prototype.filename = null;
298
299/**
300 * Contains the current modified state of the diagram. This is false for
301 * new diagrams and after the diagram was saved.
302 */
303Editor.prototype.modified = false;
304
305/**
306 * Specifies if the diagram should be saved automatically if possible. Default
307 * is true.
308 */
309Editor.prototype.autosave = true;
310
311/**
312 * Specifies the top spacing for the initial page view. Default is 0.
313 */
314Editor.prototype.initialTopSpacing = 0;
315
316/**
317 * Specifies the app name. Default is document.title.
318 */
319Editor.prototype.appName = document.title;
320
321/**
322 *
323 */
324Editor.prototype.editBlankUrl = window.location.protocol + '//' + window.location.host + '/';
325
326/**
327 * Default value for the graph container overflow style.
328 */
329Editor.prototype.defaultGraphOverflow = 'hidden';
330
331/**
332 * Initializes the environment.
333 */
334Editor.prototype.init = function() { };
335
336/**
337 * Sets the XML node for the current diagram.
338 */
339Editor.prototype.isChromelessView = function()
340{
341	return this.chromeless;
342};
343
344/**
345 * Sets the XML node for the current diagram.
346 */
347Editor.prototype.setAutosave = function(value)
348{
349	this.autosave = value;
350	this.fireEvent(new mxEventObject('autosaveChanged'));
351};
352
353/**
354 *
355 */
356Editor.prototype.getEditBlankUrl = function(params)
357{
358	return this.editBlankUrl + params;
359}
360
361/**
362 *
363 */
364Editor.prototype.editAsNew = function(xml, title)
365{
366	var p = (title != null) ? '?title=' + encodeURIComponent(title) : '';
367
368	if (urlParams['ui'] != null)
369	{
370		p += ((p.length > 0) ? '&' : '?') + 'ui=' + urlParams['ui'];
371	}
372
373	if (typeof window.postMessage !== 'undefined' &&
374		(document.documentMode == null ||
375		document.documentMode >= 10))
376	{
377		var wnd = null;
378
379		var l = mxUtils.bind(this, function(evt)
380		{
381			if (evt.data == 'ready' && evt.source == wnd)
382			{
383				mxEvent.removeListener(window, 'message', l);
384				wnd.postMessage(xml, '*');
385			}
386		});
387
388		mxEvent.addListener(window, 'message', l);
389		wnd = this.graph.openLink(this.getEditBlankUrl(
390			p + ((p.length > 0) ? '&' : '?') +
391			'client=1'), null, true);
392	}
393	else
394	{
395		this.graph.openLink(this.getEditBlankUrl(p) +
396			'#R' + encodeURIComponent(xml));
397	}
398};
399
400/**
401 * Sets the XML node for the current diagram.
402 */
403Editor.prototype.createGraph = function(themes, model)
404{
405	var graph = new Graph(null, model, null, null, themes);
406	graph.transparentBackground = false;
407
408	// Disables CSS transforms in Safari in chromeless mode
409	var graphIsCssTransformsSupported = graph.isCssTransformsSupported;
410	var self = this;
411
412	graph.isCssTransformsSupported = function()
413	{
414		return graphIsCssTransformsSupported.apply(this, arguments) &&
415			(!self.chromeless || !mxClient.IS_SF);
416	};
417
418	// Opens all links in a new window while editing
419	if (!this.chromeless)
420	{
421		graph.isBlankLink = function(href)
422		{
423			return !this.isExternalProtocol(href);
424		};
425	}
426
427	return graph;
428};
429
430/**
431 * Sets the XML node for the current diagram.
432 */
433Editor.prototype.resetGraph = function()
434{
435	this.graph.gridEnabled = this.graph.defaultGridEnabled && (!this.isChromelessView() || urlParams['grid'] == '1');
436	this.graph.graphHandler.guidesEnabled = true;
437	this.graph.setTooltips(true);
438	this.graph.setConnectable(true);
439	this.graph.foldingEnabled = true;
440	this.graph.scrollbars = this.graph.defaultScrollbars;
441	this.graph.pageVisible = this.graph.defaultPageVisible;
442	this.graph.pageBreaksVisible = this.graph.pageVisible;
443	this.graph.preferPageSize = this.graph.pageBreaksVisible;
444	this.graph.background = null;
445	this.graph.pageScale = mxGraph.prototype.pageScale;
446	this.graph.pageFormat = mxGraph.prototype.pageFormat;
447	this.graph.currentScale = 1;
448	this.graph.currentTranslate.x = 0;
449	this.graph.currentTranslate.y = 0;
450	this.updateGraphComponents();
451	this.graph.view.setScale(1);
452};
453
454/**
455 * Sets the XML node for the current diagram.
456 */
457Editor.prototype.readGraphState = function(node)
458{
459	var grid = node.getAttribute('grid');
460
461	if (grid == null || grid == '')
462	{
463		grid = this.graph.defaultGridEnabled ? '1' : '0';
464	}
465
466	this.graph.gridEnabled = grid != '0' && (!this.isChromelessView() || urlParams['grid'] == '1');
467	this.graph.gridSize = parseFloat(node.getAttribute('gridSize')) || mxGraph.prototype.gridSize;
468	this.graph.graphHandler.guidesEnabled = node.getAttribute('guides') != '0';
469	this.graph.setTooltips(node.getAttribute('tooltips') != '0');
470	this.graph.setConnectable(node.getAttribute('connect') != '0');
471	this.graph.connectionArrowsEnabled = node.getAttribute('arrows') != '0';
472	this.graph.foldingEnabled = node.getAttribute('fold') != '0';
473
474	if (this.isChromelessView() && this.graph.foldingEnabled)
475	{
476		this.graph.foldingEnabled = urlParams['nav'] == '1';
477		this.graph.cellRenderer.forceControlClickHandler = this.graph.foldingEnabled;
478	}
479
480	var ps = parseFloat(node.getAttribute('pageScale'));
481
482	if (!isNaN(ps) && ps > 0)
483	{
484		this.graph.pageScale = ps;
485	}
486	else
487	{
488		this.graph.pageScale = mxGraph.prototype.pageScale;
489	}
490
491	if (!this.graph.isLightboxView() && !this.graph.isViewer())
492	{
493		var pv = node.getAttribute('page');
494
495		if (pv != null)
496		{
497			this.graph.pageVisible = (pv != '0');
498		}
499		else
500		{
501			this.graph.pageVisible = this.graph.defaultPageVisible;
502		}
503	}
504	else
505	{
506		this.graph.pageVisible = false;
507	}
508
509	this.graph.pageBreaksVisible = this.graph.pageVisible;
510	this.graph.preferPageSize = this.graph.pageBreaksVisible;
511
512	var pw = parseFloat(node.getAttribute('pageWidth'));
513	var ph = parseFloat(node.getAttribute('pageHeight'));
514
515	if (!isNaN(pw) && !isNaN(ph))
516	{
517		this.graph.pageFormat = new mxRectangle(0, 0, pw, ph);
518	}
519
520	// Loads the persistent state settings
521	var bg = node.getAttribute('background');
522
523	if (bg != null && bg.length > 0)
524	{
525		this.graph.background = bg;
526	}
527	else
528	{
529		this.graph.background = null;
530	}
531};
532
533/**
534 * Sets the XML node for the current diagram.
535 */
536Editor.prototype.setGraphXml = function(node)
537{
538	if (node != null)
539	{
540		var dec = new mxCodec(node.ownerDocument);
541
542		if (node.nodeName == 'mxGraphModel')
543		{
544			this.graph.model.beginUpdate();
545
546			try
547			{
548				this.graph.model.clear();
549				this.graph.view.scale = 1;
550				this.readGraphState(node);
551				this.updateGraphComponents();
552				dec.decode(node, this.graph.getModel());
553			}
554			finally
555			{
556				this.graph.model.endUpdate();
557			}
558
559			this.fireEvent(new mxEventObject('resetGraphView'));
560		}
561		else if (node.nodeName == 'root')
562		{
563			this.resetGraph();
564
565			// Workaround for invalid XML output in Firefox 20 due to bug in mxUtils.getXml
566			var wrapper = dec.document.createElement('mxGraphModel');
567			wrapper.appendChild(node);
568
569			dec.decode(wrapper, this.graph.getModel());
570			this.updateGraphComponents();
571			this.fireEvent(new mxEventObject('resetGraphView'));
572		}
573		else
574		{
575			throw {
576			    message: mxResources.get('cannotOpenFile'),
577			    node: node,
578			    toString: function() { return this.message; }
579			};
580		}
581	}
582	else
583	{
584		this.resetGraph();
585		this.graph.model.clear();
586		this.fireEvent(new mxEventObject('resetGraphView'));
587	}
588};
589
590/**
591 * Returns the XML node that represents the current diagram.
592 */
593Editor.prototype.getGraphXml = function(ignoreSelection)
594{
595	ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
596	var node = null;
597
598	if (ignoreSelection)
599	{
600		var enc = new mxCodec(mxUtils.createXmlDocument());
601		node = enc.encode(this.graph.getModel());
602	}
603	else
604	{
605		node = this.graph.encodeCells(mxUtils.sortCells(this.graph.model.getTopmostCells(
606			this.graph.getSelectionCells())));
607	}
608
609	if (this.graph.view.translate.x != 0 || this.graph.view.translate.y != 0)
610	{
611		node.setAttribute('dx', Math.round(this.graph.view.translate.x * 100) / 100);
612		node.setAttribute('dy', Math.round(this.graph.view.translate.y * 100) / 100);
613	}
614
615	node.setAttribute('grid', (this.graph.isGridEnabled()) ? '1' : '0');
616	node.setAttribute('gridSize', this.graph.gridSize);
617	node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
618	node.setAttribute('tooltips', (this.graph.tooltipHandler.isEnabled()) ? '1' : '0');
619	node.setAttribute('connect', (this.graph.connectionHandler.isEnabled()) ? '1' : '0');
620	node.setAttribute('arrows', (this.graph.connectionArrowsEnabled) ? '1' : '0');
621	node.setAttribute('fold', (this.graph.foldingEnabled) ? '1' : '0');
622	node.setAttribute('page', (this.graph.pageVisible) ? '1' : '0');
623	node.setAttribute('pageScale', this.graph.pageScale);
624	node.setAttribute('pageWidth', this.graph.pageFormat.width);
625	node.setAttribute('pageHeight', this.graph.pageFormat.height);
626
627	if (this.graph.background != null)
628	{
629		node.setAttribute('background', this.graph.background);
630	}
631
632	return node;
633};
634
635/**
636 * Keeps the graph container in sync with the persistent graph state
637 */
638Editor.prototype.updateGraphComponents = function()
639{
640	var graph = this.graph;
641
642	if (graph.container != null)
643	{
644		graph.view.validateBackground();
645		graph.container.style.overflow = (graph.scrollbars) ? 'auto' : this.defaultGraphOverflow;
646
647		this.fireEvent(new mxEventObject('updateGraphComponents'));
648	}
649};
650
651/**
652 * Sets the modified flag.
653 */
654Editor.prototype.setModified = function(value)
655{
656	this.modified = value;
657};
658
659/**
660 * Sets the filename.
661 */
662Editor.prototype.setFilename = function(value)
663{
664	this.filename = value;
665};
666
667/**
668 * Creates and returns a new undo manager.
669 */
670Editor.prototype.createUndoManager = function()
671{
672	var graph = this.graph;
673	var undoMgr = new mxUndoManager();
674
675	this.undoListener = function(sender, evt)
676	{
677		undoMgr.undoableEditHappened(evt.getProperty('edit'));
678	};
679
680    // Installs the command history
681	var listener = mxUtils.bind(this, function(sender, evt)
682	{
683		this.undoListener.apply(this, arguments);
684	});
685
686	graph.getModel().addListener(mxEvent.UNDO, listener);
687	graph.getView().addListener(mxEvent.UNDO, listener);
688
689	// Keeps the selection in sync with the history
690	var undoHandler = function(sender, evt)
691	{
692		var cand = graph.getSelectionCellsForChanges(evt.getProperty('edit').changes, function(change)
693		{
694			// Only selects changes to the cell hierarchy
695			return !(change instanceof mxChildChange);
696		});
697
698		if (cand.length > 0)
699		{
700			var model = graph.getModel();
701			var cells = [];
702
703			for (var i = 0; i < cand.length; i++)
704			{
705				if (graph.view.getState(cand[i]) != null)
706				{
707					cells.push(cand[i]);
708				}
709			}
710
711			graph.setSelectionCells(cells);
712		}
713	};
714
715	undoMgr.addListener(mxEvent.UNDO, undoHandler);
716	undoMgr.addListener(mxEvent.REDO, undoHandler);
717
718	return undoMgr;
719};
720
721/**
722 * Adds basic stencil set (no namespace).
723 */
724Editor.prototype.initStencilRegistry = function() { };
725
726/**
727 * Creates and returns a new undo manager.
728 */
729Editor.prototype.destroy = function()
730{
731	if (this.graph != null)
732	{
733		this.graph.destroy();
734		this.graph = null;
735	}
736};
737
738/**
739 * Class for asynchronously opening a new window and loading a file at the same
740 * time. This acts as a bridge between the open dialog and the new editor.
741 */
742OpenFile = function(done)
743{
744	this.producer = null;
745	this.consumer = null;
746	this.done = done;
747	this.args = null;
748};
749
750/**
751 * Registers the editor from the new window.
752 */
753OpenFile.prototype.setConsumer = function(value)
754{
755	this.consumer = value;
756	this.execute();
757};
758
759/**
760 * Sets the data from the loaded file.
761 */
762OpenFile.prototype.setData = function()
763{
764	this.args = arguments;
765	this.execute();
766};
767
768/**
769 * Displays an error message.
770 */
771OpenFile.prototype.error = function(msg)
772{
773	this.cancel(true);
774	mxUtils.alert(msg);
775};
776
777/**
778 * Consumes the data.
779 */
780OpenFile.prototype.execute = function()
781{
782	if (this.consumer != null && this.args != null)
783	{
784		this.cancel(false);
785		this.consumer.apply(this, this.args);
786	}
787};
788
789/**
790 * Cancels the operation.
791 */
792OpenFile.prototype.cancel = function(cancel)
793{
794	if (this.done != null)
795	{
796		this.done((cancel != null) ? cancel : true);
797	}
798};
799
800/**
801 * Basic dialogs that are available in the viewer (print dialog).
802 */
803function Dialog(editorUi, elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, ignoreBgClick)
804{
805	var dx = transparent? 57 : 0;
806	var w0 = w;
807	var h0 = h;
808	var padding = transparent? 0 : 64; //No padding needed for transparent dialogs
809
810	var ds = (!Editor.inlineFullscreen && editorUi.embedViewport != null) ?
811		mxUtils.clone(editorUi.embedViewport) : mxUtils.getDocumentSize();
812
813	// Workaround for print dialog offset in viewer lightbox
814	if (editorUi.embedViewport == null && window.innerHeight != null)
815	{
816		ds.height = window.innerHeight;
817	}
818
819	var dh = ds.height;
820	var left = Math.max(1, Math.round((ds.width - w - padding) / 2));
821	var top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));
822
823	// Keeps window size inside available space
824	elt.style.maxHeight = '100%';
825
826	w = (document.body != null) ? Math.min(w, document.body.scrollWidth - padding) : w;
827	h = Math.min(h, dh - padding);
828
829	// Increments zIndex to put subdialogs and background over existing dialogs and background
830	if (editorUi.dialogs.length > 0)
831	{
832		this.zIndex += editorUi.dialogs.length * 2;
833	}
834
835	if (this.bg == null)
836	{
837		this.bg = editorUi.createDiv('background');
838		this.bg.style.position = 'absolute';
839		this.bg.style.background = Dialog.backdropColor;
840		this.bg.style.height = dh + 'px';
841		this.bg.style.right = '0px';
842		this.bg.style.zIndex = this.zIndex - 2;
843
844		mxUtils.setOpacity(this.bg, this.bgOpacity);
845	}
846
847	var origin = mxUtils.getDocumentScrollOrigin(document);
848	this.bg.style.left = origin.x + 'px';
849	this.bg.style.top = origin.y + 'px';
850	left += origin.x;
851	top += origin.y;
852
853	if (!Editor.inlineFullscreen && editorUi.embedViewport != null)
854	{
855		this.bg.style.height = mxUtils.getDocumentSize().height + 'px';
856		top += editorUi.embedViewport.y;
857		left += editorUi.embedViewport.x;
858	}
859
860	if (modal)
861	{
862		document.body.appendChild(this.bg);
863	}
864
865	var div = editorUi.createDiv(transparent? 'geTransDialog' : 'geDialog');
866	var pos = this.getPosition(left, top, w, h);
867	left = pos.x;
868	top = pos.y;
869
870	div.style.width = w + 'px';
871	div.style.height = h + 'px';
872	div.style.left = left + 'px';
873	div.style.top = top + 'px';
874	div.style.zIndex = this.zIndex;
875
876	div.appendChild(elt);
877	document.body.appendChild(div);
878
879	// Adds vertical scrollbars if needed
880	if (!noScroll && elt.clientHeight > div.clientHeight - padding)
881	{
882		elt.style.overflowY = 'auto';
883	}
884
885	//Prevent horizontal scrollbar
886	elt.style.overflowX = 'hidden';
887
888	if (closable)
889	{
890		var img = document.createElement('img');
891
892		img.setAttribute('src', Dialog.prototype.closeImage);
893		img.setAttribute('title', mxResources.get('close'));
894		img.className = 'geDialogClose';
895		img.style.top = (top + 14) + 'px';
896		img.style.left = (left + w + 38 - dx) + 'px';
897		img.style.zIndex = this.zIndex;
898
899		mxEvent.addListener(img, 'click', mxUtils.bind(this, function()
900		{
901			editorUi.hideDialog(true);
902		}));
903
904		document.body.appendChild(img);
905		this.dialogImg = img;
906
907		if (!ignoreBgClick)
908		{
909			var mouseDownSeen = false;
910
911			mxEvent.addGestureListeners(this.bg, mxUtils.bind(this, function(evt)
912			{
913				mouseDownSeen = true;
914			}), null, mxUtils.bind(this, function(evt)
915			{
916				if (mouseDownSeen)
917				{
918					editorUi.hideDialog(true);
919					mouseDownSeen = false;
920				}
921			}));
922		}
923	}
924
925	this.resizeListener = mxUtils.bind(this, function()
926	{
927		if (onResize != null)
928		{
929			var newWH = onResize();
930
931			if (newWH != null)
932			{
933				w0 = w = newWH.w;
934				h0 = h = newWH.h;
935			}
936		}
937
938		var ds = mxUtils.getDocumentSize();
939		dh = ds.height;
940		this.bg.style.height = dh + 'px';
941
942		if (!Editor.inlineFullscreen && editorUi.embedViewport != null)
943		{
944			this.bg.style.height = mxUtils.getDocumentSize().height + 'px';
945		}
946
947		left = Math.max(1, Math.round((ds.width - w - padding) / 2));
948		top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));
949		w = (document.body != null) ? Math.min(w0, document.body.scrollWidth - padding) : w0;
950		h = Math.min(h0, dh - padding);
951
952		var pos = this.getPosition(left, top, w, h);
953		left = pos.x;
954		top = pos.y;
955
956		div.style.left = left + 'px';
957		div.style.top = top + 'px';
958		div.style.width = w + 'px';
959		div.style.height = h + 'px';
960
961		// Adds vertical scrollbars if needed
962		if (!noScroll && elt.clientHeight > div.clientHeight - padding)
963		{
964			elt.style.overflowY = 'auto';
965		}
966
967		if (this.dialogImg != null)
968		{
969			this.dialogImg.style.top = (top + 14) + 'px';
970			this.dialogImg.style.left = (left + w + 38 - dx) + 'px';
971		}
972	});
973
974	mxEvent.addListener(window, 'resize', this.resizeListener);
975
976	this.onDialogClose = onClose;
977	this.container = div;
978
979	editorUi.editor.fireEvent(new mxEventObject('showDialog'));
980};
981
982/**
983 *
984 */
985Dialog.backdropColor = 'white';
986
987/**
988 *
989 */
990Dialog.prototype.zIndex = mxPopupMenu.prototype.zIndex - 2;
991
992/**
993 *
994 */
995Dialog.prototype.noColorImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/nocolor.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkEzRDlBMUUwODYxMTExRTFCMzA4RDdDMjJBMEMxRDM3IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkEzRDlBMUUxODYxMTExRTFCMzA4RDdDMjJBMEMxRDM3Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTNEOUExREU4NjExMTFFMUIzMDhEN0MyMkEwQzFEMzciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTNEOUExREY4NjExMTFFMUIzMDhEN0MyMkEwQzFEMzciLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5xh3fmAAAABlBMVEX////MzMw46qqDAAAAGElEQVR42mJggAJGKGAYIIGBth8KAAIMAEUQAIElnLuQAAAAAElFTkSuQmCC';
996
997/**
998 *
999 */
1000Dialog.prototype.closeImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/close.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJAQMAAADaX5RTAAAABlBMVEV7mr3///+wksspAAAAAnRSTlP/AOW3MEoAAAAdSURBVAgdY9jXwCDDwNDRwHCwgeExmASygSL7GgB12QiqNHZZIwAAAABJRU5ErkJggg==';
1001
1002/**
1003 *
1004 */
1005Dialog.prototype.clearImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/clear.gif' : 'data:image/gif;base64,R0lGODlhDQAKAIABAMDAwP///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUIzOEM1NzI4NjEyMTFFMUEzMkNDMUE3NjZERDE2QjIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUIzOEM1NzM4NjEyMTFFMUEzMkNDMUE3NjZERDE2QjIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QjM4QzU3MDg2MTIxMUUxQTMyQ0MxQTc2NkREMTZCMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QjM4QzU3MTg2MTIxMUUxQTMyQ0MxQTc2NkREMTZCMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAEALAAAAAANAAoAAAIXTGCJebD9jEOTqRlttXdrB32PJ2ncyRQAOw==';
1006
1007/**
1008 * Removes the dialog from the DOM.
1009 */
1010Dialog.prototype.bgOpacity = 80;
1011
1012/**
1013 * Removes the dialog from the DOM.
1014 */
1015Dialog.prototype.getPosition = function(left, top)
1016{
1017	return new mxPoint(left, top);
1018};
1019
1020/**
1021 * Removes the dialog from the DOM.
1022 */
1023Dialog.prototype.close = function(cancel, isEsc)
1024{
1025	if (this.onDialogClose != null)
1026	{
1027		if (this.onDialogClose(cancel, isEsc) == false)
1028		{
1029			return false;
1030		}
1031
1032		this.onDialogClose = null;
1033	}
1034
1035	if (this.dialogImg != null)
1036	{
1037		this.dialogImg.parentNode.removeChild(this.dialogImg);
1038		this.dialogImg = null;
1039	}
1040
1041	if (this.bg != null && this.bg.parentNode != null)
1042	{
1043		this.bg.parentNode.removeChild(this.bg);
1044	}
1045
1046	mxEvent.removeListener(window, 'resize', this.resizeListener);
1047	this.container.parentNode.removeChild(this.container);
1048};
1049
1050/**
1051 *
1052 */
1053var ErrorDialog = function(editorUi, title, message, buttonText, fn, retry, buttonText2, fn2, hide, buttonText3, fn3)
1054{
1055	hide = (hide != null) ? hide : true;
1056
1057	var div = document.createElement('div');
1058	div.style.textAlign = 'center';
1059
1060	if (title != null)
1061	{
1062		var hd = document.createElement('div');
1063		hd.style.padding = '0px';
1064		hd.style.margin = '0px';
1065		hd.style.fontSize = '18px';
1066		hd.style.paddingBottom = '16px';
1067		hd.style.marginBottom = '10px';
1068		hd.style.borderBottom = '1px solid #c0c0c0';
1069		hd.style.color = 'gray';
1070		hd.style.whiteSpace = 'nowrap';
1071		hd.style.textOverflow = 'ellipsis';
1072		hd.style.overflow = 'hidden';
1073		mxUtils.write(hd, title);
1074		hd.setAttribute('title', title);
1075		div.appendChild(hd);
1076	}
1077
1078	var p2 = document.createElement('div');
1079	p2.style.lineHeight = '1.2em';
1080	p2.style.padding = '6px';
1081	p2.innerHTML = message;
1082	div.appendChild(p2);
1083
1084	var btns = document.createElement('div');
1085	btns.style.marginTop = '12px';
1086	btns.style.textAlign = 'center';
1087
1088	if (retry != null)
1089	{
1090		var retryBtn = mxUtils.button(mxResources.get('tryAgain'), function()
1091		{
1092			editorUi.hideDialog();
1093			retry();
1094		});
1095		retryBtn.className = 'geBtn';
1096		btns.appendChild(retryBtn);
1097
1098		btns.style.textAlign = 'center';
1099	}
1100
1101	if (buttonText3 != null)
1102	{
1103		var btn3 = mxUtils.button(buttonText3, function()
1104		{
1105			if (fn3 != null)
1106			{
1107				fn3();
1108			}
1109		});
1110
1111		btn3.className = 'geBtn';
1112		btns.appendChild(btn3);
1113	}
1114
1115	var btn = mxUtils.button(buttonText, function()
1116	{
1117		if (hide)
1118		{
1119			editorUi.hideDialog();
1120		}
1121
1122		if (fn != null)
1123		{
1124			fn();
1125		}
1126	});
1127
1128	btn.className = 'geBtn';
1129	btns.appendChild(btn);
1130
1131	if (buttonText2 != null)
1132	{
1133		var mainBtn = mxUtils.button(buttonText2, function()
1134		{
1135			if (hide)
1136			{
1137				editorUi.hideDialog();
1138			}
1139
1140			if (fn2 != null)
1141			{
1142				fn2();
1143			}
1144		});
1145
1146		mainBtn.className = 'geBtn gePrimaryBtn';
1147		btns.appendChild(mainBtn);
1148	}
1149
1150	this.init = function()
1151	{
1152		btn.focus();
1153	};
1154
1155	div.appendChild(btns);
1156
1157	this.container = div;
1158};
1159
1160/**
1161 * Constructs a new print dialog.
1162 */
1163var PrintDialog = function(editorUi, title)
1164{
1165	this.create(editorUi, title);
1166};
1167
1168/**
1169 * Constructs a new print dialog.
1170 */
1171PrintDialog.prototype.create = function(editorUi)
1172{
1173	var graph = editorUi.editor.graph;
1174	var row, td;
1175
1176	var table = document.createElement('table');
1177	table.style.width = '100%';
1178	table.style.height = '100%';
1179	var tbody = document.createElement('tbody');
1180
1181	row = document.createElement('tr');
1182
1183	var onePageCheckBox = document.createElement('input');
1184	onePageCheckBox.setAttribute('type', 'checkbox');
1185	td = document.createElement('td');
1186	td.setAttribute('colspan', '2');
1187	td.style.fontSize = '10pt';
1188	td.appendChild(onePageCheckBox);
1189
1190	var span = document.createElement('span');
1191	mxUtils.write(span, ' ' + mxResources.get('fitPage'));
1192	td.appendChild(span);
1193
1194	mxEvent.addListener(span, 'click', function(evt)
1195	{
1196		onePageCheckBox.checked = !onePageCheckBox.checked;
1197		pageCountCheckBox.checked = !onePageCheckBox.checked;
1198		mxEvent.consume(evt);
1199	});
1200
1201	mxEvent.addListener(onePageCheckBox, 'change', function()
1202	{
1203		pageCountCheckBox.checked = !onePageCheckBox.checked;
1204	});
1205
1206	row.appendChild(td);
1207	tbody.appendChild(row);
1208
1209	row = row.cloneNode(false);
1210
1211	var pageCountCheckBox = document.createElement('input');
1212	pageCountCheckBox.setAttribute('type', 'checkbox');
1213	td = document.createElement('td');
1214	td.style.fontSize = '10pt';
1215	td.appendChild(pageCountCheckBox);
1216
1217	var span = document.createElement('span');
1218	mxUtils.write(span, ' ' + mxResources.get('posterPrint') + ':');
1219	td.appendChild(span);
1220
1221	mxEvent.addListener(span, 'click', function(evt)
1222	{
1223		pageCountCheckBox.checked = !pageCountCheckBox.checked;
1224		onePageCheckBox.checked = !pageCountCheckBox.checked;
1225		mxEvent.consume(evt);
1226	});
1227
1228	row.appendChild(td);
1229
1230	var pageCountInput = document.createElement('input');
1231	pageCountInput.setAttribute('value', '1');
1232	pageCountInput.setAttribute('type', 'number');
1233	pageCountInput.setAttribute('min', '1');
1234	pageCountInput.setAttribute('size', '4');
1235	pageCountInput.setAttribute('disabled', 'disabled');
1236	pageCountInput.style.width = '50px';
1237
1238	td = document.createElement('td');
1239	td.style.fontSize = '10pt';
1240	td.appendChild(pageCountInput);
1241	mxUtils.write(td, ' ' + mxResources.get('pages') + ' (max)');
1242	row.appendChild(td);
1243	tbody.appendChild(row);
1244
1245	mxEvent.addListener(pageCountCheckBox, 'change', function()
1246	{
1247		if (pageCountCheckBox.checked)
1248		{
1249			pageCountInput.removeAttribute('disabled');
1250		}
1251		else
1252		{
1253			pageCountInput.setAttribute('disabled', 'disabled');
1254		}
1255
1256		onePageCheckBox.checked = !pageCountCheckBox.checked;
1257	});
1258
1259	row = row.cloneNode(false);
1260
1261	td = document.createElement('td');
1262	mxUtils.write(td, mxResources.get('pageScale') + ':');
1263	row.appendChild(td);
1264
1265	td = document.createElement('td');
1266	var pageScaleInput = document.createElement('input');
1267	pageScaleInput.setAttribute('value', '100 %');
1268	pageScaleInput.setAttribute('size', '5');
1269	pageScaleInput.style.width = '50px';
1270
1271	td.appendChild(pageScaleInput);
1272	row.appendChild(td);
1273	tbody.appendChild(row);
1274
1275	row = document.createElement('tr');
1276	td = document.createElement('td');
1277	td.colSpan = 2;
1278	td.style.paddingTop = '20px';
1279	td.setAttribute('align', 'right');
1280
1281	// Overall scale for print-out to account for print borders in dialogs etc
1282	function preview(print)
1283	{
1284		var autoOrigin = onePageCheckBox.checked || pageCountCheckBox.checked;
1285		var printScale = parseInt(pageScaleInput.value) / 100;
1286
1287		if (isNaN(printScale))
1288		{
1289			printScale = 1;
1290			pageScaleInput.value = '100%';
1291		}
1292
1293		// Workaround to match available paper size in actual print output
1294		printScale *= 0.75;
1295
1296		var pf = graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT;
1297		var scale = 1 / graph.pageScale;
1298
1299		if (autoOrigin)
1300		{
1301    		var pageCount = (onePageCheckBox.checked) ? 1 : parseInt(pageCountInput.value);
1302
1303			if (!isNaN(pageCount))
1304			{
1305				scale = mxUtils.getScaleForPageCount(pageCount, graph, pf);
1306			}
1307		}
1308
1309		// Negative coordinates are cropped or shifted if page visible
1310		var gb = graph.getGraphBounds();
1311		var border = 0;
1312		var x0 = 0;
1313		var y0 = 0;
1314
1315		// Applies print scale
1316		pf = mxRectangle.fromRectangle(pf);
1317		pf.width = Math.ceil(pf.width * printScale);
1318		pf.height = Math.ceil(pf.height * printScale);
1319		scale *= printScale;
1320
1321		// Starts at first visible page
1322		if (!autoOrigin && graph.pageVisible)
1323		{
1324			var layout = graph.getPageLayout();
1325			x0 -= layout.x * pf.width;
1326			y0 -= layout.y * pf.height;
1327		}
1328		else
1329		{
1330			autoOrigin = true;
1331		}
1332
1333		var preview = PrintDialog.createPrintPreview(graph, scale, pf, border, x0, y0, autoOrigin);
1334		preview.open();
1335
1336		if (print)
1337		{
1338			PrintDialog.printPreview(preview);
1339		}
1340	};
1341
1342	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
1343	{
1344		editorUi.hideDialog();
1345	});
1346	cancelBtn.className = 'geBtn';
1347
1348	if (editorUi.editor.cancelFirst)
1349	{
1350		td.appendChild(cancelBtn);
1351	}
1352
1353	if (PrintDialog.previewEnabled)
1354	{
1355		var previewBtn = mxUtils.button(mxResources.get('preview'), function()
1356		{
1357			editorUi.hideDialog();
1358			preview(false);
1359		});
1360		previewBtn.className = 'geBtn';
1361		td.appendChild(previewBtn);
1362	}
1363
1364	var printBtn = mxUtils.button(mxResources.get((!PrintDialog.previewEnabled) ? 'ok' : 'print'), function()
1365	{
1366		editorUi.hideDialog();
1367		preview(true);
1368	});
1369	printBtn.className = 'geBtn gePrimaryBtn';
1370	td.appendChild(printBtn);
1371
1372	if (!editorUi.editor.cancelFirst)
1373	{
1374		td.appendChild(cancelBtn);
1375	}
1376
1377	row.appendChild(td);
1378	tbody.appendChild(row);
1379
1380	table.appendChild(tbody);
1381	this.container = table;
1382};
1383
1384/**
1385 * Constructs a new print dialog.
1386 */
1387PrintDialog.printPreview = function(preview)
1388{
1389	try
1390	{
1391		if (preview.wnd != null)
1392		{
1393			var printFn = function()
1394			{
1395				preview.wnd.focus();
1396				preview.wnd.print();
1397				preview.wnd.close();
1398			};
1399
1400			// Workaround for Google Chrome which needs a bit of a
1401			// delay in order to render the SVG contents
1402			// Needs testing in production
1403			if (mxClient.IS_GC)
1404			{
1405				window.setTimeout(printFn, 500);
1406			}
1407			else
1408			{
1409				printFn();
1410			}
1411		}
1412	}
1413	catch (e)
1414	{
1415		// ignores possible Access Denied
1416	}
1417};
1418
1419/**
1420 * Constructs a new print dialog.
1421 */
1422PrintDialog.createPrintPreview = function(graph, scale, pf, border, x0, y0, autoOrigin)
1423{
1424	var preview = new mxPrintPreview(graph, scale, pf, border, x0, y0);
1425	preview.title = mxResources.get('preview');
1426	preview.printBackgroundImage = true;
1427	preview.autoOrigin = autoOrigin;
1428	var bg = graph.background;
1429
1430	if (bg == null || bg == '' || bg == mxConstants.NONE)
1431	{
1432		bg = '#ffffff';
1433	}
1434
1435	preview.backgroundColor = bg;
1436
1437	var writeHead = preview.writeHead;
1438
1439	// Adds a border in the preview
1440	preview.writeHead = function(doc)
1441	{
1442		writeHead.apply(this, arguments);
1443
1444		doc.writeln('<style type="text/css">');
1445		doc.writeln('@media screen {');
1446		doc.writeln('  body > div { padding:30px;box-sizing:content-box; }');
1447		doc.writeln('}');
1448		doc.writeln('</style>');
1449	};
1450
1451	return preview;
1452};
1453
1454/**
1455 * Specifies if the preview button should be enabled. Default is true.
1456 */
1457PrintDialog.previewEnabled = true;
1458
1459/**
1460 * Constructs a new page setup dialog.
1461 */
1462var PageSetupDialog = function(editorUi)
1463{
1464	var graph = editorUi.editor.graph;
1465	var row, td;
1466
1467	var table = document.createElement('table');
1468	table.style.width = '100%';
1469	table.style.height = '100%';
1470	var tbody = document.createElement('tbody');
1471
1472	row = document.createElement('tr');
1473
1474	td = document.createElement('td');
1475	td.style.verticalAlign = 'top';
1476	td.style.fontSize = '10pt';
1477	mxUtils.write(td, mxResources.get('paperSize') + ':');
1478
1479	row.appendChild(td);
1480
1481	td = document.createElement('td');
1482	td.style.verticalAlign = 'top';
1483	td.style.fontSize = '10pt';
1484
1485	var accessor = PageSetupDialog.addPageFormatPanel(td, 'pagesetupdialog', graph.pageFormat);
1486
1487	row.appendChild(td);
1488	tbody.appendChild(row);
1489
1490	row = document.createElement('tr');
1491
1492	td = document.createElement('td');
1493	mxUtils.write(td, mxResources.get('background') + ':');
1494
1495	row.appendChild(td);
1496
1497	td = document.createElement('td');
1498	td.style.whiteSpace = 'nowrap';
1499
1500	var backgroundInput = document.createElement('input');
1501	backgroundInput.setAttribute('type', 'text');
1502
1503	var backgroundButton = document.createElement('button');
1504	backgroundButton.style.width = '22px';
1505	backgroundButton.style.height = '22px';
1506	backgroundButton.style.cursor = 'pointer';
1507	backgroundButton.style.marginRight = '20px';
1508	backgroundButton.style.backgroundPosition = 'center center';
1509	backgroundButton.style.backgroundRepeat = 'no-repeat';
1510
1511	if (mxClient.IS_FF)
1512	{
1513		backgroundButton.style.position = 'relative';
1514		backgroundButton.style.top = '-6px';
1515	}
1516
1517	var newBackgroundColor = graph.background;
1518
1519	function updateBackgroundColor()
1520	{
1521		if (newBackgroundColor == null || newBackgroundColor == mxConstants.NONE)
1522		{
1523			backgroundButton.style.backgroundColor = '';
1524			backgroundButton.style.backgroundImage = 'url(\'' + Dialog.prototype.noColorImage + '\')';
1525		}
1526		else
1527		{
1528			backgroundButton.style.backgroundColor = newBackgroundColor;
1529			backgroundButton.style.backgroundImage = '';
1530		}
1531	};
1532
1533	updateBackgroundColor();
1534
1535	mxEvent.addListener(backgroundButton, 'click', function(evt)
1536	{
1537		editorUi.pickColor(newBackgroundColor || 'none', function(color)
1538		{
1539			newBackgroundColor = color;
1540			updateBackgroundColor();
1541		});
1542		mxEvent.consume(evt);
1543	});
1544
1545	td.appendChild(backgroundButton);
1546
1547	mxUtils.write(td, mxResources.get('gridSize') + ':');
1548
1549	var gridSizeInput = document.createElement('input');
1550	gridSizeInput.setAttribute('type', 'number');
1551	gridSizeInput.setAttribute('min', '0');
1552	gridSizeInput.style.width = '40px';
1553	gridSizeInput.style.marginLeft = '6px';
1554
1555	gridSizeInput.value = graph.getGridSize();
1556	td.appendChild(gridSizeInput);
1557
1558	mxEvent.addListener(gridSizeInput, 'change', function()
1559	{
1560		var value = parseInt(gridSizeInput.value);
1561		gridSizeInput.value = Math.max(1, (isNaN(value)) ? graph.getGridSize() : value);
1562	});
1563
1564	row.appendChild(td);
1565	tbody.appendChild(row);
1566
1567	row = document.createElement('tr');
1568	td = document.createElement('td');
1569
1570	mxUtils.write(td, mxResources.get('image') + ':');
1571
1572	row.appendChild(td);
1573	td = document.createElement('td');
1574
1575	var changeImageLink = document.createElement('button');
1576	changeImageLink.className = 'geBtn';
1577	changeImageLink.style.margin = '0px';
1578	mxUtils.write(changeImageLink, mxResources.get('change') + '...');
1579
1580	var imgPreview = document.createElement('img');
1581	imgPreview.setAttribute('valign', 'middle');
1582	imgPreview.style.verticalAlign = 'middle';
1583	imgPreview.style.border = '1px solid lightGray';
1584	imgPreview.style.borderRadius = '4px';
1585	imgPreview.style.marginRight = '14px';
1586	imgPreview.style.maxWidth = '100px';
1587	imgPreview.style.cursor = 'pointer';
1588	imgPreview.style.height = '60px';
1589	imgPreview.style.padding = '4px';
1590
1591	var newBackgroundImage = graph.backgroundImage;
1592
1593	function updateBackgroundImage()
1594	{
1595		var img = newBackgroundImage;
1596
1597		if (img != null && Graph.isPageLink(img.src))
1598		{
1599			img = editorUi.createImageForPageLink(img.src, null);
1600		}
1601
1602		if (img != null && img.src != null)
1603		{
1604			imgPreview.setAttribute('src', img.src);
1605			imgPreview.style.display = '';
1606		}
1607		else
1608		{
1609			imgPreview.removeAttribute('src');
1610			imgPreview.style.display = 'none';
1611		}
1612	};
1613
1614	var changeImage = function(evt)
1615	{
1616		editorUi.showBackgroundImageDialog(function(image, failed)
1617		{
1618			if (!failed)
1619			{
1620				newBackgroundImage = image;
1621				updateBackgroundImage();
1622			}
1623		}, newBackgroundImage);
1624
1625		mxEvent.consume(evt);
1626	};
1627
1628	mxEvent.addListener(changeImageLink, 'click', changeImage);
1629	mxEvent.addListener(imgPreview, 'click', changeImage);
1630
1631	updateBackgroundImage();
1632	td.appendChild(imgPreview);
1633	td.appendChild(changeImageLink);
1634
1635	row.appendChild(td);
1636	tbody.appendChild(row);
1637
1638	row = document.createElement('tr');
1639	td = document.createElement('td');
1640	td.colSpan = 2;
1641	td.style.paddingTop = '16px';
1642	td.setAttribute('align', 'right');
1643
1644	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
1645	{
1646		editorUi.hideDialog();
1647	});
1648	cancelBtn.className = 'geBtn';
1649
1650	if (editorUi.editor.cancelFirst)
1651	{
1652		td.appendChild(cancelBtn);
1653	}
1654
1655	var applyBtn = mxUtils.button(mxResources.get('apply'), function()
1656	{
1657		editorUi.hideDialog();
1658		var gridSize = parseInt(gridSizeInput.value);
1659
1660		if (!isNaN(gridSize) && graph.gridSize !== gridSize)
1661		{
1662			graph.setGridSize(gridSize);
1663		}
1664
1665		var change = new ChangePageSetup(editorUi, newBackgroundColor,
1666			newBackgroundImage, accessor.get());
1667		change.ignoreColor = graph.background == newBackgroundColor;
1668
1669		var oldSrc = (graph.backgroundImage != null) ? graph.backgroundImage.src : null;
1670		var newSrc = (newBackgroundImage != null) ? newBackgroundImage.src : null;
1671
1672		change.ignoreImage = oldSrc === newSrc;
1673
1674		if (graph.pageFormat.width != change.previousFormat.width ||
1675			graph.pageFormat.height != change.previousFormat.height ||
1676			!change.ignoreColor || !change.ignoreImage)
1677		{
1678			graph.model.execute(change);
1679		}
1680	});
1681	applyBtn.className = 'geBtn gePrimaryBtn';
1682	td.appendChild(applyBtn);
1683
1684	if (!editorUi.editor.cancelFirst)
1685	{
1686		td.appendChild(cancelBtn);
1687	}
1688
1689	row.appendChild(td);
1690	tbody.appendChild(row);
1691
1692	table.appendChild(tbody);
1693	this.container = table;
1694};
1695
1696/**
1697 *
1698 */
1699PageSetupDialog.addPageFormatPanel = function(div, namePostfix, pageFormat, pageFormatListener)
1700{
1701	var formatName = 'format-' + namePostfix;
1702
1703	var portraitCheckBox = document.createElement('input');
1704	portraitCheckBox.setAttribute('name', formatName);
1705	portraitCheckBox.setAttribute('type', 'radio');
1706	portraitCheckBox.setAttribute('value', 'portrait');
1707
1708	var landscapeCheckBox = document.createElement('input');
1709	landscapeCheckBox.setAttribute('name', formatName);
1710	landscapeCheckBox.setAttribute('type', 'radio');
1711	landscapeCheckBox.setAttribute('value', 'landscape');
1712
1713	var paperSizeSelect = document.createElement('select');
1714	paperSizeSelect.style.marginBottom = '8px';
1715	paperSizeSelect.style.borderRadius = '4px';
1716	paperSizeSelect.style.border = '1px solid rgb(160, 160, 160)';
1717	paperSizeSelect.style.width = '206px';
1718
1719	var formatDiv = document.createElement('div');
1720	formatDiv.style.marginLeft = '4px';
1721	formatDiv.style.width = '210px';
1722	formatDiv.style.height = '24px';
1723
1724	portraitCheckBox.style.marginRight = '6px';
1725	formatDiv.appendChild(portraitCheckBox);
1726
1727	var portraitSpan = document.createElement('span');
1728	portraitSpan.style.maxWidth = '100px';
1729	mxUtils.write(portraitSpan, mxResources.get('portrait'));
1730	formatDiv.appendChild(portraitSpan);
1731
1732	landscapeCheckBox.style.marginLeft = '10px';
1733	landscapeCheckBox.style.marginRight = '6px';
1734	formatDiv.appendChild(landscapeCheckBox);
1735
1736	var landscapeSpan = document.createElement('span');
1737	landscapeSpan.style.width = '100px';
1738	mxUtils.write(landscapeSpan, mxResources.get('landscape'));
1739	formatDiv.appendChild(landscapeSpan)
1740
1741	var customDiv = document.createElement('div');
1742	customDiv.style.marginLeft = '4px';
1743	customDiv.style.width = '210px';
1744	customDiv.style.height = '24px';
1745
1746	var widthInput = document.createElement('input');
1747	widthInput.setAttribute('size', '7');
1748	widthInput.style.textAlign = 'right';
1749	customDiv.appendChild(widthInput);
1750	mxUtils.write(customDiv, ' in x ');
1751
1752	var heightInput = document.createElement('input');
1753	heightInput.setAttribute('size', '7');
1754	heightInput.style.textAlign = 'right';
1755	customDiv.appendChild(heightInput);
1756	mxUtils.write(customDiv, ' in');
1757
1758	formatDiv.style.display = 'none';
1759	customDiv.style.display = 'none';
1760
1761	var pf = new Object();
1762	var formats = PageSetupDialog.getFormats();
1763
1764	for (var i = 0; i < formats.length; i++)
1765	{
1766		var f = formats[i];
1767		pf[f.key] = f;
1768
1769		var paperSizeOption = document.createElement('option');
1770		paperSizeOption.setAttribute('value', f.key);
1771		mxUtils.write(paperSizeOption, f.title);
1772		paperSizeSelect.appendChild(paperSizeOption);
1773	}
1774
1775	var customSize = false;
1776
1777	function listener(sender, evt, force)
1778	{
1779		if (force || (widthInput != document.activeElement && heightInput != document.activeElement))
1780		{
1781			var detected = false;
1782
1783			for (var i = 0; i < formats.length; i++)
1784			{
1785				var f = formats[i];
1786
1787				// Special case where custom was chosen
1788				if (customSize)
1789				{
1790					if (f.key == 'custom')
1791					{
1792						paperSizeSelect.value = f.key;
1793						customSize = false;
1794					}
1795				}
1796				else if (f.format != null)
1797				{
1798					// Fixes wrong values for previous A4 and A5 page sizes
1799					if (f.key == 'a4')
1800					{
1801						if (pageFormat.width == 826)
1802						{
1803							pageFormat = mxRectangle.fromRectangle(pageFormat);
1804							pageFormat.width = 827;
1805						}
1806						else if (pageFormat.height == 826)
1807						{
1808							pageFormat = mxRectangle.fromRectangle(pageFormat);
1809							pageFormat.height = 827;
1810						}
1811					}
1812					else if (f.key == 'a5')
1813					{
1814						if (pageFormat.width == 584)
1815						{
1816							pageFormat = mxRectangle.fromRectangle(pageFormat);
1817							pageFormat.width = 583;
1818						}
1819						else if (pageFormat.height == 584)
1820						{
1821							pageFormat = mxRectangle.fromRectangle(pageFormat);
1822							pageFormat.height = 583;
1823						}
1824					}
1825
1826					if (pageFormat.width == f.format.width && pageFormat.height == f.format.height)
1827					{
1828						paperSizeSelect.value = f.key;
1829						portraitCheckBox.setAttribute('checked', 'checked');
1830						portraitCheckBox.defaultChecked = true;
1831						portraitCheckBox.checked = true;
1832						landscapeCheckBox.removeAttribute('checked');
1833						landscapeCheckBox.defaultChecked = false;
1834						landscapeCheckBox.checked = false;
1835						detected = true;
1836					}
1837					else if (pageFormat.width == f.format.height && pageFormat.height == f.format.width)
1838					{
1839						paperSizeSelect.value = f.key;
1840						portraitCheckBox.removeAttribute('checked');
1841						portraitCheckBox.defaultChecked = false;
1842						portraitCheckBox.checked = false;
1843						landscapeCheckBox.setAttribute('checked', 'checked');
1844						landscapeCheckBox.defaultChecked = true;
1845						landscapeCheckBox.checked = true;
1846						detected = true;
1847					}
1848				}
1849			}
1850
1851			// Selects custom format which is last in list
1852			if (!detected)
1853			{
1854				widthInput.value = pageFormat.width / 100;
1855				heightInput.value = pageFormat.height / 100;
1856				portraitCheckBox.setAttribute('checked', 'checked');
1857				paperSizeSelect.value = 'custom';
1858				formatDiv.style.display = 'none';
1859				customDiv.style.display = '';
1860			}
1861			else
1862			{
1863				formatDiv.style.display = '';
1864				customDiv.style.display = 'none';
1865			}
1866		}
1867	};
1868
1869	listener();
1870
1871	div.appendChild(paperSizeSelect);
1872	mxUtils.br(div);
1873
1874	div.appendChild(formatDiv);
1875	div.appendChild(customDiv);
1876
1877	var currentPageFormat = pageFormat;
1878
1879	var update = function(evt, selectChanged)
1880	{
1881		var f = pf[paperSizeSelect.value];
1882
1883		if (f.format != null)
1884		{
1885			widthInput.value = f.format.width / 100;
1886			heightInput.value = f.format.height / 100;
1887			customDiv.style.display = 'none';
1888			formatDiv.style.display = '';
1889		}
1890		else
1891		{
1892			formatDiv.style.display = 'none';
1893			customDiv.style.display = '';
1894		}
1895
1896		var wi = parseFloat(widthInput.value);
1897
1898		if (isNaN(wi) || wi <= 0)
1899		{
1900			widthInput.value = pageFormat.width / 100;
1901		}
1902
1903		var hi = parseFloat(heightInput.value);
1904
1905		if (isNaN(hi) || hi <= 0)
1906		{
1907			heightInput.value = pageFormat.height / 100;
1908		}
1909
1910		var newPageFormat = new mxRectangle(0, 0,
1911			Math.floor(parseFloat(widthInput.value) * 100),
1912			Math.floor(parseFloat(heightInput.value) * 100));
1913
1914		if (paperSizeSelect.value != 'custom' && landscapeCheckBox.checked)
1915		{
1916			newPageFormat = new mxRectangle(0, 0, newPageFormat.height, newPageFormat.width);
1917		}
1918
1919		// Initial select of custom should not update page format to avoid update of combo
1920		if ((!selectChanged || !customSize) && (newPageFormat.width != currentPageFormat.width ||
1921			newPageFormat.height != currentPageFormat.height))
1922		{
1923			currentPageFormat = newPageFormat;
1924
1925			// Updates page format and reloads format panel
1926			if (pageFormatListener != null)
1927			{
1928				pageFormatListener(currentPageFormat);
1929			}
1930		}
1931	};
1932
1933	mxEvent.addListener(portraitSpan, 'click', function(evt)
1934	{
1935		portraitCheckBox.checked = true;
1936		update(evt);
1937		mxEvent.consume(evt);
1938	});
1939
1940	mxEvent.addListener(landscapeSpan, 'click', function(evt)
1941	{
1942		landscapeCheckBox.checked = true;
1943		update(evt);
1944		mxEvent.consume(evt);
1945	});
1946
1947	mxEvent.addListener(widthInput, 'blur', update);
1948	mxEvent.addListener(widthInput, 'click', update);
1949	mxEvent.addListener(heightInput, 'blur', update);
1950	mxEvent.addListener(heightInput, 'click', update);
1951	mxEvent.addListener(landscapeCheckBox, 'change', update);
1952	mxEvent.addListener(portraitCheckBox, 'change', update);
1953	mxEvent.addListener(paperSizeSelect, 'change', function(evt)
1954	{
1955		// Handles special case where custom was chosen
1956		customSize = paperSizeSelect.value == 'custom';
1957		update(evt, true);
1958	});
1959
1960	update();
1961
1962	return {set: function(value)
1963	{
1964		pageFormat = value;
1965		listener(null, null, true);
1966	},get: function()
1967	{
1968		return currentPageFormat;
1969	}, widthInput: widthInput,
1970		heightInput: heightInput};
1971};
1972
1973/**
1974 *
1975 */
1976PageSetupDialog.getFormats = function()
1977{
1978	return [{key: 'letter', title: 'US-Letter (8,5" x 11")', format: mxConstants.PAGE_FORMAT_LETTER_PORTRAIT},
1979	        {key: 'legal', title: 'US-Legal (8,5" x 14")', format: new mxRectangle(0, 0, 850, 1400)},
1980	        {key: 'tabloid', title: 'US-Tabloid (11" x 17")', format: new mxRectangle(0, 0, 1100, 1700)},
1981	        {key: 'executive', title: 'US-Executive (7" x 10")', format: new mxRectangle(0, 0, 700, 1000)},
1982	        {key: 'a0', title: 'A0 (841 mm x 1189 mm)', format: new mxRectangle(0, 0, 3300, 4681)},
1983	        {key: 'a1', title: 'A1 (594 mm x 841 mm)', format: new mxRectangle(0, 0, 2339, 3300)},
1984	        {key: 'a2', title: 'A2 (420 mm x 594 mm)', format: new mxRectangle(0, 0, 1654, 2336)},
1985	        {key: 'a3', title: 'A3 (297 mm x 420 mm)', format: new mxRectangle(0, 0, 1169, 1654)},
1986	        {key: 'a4', title: 'A4 (210 mm x 297 mm)', format: mxConstants.PAGE_FORMAT_A4_PORTRAIT},
1987	        {key: 'a5', title: 'A5 (148 mm x 210 mm)', format: new mxRectangle(0, 0, 583, 827)},
1988	        {key: 'a6', title: 'A6 (105 mm x 148 mm)', format: new mxRectangle(0, 0, 413, 583)},
1989	        {key: 'a7', title: 'A7 (74 mm x 105 mm)', format: new mxRectangle(0, 0, 291, 413)},
1990	        {key: 'b4', title: 'B4 (250 mm x 353 mm)', format: new mxRectangle(0, 0, 980, 1390)},
1991	        {key: 'b5', title: 'B5 (176 mm x 250 mm)', format: new mxRectangle(0, 0, 690, 980)},
1992	        {key: '16-9', title: '16:9 (1600 x 900)', format: new mxRectangle(0, 0, 900, 1600)},
1993	        {key: '16-10', title: '16:10 (1920 x 1200)', format: new mxRectangle(0, 0, 1200, 1920)},
1994	        {key: '4-3', title: '4:3 (1600 x 1200)', format: new mxRectangle(0, 0, 1200, 1600)},
1995	        {key: 'custom', title: mxResources.get('custom'), format: null}];
1996};
1997
1998/**
1999 * Constructs a new filename dialog.
2000 */
2001var FilenameDialog = function(editorUi, filename, buttonText, fn, label, validateFn, content, helpLink, closeOnBtn, cancelFn, hints, w)
2002{
2003	closeOnBtn = (closeOnBtn != null) ? closeOnBtn : true;
2004	var row, td;
2005
2006	var table = document.createElement('table');
2007	var tbody = document.createElement('tbody');
2008	table.style.position = 'absolute';
2009	table.style.top = '30px';
2010	table.style.left = '20px';
2011
2012	row = document.createElement('tr');
2013
2014	td = document.createElement('td');
2015	td.style.textOverflow = 'ellipsis';
2016	td.style.textAlign = 'right';
2017	td.style.maxWidth = '100px';
2018	td.style.fontSize = '10pt';
2019	td.style.width = '84px';
2020	mxUtils.write(td, (label || mxResources.get('filename')) + ':');
2021
2022	row.appendChild(td);
2023
2024	var nameInput = document.createElement('input');
2025	nameInput.setAttribute('value', filename || '');
2026	nameInput.style.marginLeft = '4px';
2027	nameInput.style.width = (w != null) ? w + 'px' : '180px';
2028
2029	var genericBtn = mxUtils.button(buttonText, function()
2030	{
2031		if (validateFn == null || validateFn(nameInput.value))
2032		{
2033			if (closeOnBtn)
2034			{
2035				editorUi.hideDialog();
2036			}
2037
2038			fn(nameInput.value);
2039		}
2040	});
2041	genericBtn.className = 'geBtn gePrimaryBtn';
2042
2043	this.init = function()
2044	{
2045		if (label == null && content != null)
2046		{
2047			return;
2048		}
2049
2050		nameInput.focus();
2051
2052		if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
2053		{
2054			nameInput.select();
2055		}
2056		else
2057		{
2058			document.execCommand('selectAll', false, null);
2059		}
2060
2061		// Installs drag and drop handler for links
2062		if (Graph.fileSupport)
2063		{
2064			// Setup the dnd listeners
2065			var dlg = table.parentNode;
2066
2067			if (dlg != null)
2068			{
2069				var graph = editorUi.editor.graph;
2070				var dropElt = null;
2071
2072				mxEvent.addListener(dlg, 'dragleave', function(evt)
2073				{
2074					if (dropElt != null)
2075				    {
2076						dropElt.style.backgroundColor = '';
2077				    	dropElt = null;
2078				    }
2079
2080					evt.stopPropagation();
2081					evt.preventDefault();
2082				});
2083
2084				mxEvent.addListener(dlg, 'dragover', mxUtils.bind(this, function(evt)
2085				{
2086					// IE 10 does not implement pointer-events so it can't have a drop highlight
2087					if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
2088					{
2089						dropElt = nameInput;
2090						dropElt.style.backgroundColor = '#ebf2f9';
2091					}
2092
2093					evt.stopPropagation();
2094					evt.preventDefault();
2095				}));
2096
2097				mxEvent.addListener(dlg, 'drop', mxUtils.bind(this, function(evt)
2098				{
2099				    if (dropElt != null)
2100				    {
2101						dropElt.style.backgroundColor = '';
2102				    	dropElt = null;
2103				    }
2104
2105				    if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0)
2106				    {
2107				    	nameInput.value = decodeURIComponent(evt.dataTransfer.getData('text/uri-list'));
2108				    	genericBtn.click();
2109				    }
2110
2111				    evt.stopPropagation();
2112				    evt.preventDefault();
2113				}));
2114			}
2115		}
2116	};
2117
2118	td = document.createElement('td');
2119	td.style.whiteSpace = 'nowrap';
2120	td.appendChild(nameInput);
2121	row.appendChild(td);
2122
2123	if (label != null || content == null)
2124	{
2125		tbody.appendChild(row);
2126
2127		if (hints != null)
2128		{
2129			td.appendChild(FilenameDialog.createTypeHint(editorUi, nameInput, hints));
2130
2131			if (editorUi.editor.diagramFileTypes != null)
2132			{
2133				row = document.createElement('tr');
2134
2135				td = document.createElement('td');
2136				td.style.textOverflow = 'ellipsis';
2137				td.style.textAlign = 'right';
2138				td.style.maxWidth = '100px';
2139				td.style.fontSize = '10pt';
2140				td.style.width = '84px';
2141				mxUtils.write(td, mxResources.get('type') + ':');
2142				row.appendChild(td);
2143
2144				td = document.createElement('td');
2145				td.style.whiteSpace = 'nowrap';
2146				row.appendChild(td);
2147
2148				var typeSelect = FilenameDialog.createFileTypes(editorUi,
2149					nameInput, editorUi.editor.diagramFileTypes);
2150				typeSelect.style.marginLeft = '4px';
2151				typeSelect.style.width = '198px';
2152
2153				td.appendChild(typeSelect);
2154				nameInput.style.width = (w != null) ? (w - 40) + 'px' : '190px';
2155
2156				row.appendChild(td);
2157				tbody.appendChild(row);
2158			}
2159		}
2160	}
2161
2162	if (content != null)
2163	{
2164		row = document.createElement('tr');
2165		td = document.createElement('td');
2166		td.colSpan = 2;
2167		td.appendChild(content);
2168		row.appendChild(td);
2169		tbody.appendChild(row);
2170	}
2171
2172	row = document.createElement('tr');
2173	td = document.createElement('td');
2174	td.colSpan = 2;
2175	td.style.paddingTop = (hints != null) ? '12px' : '20px';
2176	td.style.whiteSpace = 'nowrap';
2177	td.setAttribute('align', 'right');
2178
2179	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
2180	{
2181		editorUi.hideDialog();
2182
2183		if (cancelFn != null)
2184		{
2185			cancelFn();
2186		}
2187	});
2188	cancelBtn.className = 'geBtn';
2189
2190	if (editorUi.editor.cancelFirst)
2191	{
2192		td.appendChild(cancelBtn);
2193	}
2194
2195	if (helpLink != null)
2196	{
2197		var helpBtn = mxUtils.button(mxResources.get('help'), function()
2198		{
2199			editorUi.editor.graph.openLink(helpLink);
2200		});
2201
2202		helpBtn.className = 'geBtn';
2203		td.appendChild(helpBtn);
2204	}
2205
2206	mxEvent.addListener(nameInput, 'keypress', function(e)
2207	{
2208		if (e.keyCode == 13)
2209		{
2210			genericBtn.click();
2211		}
2212	});
2213
2214	td.appendChild(genericBtn);
2215
2216	if (!editorUi.editor.cancelFirst)
2217	{
2218		td.appendChild(cancelBtn);
2219	}
2220
2221	row.appendChild(td);
2222	tbody.appendChild(row);
2223	table.appendChild(tbody);
2224
2225	this.container = table;
2226};
2227
2228/**
2229 *
2230 */
2231FilenameDialog.filenameHelpLink = null;
2232
2233/**
2234 *
2235 */
2236FilenameDialog.createTypeHint = function(ui, nameInput, hints)
2237{
2238	var hint = document.createElement('img');
2239	hint.style.backgroundPosition = 'center bottom';
2240	hint.style.backgroundRepeat = 'no-repeat';
2241	hint.style.margin = '2px 0 0 4px';
2242	hint.style.verticalAlign = 'top';
2243	hint.style.cursor = 'pointer';
2244	hint.style.height = '16px';
2245	hint.style.width = '16px';
2246	mxUtils.setOpacity(hint, 70);
2247
2248	var nameChanged = function()
2249	{
2250		hint.setAttribute('src', Editor.helpImage);
2251		hint.setAttribute('title', mxResources.get('help'));
2252
2253		for (var i = 0; i < hints.length; i++)
2254		{
2255			if (hints[i].ext.length > 0 && nameInput.value.toLowerCase().substring(
2256				nameInput.value.length - hints[i].ext.length - 1) == '.' + hints[i].ext)
2257			{
2258				hint.setAttribute('title', mxResources.get(hints[i].title));
2259				break;
2260			}
2261		}
2262	};
2263
2264	mxEvent.addListener(nameInput, 'keyup', nameChanged);
2265	mxEvent.addListener(nameInput, 'change', nameChanged);
2266	mxEvent.addListener(hint, 'click', function(evt)
2267	{
2268		var title = hint.getAttribute('title');
2269
2270		if (hint.getAttribute('src') == Editor.helpImage)
2271		{
2272			ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
2273		}
2274		else if (title != '')
2275		{
2276			ui.showError(null, title, mxResources.get('help'), function()
2277			{
2278				ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
2279			}, null, mxResources.get('ok'), null, null, null, 340, 90);
2280		}
2281
2282		mxEvent.consume(evt);
2283	});
2284
2285	nameChanged();
2286
2287	return hint;
2288};
2289
2290/**
2291 *
2292 */
2293FilenameDialog.createFileTypes = function(editorUi, nameInput, types)
2294{
2295	var typeSelect = document.createElement('select');
2296
2297	for (var i = 0; i < types.length; i++)
2298	{
2299		var typeOption = document.createElement('option');
2300		typeOption.setAttribute('value', i);
2301		mxUtils.write(typeOption, mxResources.get(types[i].description) +
2302			' (.' + types[i].extension + ')');
2303		typeSelect.appendChild(typeOption);
2304	}
2305
2306	mxEvent.addListener(typeSelect, 'change', function(evt)
2307	{
2308		var ext = types[typeSelect.value].extension;
2309		var idx2 = nameInput.value.lastIndexOf('.drawio.');
2310		var idx = (idx2 > 0) ? idx2 : nameInput.value.lastIndexOf('.');
2311
2312		if (ext != 'drawio')
2313		{
2314			ext = 'drawio.' + ext;
2315		}
2316
2317		if (idx > 0)
2318		{
2319			nameInput.value = nameInput.value.substring(0, idx + 1) + ext;
2320		}
2321		else
2322		{
2323			nameInput.value = nameInput.value + '.' + ext;
2324		}
2325
2326		if ('createEvent' in document)
2327		{
2328		    var changeEvent = document.createEvent('HTMLEvents');
2329		    changeEvent.initEvent('change', false, true);
2330		    nameInput.dispatchEvent(changeEvent);
2331		}
2332		else
2333		{
2334		    nameInput.fireEvent('onchange');
2335		}
2336	});
2337
2338	var nameInputChanged = function(evt)
2339	{
2340		var name = nameInput.value.toLowerCase();
2341		var active = 0;
2342
2343		// Finds current extension
2344		for (var i = 0; i < types.length; i++)
2345		{
2346			var ext = types[i].extension;
2347			var subExt = null;
2348
2349			if (ext != 'drawio')
2350			{
2351				subExt = ext;
2352				ext = '.drawio.' + ext;
2353			}
2354
2355			if (name.substring(name.length - ext.length - 1) == '.' + ext ||
2356				(subExt != null && name.substring(name.length - subExt.length - 1) == '.' + subExt))
2357			{
2358				active = i;
2359				break;
2360			}
2361		}
2362
2363		typeSelect.value = active;
2364	};
2365
2366	mxEvent.addListener(nameInput, 'change', nameInputChanged);
2367	mxEvent.addListener(nameInput, 'keyup', nameInputChanged);
2368	nameInputChanged();
2369
2370	return typeSelect;
2371};
2372
2373/**
2374 * Static overrides
2375 */
2376(function()
2377{
2378	// Uses HTML for background pages (to support grid background image)
2379	mxGraphView.prototype.validateBackgroundPage = function()
2380	{
2381		var graph = this.graph;
2382
2383		if (graph.container != null && !graph.transparentBackground)
2384		{
2385			if (graph.pageVisible)
2386			{
2387				var bounds = this.getBackgroundPageBounds();
2388
2389				if (this.backgroundPageShape == null)
2390				{
2391					// Finds first element in graph container
2392					var firstChild = graph.container.firstChild;
2393
2394					while (firstChild != null && firstChild.nodeType != mxConstants.NODETYPE_ELEMENT)
2395					{
2396						firstChild = firstChild.nextSibling;
2397					}
2398
2399					if (firstChild != null)
2400					{
2401						this.backgroundPageShape = this.createBackgroundPageShape(bounds);
2402						this.backgroundPageShape.scale = 1;
2403
2404						// IE8 standards has known rendering issues inside mxWindow but not using shadow is worse.
2405						this.backgroundPageShape.isShadow = true;
2406						this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
2407						this.backgroundPageShape.init(graph.container);
2408
2409						// Required for the browser to render the background page in correct order
2410						firstChild.style.position = 'absolute';
2411						graph.container.insertBefore(this.backgroundPageShape.node, firstChild);
2412						this.backgroundPageShape.redraw();
2413
2414						this.backgroundPageShape.node.className = 'geBackgroundPage';
2415
2416						// Adds listener for double click handling on background
2417						mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
2418							mxUtils.bind(this, function(evt)
2419							{
2420								graph.dblClick(evt);
2421							})
2422						);
2423
2424						// Adds basic listeners for graph event dispatching outside of the
2425						// container and finishing the handling of a single gesture
2426						mxEvent.addGestureListeners(this.backgroundPageShape.node,
2427							mxUtils.bind(this, function(evt)
2428							{
2429								graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
2430							}),
2431							mxUtils.bind(this, function(evt)
2432							{
2433								// Hides the tooltip if mouse is outside container
2434								if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover())
2435								{
2436									graph.tooltipHandler.hide();
2437								}
2438
2439								if (graph.isMouseDown && !mxEvent.isConsumed(evt))
2440								{
2441									graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
2442								}
2443							}),
2444							mxUtils.bind(this, function(evt)
2445							{
2446								graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
2447							})
2448						);
2449					}
2450				}
2451				else
2452				{
2453					this.backgroundPageShape.scale = 1;
2454					this.backgroundPageShape.bounds = bounds;
2455					this.backgroundPageShape.redraw();
2456				}
2457			}
2458			else if (this.backgroundPageShape != null)
2459			{
2460				this.backgroundPageShape.destroy();
2461				this.backgroundPageShape = null;
2462			}
2463
2464			this.validateBackgroundStyles();
2465		}
2466	};
2467
2468	// Updates the CSS of the background to draw the grid
2469	mxGraphView.prototype.validateBackgroundStyles = function()
2470	{
2471		var graph = this.graph;
2472		var color = (graph.background == null || graph.background == mxConstants.NONE) ? graph.defaultPageBackgroundColor : graph.background;
2473		var gridColor = (color != null && this.gridColor != color.toLowerCase()) ? this.gridColor : '#ffffff';
2474		var image = 'none';
2475		var position = '';
2476
2477		if (graph.isGridEnabled() || graph.gridVisible)
2478		{
2479			var phase = 10;
2480
2481			if (mxClient.IS_SVG)
2482			{
2483				// Generates the SVG required for drawing the dynamic grid
2484				image = unescape(encodeURIComponent(this.createSvgGrid(gridColor)));
2485				image = (window.btoa) ? btoa(image) : Base64.encode(image, true);
2486				image = 'url(' + 'data:image/svg+xml;base64,' + image + ')'
2487				phase = graph.gridSize * this.scale * this.gridSteps;
2488			}
2489			else
2490			{
2491				// Fallback to grid wallpaper with fixed size
2492				image = 'url(' + this.gridImage + ')';
2493			}
2494
2495			var x0 = 0;
2496			var y0 = 0;
2497
2498			if (graph.view.backgroundPageShape != null)
2499			{
2500				var bds = this.getBackgroundPageBounds();
2501
2502				x0 = 1 + bds.x;
2503				y0 = 1 + bds.y;
2504			}
2505
2506			// Computes the offset to maintain origin for grid
2507			position = -Math.round(phase - mxUtils.mod(this.translate.x * this.scale - x0, phase)) + 'px ' +
2508				-Math.round(phase - mxUtils.mod(this.translate.y * this.scale - y0, phase)) + 'px';
2509		}
2510
2511		var canvas = graph.view.canvas;
2512
2513		if (canvas.ownerSVGElement != null)
2514		{
2515			canvas = canvas.ownerSVGElement;
2516		}
2517
2518		if (graph.view.backgroundPageShape != null)
2519		{
2520			graph.view.backgroundPageShape.node.style.backgroundPosition = position;
2521			graph.view.backgroundPageShape.node.style.backgroundImage = image;
2522			graph.view.backgroundPageShape.node.style.backgroundColor = color;
2523			graph.view.backgroundPageShape.node.style.borderColor = graph.defaultPageBorderColor;
2524			graph.container.className = 'geDiagramContainer geDiagramBackdrop';
2525			canvas.style.backgroundImage = 'none';
2526			canvas.style.backgroundColor = '';
2527		}
2528		else
2529		{
2530			graph.container.className = 'geDiagramContainer';
2531			canvas.style.backgroundPosition = position;
2532			canvas.style.backgroundColor = color;
2533			canvas.style.backgroundImage = image;
2534		}
2535	};
2536
2537	// Returns the SVG required for painting the background grid.
2538	mxGraphView.prototype.createSvgGrid = function(color)
2539	{
2540		var tmp = this.graph.gridSize * this.scale;
2541
2542		while (tmp < this.minGridSize)
2543		{
2544			tmp *= 2;
2545		}
2546
2547		var tmp2 = this.gridSteps * tmp;
2548
2549		// Small grid lines
2550		var d = [];
2551
2552		for (var i = 1; i < this.gridSteps; i++)
2553		{
2554			var tmp3 = i * tmp;
2555			d.push('M 0 ' + tmp3 + ' L ' + tmp2 + ' ' + tmp3 + ' M ' + tmp3 + ' 0 L ' + tmp3 + ' ' + tmp2);
2556		}
2557
2558		// KNOWN: Rounding errors for certain scales (eg. 144%, 121% in Chrome, FF and Safari). Workaround
2559		// in Chrome is to use 100% for the svg size, but this results in blurred grid for large diagrams.
2560		var size = tmp2;
2561		var svg =  '<svg width="' + size + '" height="' + size + '" xmlns="' + mxConstants.NS_SVG + '">' +
2562		    '<defs><pattern id="grid" width="' + tmp2 + '" height="' + tmp2 + '" patternUnits="userSpaceOnUse">' +
2563		    '<path d="' + d.join(' ') + '" fill="none" stroke="' + color + '" opacity="0.2" stroke-width="1"/>' +
2564		    '<path d="M ' + tmp2 + ' 0 L 0 0 0 ' + tmp2 + '" fill="none" stroke="' + color + '" stroke-width="1"/>' +
2565		    '</pattern></defs><rect width="100%" height="100%" fill="url(#grid)"/></svg>';
2566
2567		return svg;
2568	};
2569
2570	// Adds panning for the grid with no page view and disabled scrollbars
2571	var mxGraphPanGraph = mxGraph.prototype.panGraph;
2572	mxGraph.prototype.panGraph = function(dx, dy)
2573	{
2574		mxGraphPanGraph.apply(this, arguments);
2575
2576		if (this.shiftPreview1 != null)
2577		{
2578			var canvas = this.view.canvas;
2579
2580			if (canvas.ownerSVGElement != null)
2581			{
2582				canvas = canvas.ownerSVGElement;
2583			}
2584
2585			var phase = this.gridSize * this.view.scale * this.view.gridSteps;
2586			var position = -Math.round(phase - mxUtils.mod(this.view.translate.x * this.view.scale + dx, phase)) + 'px ' +
2587				-Math.round(phase - mxUtils.mod(this.view.translate.y * this.view.scale + dy, phase)) + 'px';
2588			canvas.style.backgroundPosition = position;
2589		}
2590	};
2591
2592	// Draws page breaks only within the page
2593	mxGraph.prototype.updatePageBreaks = function(visible, width, height)
2594	{
2595		var scale = this.view.scale;
2596		var tr = this.view.translate;
2597		var fmt = this.pageFormat;
2598		var ps = scale * this.pageScale;
2599
2600		var bounds2 = this.view.getBackgroundPageBounds();
2601
2602		width = bounds2.width;
2603		height = bounds2.height;
2604		var bounds = new mxRectangle(scale * tr.x, scale * tr.y, fmt.width * ps, fmt.height * ps);
2605
2606		// Does not show page breaks if the scale is too small
2607		visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
2608
2609		var horizontalCount = (visible) ? Math.ceil(height / bounds.height) - 1 : 0;
2610		var verticalCount = (visible) ? Math.ceil(width / bounds.width) - 1 : 0;
2611		var right = bounds2.x + width;
2612		var bottom = bounds2.y + height;
2613
2614		if (this.horizontalPageBreaks == null && horizontalCount > 0)
2615		{
2616			this.horizontalPageBreaks = [];
2617		}
2618
2619		if (this.verticalPageBreaks == null && verticalCount > 0)
2620		{
2621			this.verticalPageBreaks = [];
2622		}
2623
2624		var drawPageBreaks = mxUtils.bind(this, function(breaks)
2625		{
2626			if (breaks != null)
2627			{
2628				var count = (breaks == this.horizontalPageBreaks) ? horizontalCount : verticalCount;
2629
2630				for (var i = 0; i <= count; i++)
2631				{
2632					var pts = (breaks == this.horizontalPageBreaks) ?
2633						[new mxPoint(Math.round(bounds2.x), Math.round(bounds2.y + (i + 1) * bounds.height)),
2634						 new mxPoint(Math.round(right), Math.round(bounds2.y + (i + 1) * bounds.height))] :
2635						[new mxPoint(Math.round(bounds2.x + (i + 1) * bounds.width), Math.round(bounds2.y)),
2636						 new mxPoint(Math.round(bounds2.x + (i + 1) * bounds.width), Math.round(bottom))];
2637
2638					if (breaks[i] != null)
2639					{
2640						breaks[i].points = pts;
2641						breaks[i].redraw();
2642					}
2643					else
2644					{
2645						var pageBreak = new mxPolyline(pts, this.pageBreakColor);
2646						pageBreak.dialect = this.dialect;
2647						pageBreak.isDashed = this.pageBreakDashed;
2648						pageBreak.pointerEvents = false;
2649						pageBreak.init(this.view.backgroundPane);
2650						pageBreak.redraw();
2651
2652						breaks[i] = pageBreak;
2653					}
2654				}
2655
2656				for (var i = count; i < breaks.length; i++)
2657				{
2658					breaks[i].destroy();
2659				}
2660
2661				breaks.splice(count, breaks.length - count);
2662			}
2663		});
2664
2665		drawPageBreaks(this.horizontalPageBreaks);
2666		drawPageBreaks(this.verticalPageBreaks);
2667	};
2668
2669	// Disables removing relative children and table rows and cells from parents
2670	var mxGraphHandlerShouldRemoveCellsFromParent = mxGraphHandler.prototype.shouldRemoveCellsFromParent;
2671	mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)
2672	{
2673		for (var i = 0; i < cells.length; i++)
2674		{
2675			if (this.graph.isTableCell(cells[i]) || this.graph.isTableRow(cells[i]))
2676			{
2677				return false;
2678			}
2679			else if (this.graph.getModel().isVertex(cells[i]))
2680			{
2681				var geo = this.graph.getCellGeometry(cells[i]);
2682
2683				if (geo != null && geo.relative)
2684				{
2685					return false;
2686				}
2687			}
2688		}
2689
2690		return mxGraphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
2691	};
2692
2693	// Overrides to ignore hotspot only for target terminal
2694	var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
2695	mxConnectionHandler.prototype.createMarker = function()
2696	{
2697		var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
2698
2699		marker.intersects = mxUtils.bind(this, function(state, evt)
2700		{
2701			if (this.isConnecting())
2702			{
2703				return true;
2704			}
2705
2706			return mxCellMarker.prototype.intersects.apply(marker, arguments);
2707		});
2708
2709		return marker;
2710	};
2711
2712	// Creates background page shape
2713	mxGraphView.prototype.createBackgroundPageShape = function(bounds)
2714	{
2715		return new mxRectangleShape(bounds, '#ffffff', this.graph.defaultPageBorderColor);
2716	};
2717
2718	// Fits the number of background pages to the graph
2719	mxGraphView.prototype.getBackgroundPageBounds = function()
2720	{
2721		var gb = this.getGraphBounds();
2722
2723		// Computes unscaled, untranslated graph bounds
2724		var x = (gb.width > 0) ? gb.x / this.scale - this.translate.x : 0;
2725		var y = (gb.height > 0) ? gb.y / this.scale - this.translate.y : 0;
2726		var w = gb.width / this.scale;
2727		var h = gb.height / this.scale;
2728
2729		var fmt = this.graph.pageFormat;
2730		var ps = this.graph.pageScale;
2731
2732		var pw = fmt.width * ps;
2733		var ph = fmt.height * ps;
2734
2735		var x0 = Math.floor(Math.min(0, x) / pw);
2736		var y0 = Math.floor(Math.min(0, y) / ph);
2737		var xe = Math.ceil(Math.max(1, x + w) / pw);
2738		var ye = Math.ceil(Math.max(1, y + h) / ph);
2739
2740		var rows = xe - x0;
2741		var cols = ye - y0;
2742
2743		var bounds = new mxRectangle(this.scale * (this.translate.x + x0 * pw), this.scale *
2744				(this.translate.y + y0 * ph), this.scale * rows * pw, this.scale * cols * ph);
2745
2746		return bounds;
2747	};
2748
2749	// Add panning for background page in VML
2750	var graphPanGraph = mxGraph.prototype.panGraph;
2751	mxGraph.prototype.panGraph = function(dx, dy)
2752	{
2753		graphPanGraph.apply(this, arguments);
2754
2755		if ((this.dialect != mxConstants.DIALECT_SVG && this.view.backgroundPageShape != null) &&
2756			(!this.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.container)))
2757		{
2758			this.view.backgroundPageShape.node.style.marginLeft = dx + 'px';
2759			this.view.backgroundPageShape.node.style.marginTop = dy + 'px';
2760		}
2761	};
2762
2763	/**
2764	 * Consumes click events for disabled menu items.
2765	 */
2766	var mxPopupMenuAddItem = mxPopupMenu.prototype.addItem;
2767	mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled)
2768	{
2769		var result = mxPopupMenuAddItem.apply(this, arguments);
2770
2771		if (enabled != null && !enabled)
2772		{
2773			mxEvent.addListener(result, 'mousedown', function(evt)
2774			{
2775				mxEvent.consume(evt);
2776			});
2777		}
2778
2779		return result;
2780	};
2781
2782	/**
2783	 * Selects tables before cells and rows.
2784	 */
2785	var mxGraphHandlerIsPropagateSelectionCell = mxGraphHandler.prototype.isPropagateSelectionCell;
2786	mxGraphHandler.prototype.isPropagateSelectionCell = function(cell, immediate, me)
2787	{
2788		var result = false;
2789		var parent = this.graph.model.getParent(cell)
2790
2791		if (immediate)
2792		{
2793			var geo = (this.graph.model.isEdge(cell)) ? null :
2794				this.graph.getCellGeometry(cell);
2795
2796			result = !this.graph.model.isEdge(parent) &&
2797				!this.graph.isSiblingSelected(cell) &&
2798				((geo != null && geo.relative) ||
2799				!this.graph.isContainer(parent) ||
2800				this.graph.isPart(cell));
2801		}
2802		else
2803		{
2804			result = mxGraphHandlerIsPropagateSelectionCell.apply(this, arguments);
2805
2806			if (this.graph.isTableCell(cell) || this.graph.isTableRow(cell))
2807			{
2808				var table = parent;
2809
2810				if (!this.graph.isTable(table))
2811				{
2812					table = this.graph.model.getParent(table);
2813				}
2814
2815				result = !this.graph.selectionCellsHandler.isHandled(table) ||
2816					(this.graph.isCellSelected(table) && this.graph.isToggleEvent(me.getEvent())) ||
2817					(this.graph.isCellSelected(cell) && !this.graph.isToggleEvent(me.getEvent())) ||
2818					(this.graph.isTableCell(cell) && this.graph.isCellSelected(parent));
2819			}
2820		}
2821
2822		return result;
2823	};
2824
2825	/**
2826	 * Returns last selected ancestor
2827	 */
2828	mxPopupMenuHandler.prototype.getCellForPopupEvent = function(me)
2829	{
2830		var cell = me.getCell();
2831		var model = this.graph.getModel();
2832		var parent = model.getParent(cell);
2833		var state = this.graph.view.getState(parent);
2834		var selected = this.graph.isCellSelected(cell);
2835
2836		while (state != null && (model.isVertex(parent) || model.isEdge(parent)))
2837		{
2838			var temp = this.graph.isCellSelected(parent);
2839			selected = selected || temp;
2840
2841			if (temp || (!selected && (this.graph.isTableCell(cell) ||
2842				this.graph.isTableRow(cell))))
2843			{
2844				cell = parent;
2845			}
2846
2847			parent = model.getParent(parent);
2848		}
2849
2850		return cell;
2851	};
2852
2853})();
2854