diff --git a/oaweb/package.json b/oaweb/package.json
index 4e811ac..17544d3 100644
--- a/oaweb/package.json
+++ b/oaweb/package.json
@@ -14,16 +14,17 @@
},
"dependencies": {
"@quasar/extras": "^1.16.4",
+ "@toast-ui/editor": "^3.2.2",
"@veypi/msg": "^0.1.1",
"@veypi/oaer": "^0.0.1",
"@veypi/one-icon": "2",
"animate.css": "^4.1.1",
"axios": "^1.2.1",
+ "cherry-markdown": "^0.8.26",
"js-base64": "^3.7.5",
"mitt": "^3.0.1",
"pinia": "^2.0.11",
"quasar": "^2.6.0",
- "vditor": "^3.9.6",
"vue": "^3.0.0",
"vue-i18n": "^9.2.2",
"vue-router": "^4.0.0",
diff --git a/oaweb/public/cherry/drawio.html b/oaweb/public/cherry/drawio.html
new file mode 100644
index 0000000..8f4a8e8
--- /dev/null
+++ b/oaweb/public/cherry/drawio.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+ draw.io demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/oaweb/public/cherry/drawio.js b/oaweb/public/cherry/drawio.js
new file mode 100644
index 0000000..44df1fe
--- /dev/null
+++ b/oaweb/public/cherry/drawio.js
@@ -0,0 +1,74 @@
+// Extends EditorUi to update I/O action states based on availability of backend
+(function() {
+ var editorUiInit = EditorUi.prototype.init;
+
+ EditorUi.prototype.init = function() {
+ editorUiInit.apply(this, arguments);
+ };
+
+ // Adds required resources (disables loading of fallback properties, this can only
+ // be used if we know that all keys are defined in the language specific file)
+ mxResources.loadDefaultBundle = false;
+ var bundle = mxResources.getDefaultBundle(mxLanguage);
+
+ // Fixes possible asynchronous requests
+ mxUtils.getAll([bundle, './drawio/theme/default.xml'], function(xhr) {
+ // Adds bundle text to resources
+ mxResources.parse(xhr[0].getText());
+
+ // Configures the default graph theme
+ var themes = new Object();
+ themes[Graph.prototype.defaultThemeName] = xhr[1].getDocumentElement();
+
+ // Main
+ window.editorUIInstance = new EditorUi(new Editor(false, themes));
+
+ try {
+ addPostMessageListener(editorUIInstance.editor);
+ } catch (error) {
+ console.log(error);
+ }
+ window.parent.postMessage({ eventName: 'ready', value: '' }, '*');
+
+ }, function() {
+ document.body.innerHTML = 'Error loading resource files. Please check browser console.';
+ });
+})();
+
+function addPostMessageListener(graphEditor) {
+ window.addEventListener('message', function(event) {
+ if (!event.data || !event.data.eventName) {
+ return
+ }
+ switch (event.data.eventName) {
+ case 'setData':
+ var value = event.data.value;
+ var doc = mxUtils.parseXml(value);
+ var documentName = 'cherry-drawio-' + new Date().getTime();
+ editorUIInstance.editor.setGraphXml(null);
+ graphEditor.graph.importGraphModel(doc.documentElement);
+ graphEditor.setFilename(documentName);
+ window.parent.postMessage({ eventName: 'setData:success', value: '' }, '*');
+ break;
+ case 'getData':
+ editorUIInstance.editor.graph.stopEditing();
+ var xmlData = mxUtils.getXml(editorUIInstance.editor.getGraphXml());
+ editorUIInstance.exportImage(1, "#ffffff", true, null, true, 50, null, "png", function(base64, filename) {
+ window.parent.postMessage({
+ mceAction: 'getData:success',
+ eventName: 'getData:success',
+ value: {
+ xmlData: xmlData,
+ base64: base64,
+ }
+ }, '*');
+ })
+ break;
+ case 'ready?':
+ window.parent.postMessage({ eventName: 'ready', value: '' }, '*');
+ break;
+ default:
+ break;
+ }
+ });
+}
diff --git a/oaweb/public/cherry/drawio/Actions.js b/oaweb/public/cherry/drawio/Actions.js
new file mode 100644
index 0000000..198ee9b
--- /dev/null
+++ b/oaweb/public/cherry/drawio/Actions.js
@@ -0,0 +1,1961 @@
+/**
+ * Copyright (c) 2006-2020, JGraph Ltd
+ * Copyright (c) 2006-2020, draw.io AG
+ *
+ * Constructs the actions object for the given UI.
+ */
+function Actions(editorUi)
+{
+ this.editorUi = editorUi;
+ this.actions = new Object();
+ this.init();
+};
+
+/**
+ * Adds the default actions.
+ */
+Actions.prototype.init = function()
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+ var isGraphEnabled = function()
+ {
+ return Action.prototype.isEnabled.apply(this, arguments) && graph.isEnabled();
+ };
+
+ // File actions
+ this.addAction('new...', function() { graph.openLink(ui.getUrl()); });
+ this.addAction('open...', function()
+ {
+ window.openNew = true;
+ window.openKey = 'open';
+
+ ui.openFile();
+ });
+ this.put('smartFit', new Action(mxResources.get('fitWindow') + ' / ' + mxResources.get('resetView'), function()
+ {
+ graph.popupMenuHandler.hideMenu();
+
+ var scale = graph.view.scale;
+ var sx = graph.container.scrollLeft;
+ var sy = graph.container.scrollTop;
+ var tx = graph.view.translate.x;
+ var ty = graph.view.translate.y;
+ var thresh = 5;
+
+ ui.actions.get('resetView').funct();
+
+ // Toggle scale if nothing has changed
+ if (Math.abs(scale - graph.view.scale) < 0.00001 &&
+ Math.abs(sx - graph.container.scrollLeft) < thresh &&
+ Math.abs(sy - graph.container.scrollTop) < thresh &&
+ tx == graph.view.translate.x &&
+ ty == graph.view.translate.y)
+ {
+ ui.actions.get('fitWindow').funct();
+ }
+ }, null, null, 'Enter'));
+ this.addAction('keyPressEnter', function()
+ {
+ if (graph.isEnabled())
+ {
+ if (graph.isSelectionEmpty())
+ {
+ ui.actions.get('smartFit').funct();
+ }
+ else
+ {
+ graph.startEditingAtCell();
+ }
+ }
+ });
+ this.addAction('import...', function()
+ {
+ window.openNew = false;
+ window.openKey = 'import';
+
+ // Closes dialog after open
+ window.openFile = new OpenFile(mxUtils.bind(this, function()
+ {
+ ui.hideDialog();
+ }));
+
+ window.openFile.setConsumer(mxUtils.bind(this, function(xml, filename)
+ {
+ try
+ {
+ var doc = mxUtils.parseXml(xml);
+ editor.graph.setSelectionCells(editor.graph.importGraphModel(doc.documentElement));
+ }
+ catch (e)
+ {
+ mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
+ }
+ }));
+
+ // Removes openFile if dialog is closed
+ ui.showDialog(new OpenDialog(this).container, 320, 220, true, true, function()
+ {
+ window.openFile = null;
+ });
+ }).isEnabled = isGraphEnabled;
+ this.addAction('save', function() { ui.saveFile(false); }, null, null, Editor.ctrlKey + '+S').isEnabled = isGraphEnabled;
+ this.addAction('saveAs...', function() { ui.saveFile(true); }, null, null, Editor.ctrlKey + '+Shift+S').isEnabled = isGraphEnabled;
+ this.addAction('export...', function() { ui.showDialog(new ExportDialog(ui).container, 300, 340, true, true); });
+ this.addAction('editDiagram...', function()
+ {
+ var dlg = new EditDiagramDialog(ui);
+ ui.showDialog(dlg.container, 620, 420, true, false);
+ dlg.init();
+ });
+ this.addAction('pageSetup...', function() { ui.showDialog(new PageSetupDialog(ui).container, 320, 240, true, true); }).isEnabled = isGraphEnabled;
+ this.addAction('print...', function() { ui.showDialog(new PrintDialog(ui).container, 300, 180, true, true); }, null, 'sprite-print', Editor.ctrlKey + '+P');
+ this.addAction('preview', function() { mxUtils.show(graph, null, 10, 10); });
+
+ // Edit actions
+ this.addAction('undo', function() { ui.undo(); }, null, 'sprite-undo', Editor.ctrlKey + '+Z');
+ this.addAction('redo', function() { ui.redo(); }, null, 'sprite-redo', (!mxClient.IS_WIN) ? Editor.ctrlKey + '+Shift+Z' : Editor.ctrlKey + '+Y');
+ this.addAction('cut', function()
+ {
+ var cells = null;
+
+ try
+ {
+ cells = ui.copyXml();
+
+ if (cells != null)
+ {
+ graph.removeCells(cells, false);
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+
+ try
+ {
+ if (cells == null)
+ {
+ mxClipboard.cut(graph);
+ }
+ }
+ catch (e)
+ {
+ ui.handleError(e);
+ }
+ }, null, 'sprite-cut', Editor.ctrlKey + '+X');
+ this.addAction('copy', function()
+ {
+ try
+ {
+ ui.copyXml();
+ }
+ catch (e)
+ {
+ // ignore
+ }
+
+ try
+ {
+ mxClipboard.copy(graph);
+ }
+ catch (e)
+ {
+ ui.handleError(e);
+ }
+ }, null, 'sprite-copy', Editor.ctrlKey + '+C');
+ this.addAction('paste', function()
+ {
+ if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
+ {
+ var done = false;
+
+ try
+ {
+ if (Editor.enableNativeCipboard)
+ {
+ ui.readGraphModelFromClipboard(function(xml)
+ {
+ if (xml != null)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ ui.pasteXml(xml, true);
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ else
+ {
+ mxClipboard.paste(graph);
+ }
+ })
+
+ done = true;
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+
+ if (!done)
+ {
+ mxClipboard.paste(graph);
+ }
+ }
+ }, false, 'sprite-paste', Editor.ctrlKey + '+V');
+ this.addAction('pasteHere', function(evt)
+ {
+ function pasteCellsHere(cells)
+ {
+ if (cells != null)
+ {
+ var includeEdges = true;
+
+ for (var i = 0; i < cells.length && includeEdges; i++)
+ {
+ includeEdges = includeEdges && graph.model.isEdge(cells[i]);
+ }
+
+ var t = graph.view.translate;
+ var s = graph.view.scale;
+ var dx = t.x;
+ var dy = t.y;
+ var bb = null;
+
+ if (cells.length == 1 && includeEdges)
+ {
+ var geo = graph.getCellGeometry(cells[0]);
+
+ if (geo != null)
+ {
+ bb = geo.getTerminalPoint(true);
+ }
+ }
+
+ bb = (bb != null) ? bb : graph.getBoundingBoxFromGeometry(cells, includeEdges);
+
+ if (bb != null)
+ {
+ var x = Math.round(graph.snap(graph.popupMenuHandler.triggerX / s - dx));
+ var y = Math.round(graph.snap(graph.popupMenuHandler.triggerY / s - dy));
+
+ graph.cellsMoved(cells, x - bb.x, y - bb.y);
+ }
+ }
+ };
+
+ function fallback()
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ pasteCellsHere(mxClipboard.paste(graph));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ };
+
+ if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
+ {
+ var done = false;
+
+ try
+ {
+ if (Editor.enableNativeCipboard)
+ {
+ ui.readGraphModelFromClipboard(function(xml)
+ {
+ if (xml != null)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ pasteCellsHere(ui.pasteXml(xml, true));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ else
+ {
+ fallback();
+ }
+ })
+
+ done = true;
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+
+ if (!done)
+ {
+ fallback();
+ }
+ }
+ });
+
+ this.addAction('copySize', function()
+ {
+ var cell = graph.getSelectionCell();
+
+ if (graph.isEnabled() && cell != null && graph.getModel().isVertex(cell))
+ {
+ var geo = graph.getCellGeometry(cell);
+
+ if (geo != null)
+ {
+ ui.copiedSize = new mxRectangle(geo.x, geo.y, geo.width, geo.height);
+ }
+ }
+ }, null, null, 'Alt+Shift+F');
+
+ this.addAction('pasteSize', function()
+ {
+ if (graph.isEnabled() && !graph.isSelectionEmpty() && ui.copiedSize != null)
+ {
+ graph.getModel().beginUpdate();
+
+ try
+ {
+ var cells = graph.getResizableCells(graph.getSelectionCells());
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (graph.getModel().isVertex(cells[i]))
+ {
+ var geo = graph.getCellGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ geo.width = ui.copiedSize.width;
+ geo.height = ui.copiedSize.height;
+
+ graph.getModel().setGeometry(cells[i], geo);
+ }
+ }
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ }, null, null, 'Alt+Shift+V');
+
+ this.addAction('copyData', function()
+ {
+ var cell = graph.getSelectionCell() || graph.getModel().getRoot();
+
+ if (graph.isEnabled() && cell != null)
+ {
+ var value = cell.cloneValue();
+
+ if (value != null && !isNaN(value.nodeType))
+ {
+ ui.copiedValue = value;
+ }
+ }
+ }, null, null, 'Alt+Shift+B');
+
+ this.addAction('pasteData', function(evt, trigger)
+ {
+ // Context menu click uses trigger, toolbar menu click uses evt
+ var evt = (trigger != null) ? trigger : evt;
+ var model = graph.getModel();
+
+ function applyValue(cell, value)
+ {
+ var old = model.getValue(cell);
+ value = cell.cloneValue(value);
+ value.removeAttribute('placeholders');
+
+ // Carries over placeholders and label properties
+ if (old != null && !isNaN(old.nodeType))
+ {
+ value.setAttribute('placeholders', old.getAttribute('placeholders'));
+ }
+
+ if (evt == null || !mxEvent.isShiftDown(evt))
+ {
+ value.setAttribute('label', graph.convertValueToString(cell));
+ }
+
+ model.setValue(cell, value);
+ };
+
+ if (graph.isEnabled() && !graph.isSelectionEmpty() && ui.copiedValue != null)
+ {
+ model.beginUpdate();
+
+ try
+ {
+ var cells = graph.getEditableCells(graph.getSelectionCells());
+
+ if (cells.length == 0)
+ {
+ applyValue(model.getRoot(), ui.copiedValue);
+ }
+ else
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ applyValue(cells[i], ui.copiedValue);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ }, null, null, 'Alt+Shift+E');
+
+ function deleteCells(includeEdges)
+ {
+ // Cancels interactive operations
+ graph.escape();
+ var select = graph.deleteCells(graph.getDeletableCells(graph.getSelectionCells()), includeEdges);
+
+ if (select != null)
+ {
+ graph.setSelectionCells(select);
+ }
+ };
+
+ function deleteLabels()
+ {
+ if (!graph.isSelectionEmpty())
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = graph.getSelectionCells();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ graph.cellLabelChanged(cells[i], '');
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ };
+
+ this.addAction('delete', function(evt, trigger)
+ {
+ // Context menu click uses trigger, toolbar menu click uses evt
+ var evt = (trigger != null) ? trigger : evt;
+
+ if (evt != null && mxEvent.isShiftDown(evt))
+ {
+ deleteLabels();
+ }
+ else
+ {
+ deleteCells(evt != null && (mxEvent.isControlDown(evt) ||
+ mxEvent.isMetaDown(evt) || mxEvent.isAltDown(evt)));
+ }
+ }, null, null, 'Delete');
+ this.addAction('deleteAll', function()
+ {
+ deleteCells(true);
+ });
+ this.addAction('deleteLabels', function()
+ {
+ deleteLabels();
+ }, null, null, Editor.ctrlKey + '+Delete');
+ this.addAction('duplicate', function()
+ {
+ try
+ {
+ graph.setSelectionCells(graph.duplicateCells());
+ graph.scrollCellToVisible(graph.getSelectionCell());
+ }
+ catch (e)
+ {
+ ui.handleError(e);
+ }
+ }, null, null, Editor.ctrlKey + '+D');
+ this.put('mergeCells', new Action(mxResources.get('merge'), function()
+ {
+ var ss = ui.getSelectionState();
+
+ if (ss.mergeCell != null)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ graph.setCellStyles('rowspan', ss.rowspan, [ss.mergeCell]);
+ graph.setCellStyles('colspan', ss.colspan, [ss.mergeCell]);
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ }));
+ this.put('unmergeCells', new Action(mxResources.get('unmerge'), function()
+ {
+ var ss = ui.getSelectionState();
+
+ if (ss.cells.length > 0)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ graph.setCellStyles('rowspan', null, ss.cells);
+ graph.setCellStyles('colspan', null, ss.cells);
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ }));
+ this.put('turn', new Action(mxResources.get('turn') + ' / ' + mxResources.get('reverse'), function(evt, trigger)
+ {
+ // Context menu click uses trigger, toolbar menu click uses evt
+ var evt = (trigger != null) ? trigger : evt;
+
+ graph.turnShapes(graph.getResizableCells(graph.getSelectionCells()),
+ (evt != null) ? mxEvent.isShiftDown(evt) : false);
+ }, null, null, (mxClient.IS_SF) ? null : Editor.ctrlKey + '+R'));
+ this.put('selectConnections', new Action(mxResources.get('selectEdges'), function(evt)
+ {
+ var cell = graph.getSelectionCell();
+
+ if (graph.isEnabled() && cell != null)
+ {
+ graph.addSelectionCells(graph.getEdges(cell));
+ }
+ }));
+ this.addAction('selectVertices', function() { graph.selectVertices(null, true); }, null, null, Editor.ctrlKey + '+Shift+I');
+ this.addAction('selectEdges', function() { graph.selectEdges(); }, null, null, Editor.ctrlKey + '+Shift+E');
+ this.addAction('selectAll', function() { graph.selectAll(null, true); }, null, null, Editor.ctrlKey + '+A');
+ this.addAction('selectNone', function() { graph.clearSelection(); }, null, null, Editor.ctrlKey + '+Shift+A');
+ this.addAction('lockUnlock', function()
+ {
+ if (!graph.isSelectionEmpty())
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = graph.getSelectionCells();
+ var style = graph.getCurrentCellStyle(graph.getSelectionCell());
+ var value = (mxUtils.getValue(style, mxConstants.STYLE_EDITABLE, 1)) == 1 ? 0 : 1;
+ graph.setCellStyles(mxConstants.STYLE_MOVABLE, value, cells);
+ graph.setCellStyles(mxConstants.STYLE_RESIZABLE, value, cells);
+ graph.setCellStyles(mxConstants.STYLE_ROTATABLE, value, cells);
+ graph.setCellStyles(mxConstants.STYLE_DELETABLE, value, cells);
+ graph.setCellStyles(mxConstants.STYLE_EDITABLE, value, cells);
+ graph.setCellStyles('locked', (value == 1) ? 0 : 1, cells);
+ graph.setCellStyles('connectable', value, cells);
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ }, null, null, Editor.ctrlKey + '+L');
+
+ // Navigation actions
+ this.addAction('home', function() { graph.home(); }, null, null, 'Shift+Home');
+ this.addAction('exitGroup', function() { graph.exitGroup(); }, null, null, Editor.ctrlKey + '+Shift+Home');
+ this.addAction('enterGroup', function() { graph.enterGroup(); }, null, null, Editor.ctrlKey + '+Shift+End');
+ this.addAction('collapse', function() { graph.foldCells(true); }, null, null, Editor.ctrlKey + '+Home');
+ this.addAction('expand', function() { graph.foldCells(false); }, null, null, Editor.ctrlKey + '+End');
+
+ // Arrange actions
+ this.addAction('toFront', function()
+ {
+ graph.orderCells(false);
+ }, null, null, Editor.ctrlKey + '+Shift+F');
+ this.addAction('toBack', function()
+ {
+ graph.orderCells(true);
+ }, null, null, Editor.ctrlKey + '+Shift+B');
+ this.addAction('bringForward', function(evt)
+ {
+ graph.orderCells(false, null, true);
+ });
+ this.addAction('sendBackward', function(evt)
+ {
+ graph.orderCells(true, null, true);
+ });
+ this.addAction('group', function()
+ {
+ if (graph.isEnabled())
+ {
+ var cells = mxUtils.sortCells(graph.getSelectionCells(), true);
+
+ if (cells.length == 1 && !graph.isTable(cells[0]) && !graph.isTableRow(cells[0]))
+ {
+ graph.setCellStyles('container', '1');
+ }
+ else
+ {
+ cells = graph.getCellsForGroup(cells);
+
+ if (cells.length > 1)
+ {
+ graph.setSelectionCell(graph.groupCells(null, 0, cells));
+ }
+ }
+ }
+ }, null, null, Editor.ctrlKey + '+G');
+ this.addAction('ungroup', function()
+ {
+ if (graph.isEnabled())
+ {
+ var cells = graph.getEditableCells(graph.getSelectionCells());
+
+ graph.model.beginUpdate();
+ try
+ {
+ var temp = graph.ungroupCells();
+
+ // Clears container flag for remaining cells
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (graph.model.contains(cells[i]))
+ {
+ if (graph.model.getChildCount(cells[i]) == 0 &&
+ graph.model.isVertex(cells[i]))
+ {
+ graph.setCellStyles('container', '0', [cells[i]]);
+ }
+
+ temp.push(cells[i]);
+ }
+ }
+ }
+ }
+ finally
+ {
+ graph.model.endUpdate();
+ }
+
+ if (temp.length > 0)
+ {
+ graph.setSelectionCells(temp);
+ }
+ }
+ }, null, null, Editor.ctrlKey + '+Shift+U');
+ this.addAction('removeFromGroup', function()
+ {
+ if (graph.isEnabled())
+ {
+ var cells = graph.getSelectionCells();
+
+ // Removes table rows and cells
+ if (cells != null)
+ {
+ var temp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!graph.isTableRow(cells[i]) &&
+ !graph.isTableCell(cells[i]))
+ {
+ temp.push(cells[i]);
+ }
+ }
+
+ graph.removeCellsFromParent(temp);
+ }
+ }
+ });
+ // Adds action
+ this.addAction('edit', function()
+ {
+ if (graph.isEnabled())
+ {
+ graph.startEditingAtCell();
+ }
+ }, null, null, 'F2/Enter');
+ this.addAction('editData...', function()
+ {
+ var cell = graph.getSelectionCell() || graph.getModel().getRoot();
+ ui.showDataDialog(cell);
+ }, null, null, Editor.ctrlKey + '+M');
+ this.addAction('editTooltip...', function()
+ {
+ var cell = graph.getSelectionCell();
+
+ if (graph.isEnabled() && cell != null && graph.isCellEditable(cell))
+ {
+ var tooltip = '';
+
+ if (mxUtils.isNode(cell.value))
+ {
+ var tmp = null;
+
+ if (Graph.translateDiagram && Graph.diagramLanguage != null &&
+ cell.value.hasAttribute('tooltip_' + Graph.diagramLanguage))
+ {
+ tmp = cell.value.getAttribute('tooltip_' + Graph.diagramLanguage);
+ }
+
+ if (tmp == null)
+ {
+ tmp = cell.value.getAttribute('tooltip');
+ }
+
+ if (tmp != null)
+ {
+ tooltip = tmp;
+ }
+ }
+
+ var dlg = new TextareaDialog(ui, mxResources.get('editTooltip') + ':', tooltip, function(newValue)
+ {
+ graph.setTooltipForCell(cell, newValue);
+ });
+ ui.showDialog(dlg.container, 320, 200, true, true);
+ dlg.init();
+ }
+ }, null, null, 'Alt+Shift+T');
+ this.addAction('openLink', function()
+ {
+ var link = graph.getLinkForCell(graph.getSelectionCell());
+
+ if (link != null)
+ {
+ graph.openLink(link);
+ }
+ });
+ this.addAction('editLink...', function()
+ {
+ var cell = graph.getSelectionCell();
+
+ if (graph.isEnabled() && cell != null && graph.isCellEditable(cell))
+ {
+ var value = graph.getLinkForCell(cell) || '';
+
+ ui.showLinkDialog(value, mxResources.get('apply'), function(link, docs, linkTarget)
+ {
+ link = mxUtils.trim(link);
+ graph.setLinkForCell(cell, (link.length > 0) ? link : null);
+ graph.setAttributeForCell(cell, 'linkTarget', linkTarget);
+ }, true, graph.getLinkTargetForCell(cell));
+ }
+ }, null, null, 'Alt+Shift+L');
+ this.put('insertImage', new Action(mxResources.get('image') + '...', function()
+ {
+ if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
+ {
+ graph.clearSelection();
+ ui.actions.get('image').funct();
+ }
+ })).isEnabled = isGraphEnabled;
+ this.addAction('editImage...', function()
+ {
+ ui.actions.get('image').funct();
+ });
+ this.put('insertLink', new Action(mxResources.get('link') + '...', function()
+ {
+ if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
+ {
+ ui.showLinkDialog('', mxResources.get('insert'), function(link, docs, linkTarget)
+ {
+ link = mxUtils.trim(link);
+
+ if (link.length > 0)
+ {
+ var icon = null;
+ var title = graph.getLinkTitle(link);
+
+ if (docs != null && docs.length > 0)
+ {
+ icon = docs[0].iconUrl;
+ title = docs[0].name || docs[0].type;
+ title = title.charAt(0).toUpperCase() + title.substring(1);
+
+ if (title.length > 30)
+ {
+ title = title.substring(0, 30) + '...';
+ }
+ }
+
+ var linkCell = new mxCell(title, new mxGeometry(0, 0, 100, 40),
+ 'fontColor=#0000EE;fontStyle=4;rounded=1;overflow=hidden;' + ((icon != null) ?
+ 'shape=label;imageWidth=16;imageHeight=16;spacingLeft=26;align=left;image=' + icon :
+ 'spacing=10;'));
+ linkCell.vertex = true;
+
+ var pt = graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry([linkCell], true));
+ linkCell.geometry.x = pt.x;
+ linkCell.geometry.y = pt.y;
+
+ graph.setAttributeForCell(linkCell, 'linkTarget', linkTarget);
+ graph.setLinkForCell(linkCell, link);
+ graph.cellSizeUpdated(linkCell, true);
+
+ graph.getModel().beginUpdate();
+ try
+ {
+ linkCell = graph.addCell(linkCell);
+ graph.fireEvent(new mxEventObject('cellsInserted', 'cells', [linkCell]));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+
+ graph.setSelectionCell(linkCell);
+ graph.scrollCellToVisible(graph.getSelectionCell());
+ }
+ }, true);
+ }
+ }, null, null, 'L')).isEnabled = isGraphEnabled;
+ this.addAction('link...', mxUtils.bind(this, function()
+ {
+ if (graph.isEnabled())
+ {
+ if (graph.cellEditor.isContentEditing())
+ {
+ var elt = graph.getSelectedElement();
+ var link = graph.getParentByName(elt, 'A', graph.cellEditor.textarea);
+ var oldValue = '';
+
+ // Workaround for FF returning the outermost selected element after double
+ // click on a DOM hierarchy with a link inside (but not as topmost element)
+ if (link == null && elt != null && elt.getElementsByTagName != null)
+ {
+ // Finds all links in the selected DOM and uses the link
+ // where the selection text matches its text content
+ var links = elt.getElementsByTagName('a');
+
+ for (var i = 0; i < links.length && link == null; i++)
+ {
+ if (links[i].textContent == elt.textContent)
+ {
+ link = links[i];
+ }
+ }
+ }
+
+ if (link != null && link.nodeName == 'A')
+ {
+ oldValue = link.getAttribute('href') || '';
+ graph.selectNode(link);
+ }
+
+ var selState = graph.cellEditor.saveSelection();
+
+ ui.showLinkDialog(oldValue, mxResources.get('apply'), mxUtils.bind(this, function(value)
+ {
+ graph.cellEditor.restoreSelection(selState);
+
+ if (value != null)
+ {
+ graph.insertLink(value);
+ }
+ }));
+ }
+ else if (graph.isSelectionEmpty())
+ {
+ this.get('insertLink').funct();
+ }
+ else
+ {
+ this.get('editLink').funct();
+ }
+ }
+ })).isEnabled = isGraphEnabled;
+ this.addAction('autosize', function()
+ {
+ var cells = graph.getSelectionCells();
+
+ if (cells != null)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var cell = cells[i];
+
+ if (graph.getModel().isVertex(cell))
+ {
+ if (graph.getModel().getChildCount(cell) > 0)
+ {
+ graph.updateGroupBounds([cell], 0, true);
+ }
+ else
+ {
+ graph.updateCellSize(cell);
+ }
+ }
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ }, null, null, Editor.ctrlKey + '+Shift+Y');
+ this.addAction('snapToGrid', function()
+ {
+ graph.snapCellsToGrid(graph.getSelectionCells(), graph.gridSize);
+ });
+ this.addAction('formattedText', function()
+ {
+ graph.stopEditing();
+
+ var style = graph.getCommonStyle(graph.getSelectionCells());
+ var value = (mxUtils.getValue(style, 'html', '0') == '1') ? null : '1';
+
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = graph.getEditableCells(graph.getSelectionCells());
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ state = graph.getView().getState(cells[i]);
+
+ if (state != null)
+ {
+ var html = mxUtils.getValue(state.style, 'html', '0');
+
+ if (html == '1' && value == null)
+ {
+ graph.removeTextStyleForCell(state.cell);
+ graph.setCellStyles('html', value, [cells[i]]);
+ }
+ else if (html == '0' && value == '1')
+ {
+ // Converts HTML tags to text
+ var label = mxUtils.htmlEntities(graph.convertValueToString(state.cell), false);
+
+ if (mxUtils.getValue(state.style, 'nl2Br', '1') != '0')
+ {
+ // Converts newlines in plain text to breaks in HTML
+ // to match the plain text output
+ label = label.replace(/\n/g, '
');
+ }
+
+ graph.cellLabelChanged(state.cell, Graph.sanitizeHtml(label));
+ graph.setCellStyles('html', value, [cells[i]]);
+ }
+ }
+ }
+
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', ['html'],
+ 'values', [(value != null) ? value : '0'], 'cells', cells));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ });
+ this.addAction('wordWrap', function()
+ {
+ var state = graph.getView().getState(graph.getSelectionCell());
+ var value = 'wrap';
+
+ graph.stopEditing();
+
+ if (state != null && state.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap')
+ {
+ value = null;
+ }
+
+ graph.setCellStyles(mxConstants.STYLE_WHITE_SPACE, value);
+ });
+ this.addAction('rotation', function()
+ {
+ var value = '0';
+ var state = graph.getView().getState(graph.getSelectionCell());
+
+ if (state != null)
+ {
+ value = state.style[mxConstants.STYLE_ROTATION] || value;
+ }
+
+ var dlg = new FilenameDialog(ui, value, mxResources.get('apply'), function(newValue)
+ {
+ if (newValue != null && newValue.length > 0)
+ {
+ graph.setCellStyles(mxConstants.STYLE_ROTATION, newValue);
+ }
+ }, mxResources.get('enterValue') + ' (' + mxResources.get('rotation') + ' 0-360)');
+
+ ui.showDialog(dlg.container, 375, 80, true, true);
+ dlg.init();
+ });
+ // View actions
+ this.addAction('resetView', function()
+ {
+ graph.zoomTo(1);
+ ui.resetScrollbars();
+ }, null, null, 'Enter/Home');
+ this.addAction('zoomIn', function(evt)
+ {
+ if (graph.isFastZoomEnabled())
+ {
+ graph.lazyZoom(true, true, ui.buttonZoomDelay);
+ }
+ else
+ {
+ graph.zoomIn();
+ }
+ }, null, null, Editor.ctrlKey + ' + (Numpad) / Alt+Mousewheel');
+ this.addAction('zoomOut', function(evt)
+ {
+ if (graph.isFastZoomEnabled())
+ {
+ graph.lazyZoom(false, true, ui.buttonZoomDelay);
+ }
+ else
+ {
+ graph.zoomOut();
+ }
+ }, null, null, Editor.ctrlKey + ' - (Numpad) / Alt+Mousewheel');
+ this.addAction('fitWindow', function()
+ {
+ if (graph.pageVisible && graph.isSelectionEmpty())
+ {
+ graph.fitPages();
+ }
+ else
+ {
+ ui.fitDiagramToWindow();
+ }
+ }, null, null, Editor.ctrlKey + '+Shift+H');
+ this.addAction('fitPage', mxUtils.bind(this, function()
+ {
+ if (graph.pageVisible)
+ {
+ graph.fitPages(1);
+ }
+ else
+ {
+ this.get('pageView').funct();
+ }
+ }), null, null, Editor.ctrlKey + '+J');
+ this.addAction('fitTwoPages', mxUtils.bind(this, function()
+ {
+ if (graph.pageVisible)
+ {
+ graph.fitPages(2);
+ }
+ else
+ {
+ this.get('pageView').funct();
+ }
+ }), null, null, Editor.ctrlKey + '+Shift+J');
+ this.addAction('fitPageWidth', mxUtils.bind(this, function()
+ {
+ if (graph.pageVisible)
+ {
+ graph.fitPages(1, true);
+ }
+ else
+ {
+ this.get('pageView').funct();
+ }
+ }));
+ this.put('customZoom', new Action(mxResources.get('custom') + '...', mxUtils.bind(this, function()
+ {
+ var dlg = new FilenameDialog(this.editorUi, parseInt(graph.getView().getScale() * 100), mxResources.get('apply'), mxUtils.bind(this, function(newValue)
+ {
+ var val = parseInt(newValue);
+
+ if (!isNaN(val) && val > 0)
+ {
+ graph.zoomTo(val / 100);
+ }
+ }), mxResources.get('zoom') + ' (%)');
+ this.editorUi.showDialog(dlg.container, 300, 80, true, true);
+ dlg.init();
+ }), null, null, Editor.ctrlKey + '+0'));
+ this.addAction('pageScale...', mxUtils.bind(this, function()
+ {
+ var dlg = new FilenameDialog(this.editorUi, parseInt(graph.pageScale * 100), mxResources.get('apply'), mxUtils.bind(this, function(newValue)
+ {
+ var val = parseInt(newValue);
+
+ if (!isNaN(val) && val > 0)
+ {
+ var change = new ChangePageSetup(ui, null, null, null, val / 100);
+ change.ignoreColor = true;
+ change.ignoreImage = true;
+
+ graph.model.execute(change);
+ }
+ }), mxResources.get('pageScale') + ' (%)');
+ this.editorUi.showDialog(dlg.container, 300, 80, true, true);
+ dlg.init();
+ }));
+
+ // Option actions
+ var action = null;
+ action = this.addAction('grid', function()
+ {
+ graph.setGridEnabled(!graph.isGridEnabled());
+ graph.defaultGridEnabled = graph.isGridEnabled();
+ ui.fireEvent(new mxEventObject('gridEnabledChanged'));
+ }, null, null, Editor.ctrlKey + '+Shift+G');
+ action.setToggleAction(true);
+ action.setSelectedCallback(function() { return graph.isGridEnabled(); });
+ action.setEnabled(false);
+
+ action = this.addAction('guides', function()
+ {
+ graph.graphHandler.guidesEnabled = !graph.graphHandler.guidesEnabled;
+ ui.fireEvent(new mxEventObject('guidesEnabledChanged'));
+ });
+ action.setToggleAction(true);
+ action.setSelectedCallback(function() { return graph.graphHandler.guidesEnabled; });
+ action.setEnabled(false);
+
+ action = this.addAction('tooltips', function()
+ {
+ graph.tooltipHandler.setEnabled(!graph.tooltipHandler.isEnabled());
+ ui.fireEvent(new mxEventObject('tooltipsEnabledChanged'));
+ });
+ action.setToggleAction(true);
+ action.setSelectedCallback(function() { return graph.tooltipHandler.isEnabled(); });
+
+ action = this.addAction('collapseExpand', function()
+ {
+ var change = new ChangePageSetup(ui);
+ change.ignoreColor = true;
+ change.ignoreImage = true;
+ change.foldingEnabled = !graph.foldingEnabled;
+
+ graph.model.execute(change);
+ });
+ action.setToggleAction(true);
+ action.setSelectedCallback(function() { return graph.foldingEnabled; });
+ action.isEnabled = isGraphEnabled;
+ action = this.addAction('pageView', mxUtils.bind(this, function()
+ {
+ ui.setPageVisible(!graph.pageVisible);
+ }));
+ action.setToggleAction(true);
+ action.setSelectedCallback(function() { return graph.pageVisible; });
+ action = this.addAction('connectionArrows', function()
+ {
+ graph.connectionArrowsEnabled = !graph.connectionArrowsEnabled;
+ ui.fireEvent(new mxEventObject('connectionArrowsChanged'));
+ }, null, null, 'Alt+Shift+A');
+ action.setToggleAction(true);
+ action.setSelectedCallback(function() { return graph.connectionArrowsEnabled; });
+ action = this.addAction('connectionPoints', function()
+ {
+ graph.setConnectable(!graph.connectionHandler.isEnabled());
+ ui.fireEvent(new mxEventObject('connectionPointsChanged'));
+ }, null, null, 'Alt+Shift+O');
+ action.setToggleAction(true);
+ action.setSelectedCallback(function() { return graph.connectionHandler.isEnabled(); });
+ action = this.addAction('copyConnect', function()
+ {
+ graph.connectionHandler.setCreateTarget(!graph.connectionHandler.isCreateTarget());
+ ui.fireEvent(new mxEventObject('copyConnectChanged'));
+ });
+ action.setToggleAction(true);
+ action.setSelectedCallback(function() { return graph.connectionHandler.isCreateTarget(); });
+ action.isEnabled = isGraphEnabled;
+ action = this.addAction('autosave', function()
+ {
+ ui.editor.setAutosave(!ui.editor.autosave);
+ });
+ action.setToggleAction(true);
+ action.setSelectedCallback(function() { return ui.editor.autosave; });
+ action.isEnabled = isGraphEnabled;
+ action.visible = false;
+
+ // Help actions
+ this.addAction('help', function()
+ {
+ var ext = '';
+
+ if (mxResources.isLanguageSupported(mxClient.language))
+ {
+ ext = '_' + mxClient.language;
+ }
+
+ graph.openLink(RESOURCES_PATH + '/help' + ext + '.html');
+ });
+
+ var showingAbout = false;
+
+ this.put('about', new Action(mxResources.get('about') + ' Graph Editor...', function()
+ {
+ if (!showingAbout)
+ {
+ ui.showDialog(new AboutDialog(ui).container, 320, 280, true, true, function()
+ {
+ showingAbout = false;
+ });
+
+ showingAbout = true;
+ }
+ }));
+
+ // Font style actions
+ var toggleFontStyle = mxUtils.bind(this, function(key, style, fn, shortcut)
+ {
+ return this.addAction(key, function()
+ {
+ if (fn != null && graph.cellEditor.isContentEditing())
+ {
+ fn();
+ }
+ else
+ {
+ graph.stopEditing(false);
+
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = graph.getEditableCells(graph.getSelectionCells());
+ graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, style, cells);
+
+ // Removes bold and italic tags and CSS styles inside labels
+ if ((style & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ graph.updateLabelElements(cells, function(elt)
+ {
+ elt.style.fontWeight = null;
+
+ if (elt.nodeName == 'B')
+ {
+ graph.replaceElement(elt);
+ }
+ });
+ }
+ else if ((style & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ graph.updateLabelElements(cells, function(elt)
+ {
+ elt.style.fontStyle = null;
+
+ if (elt.nodeName == 'I')
+ {
+ graph.replaceElement(elt);
+ }
+ });
+ }
+ else if ((style & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ graph.updateLabelElements(cells, function(elt)
+ {
+ elt.style.textDecoration = null;
+
+ if (elt.nodeName == 'U')
+ {
+ graph.replaceElement(elt);
+ }
+ });
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (graph.model.getChildCount(cells[i]) == 0)
+ {
+ graph.autoSizeCell(cells[i], false);
+ }
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ }, null, null, shortcut);
+ });
+
+ toggleFontStyle('bold', mxConstants.FONT_BOLD, function() { document.execCommand('bold', false, null); }, Editor.ctrlKey + '+B');
+ toggleFontStyle('italic', mxConstants.FONT_ITALIC, function() { document.execCommand('italic', false, null); }, Editor.ctrlKey + '+I');
+ toggleFontStyle('underline', mxConstants.FONT_UNDERLINE, function() { document.execCommand('underline', false, null); }, Editor.ctrlKey + '+U');
+
+ // Color actions
+ this.addAction('fontColor...', function() { ui.menus.pickColor(mxConstants.STYLE_FONTCOLOR, 'forecolor', '000000'); });
+ this.addAction('strokeColor...', function() { ui.menus.pickColor(mxConstants.STYLE_STROKECOLOR); });
+ this.addAction('fillColor...', function() { ui.menus.pickColor(mxConstants.STYLE_FILLCOLOR); });
+ this.addAction('gradientColor...', function() { ui.menus.pickColor(mxConstants.STYLE_GRADIENTCOLOR); });
+ this.addAction('backgroundColor...', function() { ui.menus.pickColor(mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, 'backcolor'); });
+ this.addAction('borderColor...', function() { ui.menus.pickColor(mxConstants.STYLE_LABEL_BORDERCOLOR); });
+
+ // Format actions
+ this.addAction('removeFormat', function()
+ {
+ if (graph.isEnabled() && !graph.isSelectionEmpty() && !graph.isEditing())
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = graph.getSelectionCells();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ graph.removeTextStyleForCell(cells[i], true);
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ });
+ this.addAction('vertical', function() { ui.menus.toggleStyle(mxConstants.STYLE_HORIZONTAL, true); });
+ this.addAction('shadow', function() { ui.menus.toggleStyle(mxConstants.STYLE_SHADOW); });
+ this.addAction('solid', function()
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ graph.setCellStyles(mxConstants.STYLE_DASHED, null);
+ graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, null);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
+ 'values', [null, null], 'cells', graph.getSelectionCells()));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ });
+ this.addAction('dashed', function()
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ graph.setCellStyles(mxConstants.STYLE_DASHED, '1');
+ graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, null);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
+ 'values', ['1', null], 'cells', graph.getSelectionCells()));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ });
+ this.addAction('dotted', function()
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ graph.setCellStyles(mxConstants.STYLE_DASHED, '1');
+ graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, '1 4');
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
+ 'values', ['1', '1 4'], 'cells', graph.getSelectionCells()));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ });
+ this.addAction('sharp', function()
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ graph.setCellStyles(mxConstants.STYLE_ROUNDED, '0');
+ graph.setCellStyles(mxConstants.STYLE_CURVED, '0');
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
+ 'values', ['0', '0'], 'cells', graph.getSelectionCells()));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ });
+ this.addAction('rounded', function()
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ graph.setCellStyles(mxConstants.STYLE_ROUNDED, '1');
+ graph.setCellStyles(mxConstants.STYLE_CURVED, '0');
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
+ 'values', ['1', '0'], 'cells', graph.getSelectionCells()));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ });
+ this.addAction('toggleRounded', function()
+ {
+ if (!graph.isSelectionEmpty() && graph.isEnabled())
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = graph.getSelectionCells();
+ var style = graph.getCurrentCellStyle(cells[0]);
+ var value = (mxUtils.getValue(style, mxConstants.STYLE_ROUNDED, '0') == '1') ? '0' : '1';
+
+ graph.setCellStyles(mxConstants.STYLE_ROUNDED, value);
+ graph.setCellStyles(mxConstants.STYLE_CURVED, null);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
+ 'values', [value, '0'], 'cells', graph.getSelectionCells()));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ });
+ this.addAction('curved', function()
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ graph.setCellStyles(mxConstants.STYLE_ROUNDED, '0');
+ graph.setCellStyles(mxConstants.STYLE_CURVED, '1');
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
+ 'values', ['0', '1'], 'cells', graph.getSelectionCells()));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ });
+ this.addAction('collapsible', function()
+ {
+ var state = graph.view.getState(graph.getSelectionCell());
+ var value = '1';
+
+ if (state != null && graph.getFoldingImage(state) != null)
+ {
+ value = '0';
+ }
+
+ graph.setCellStyles('collapsible', value);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', ['collapsible'],
+ 'values', [value], 'cells', graph.getSelectionCells()));
+ });
+ this.addAction('editStyle...', mxUtils.bind(this, function()
+ {
+ var cells = graph.getEditableCells(graph.getSelectionCells());
+
+ if (cells != null && cells.length > 0)
+ {
+ var model = graph.getModel();
+
+ var dlg = new TextareaDialog(this.editorUi, mxResources.get('editStyle') + ':',
+ model.getStyle(cells[0]) || '', function(newValue)
+ {
+ if (newValue != null)
+ {
+ graph.setCellStyle(mxUtils.trim(newValue), cells);
+ }
+ }, null, null, 400, 220);
+ this.editorUi.showDialog(dlg.container, 420, 300, true, true);
+ dlg.init();
+ }
+ }), null, null, Editor.ctrlKey + '+E');
+ this.addAction('setAsDefaultStyle', function()
+ {
+ if (graph.isEnabled() && !graph.isSelectionEmpty())
+ {
+ ui.setDefaultStyle(graph.getSelectionCell());
+ }
+ }, null, null, Editor.ctrlKey + '+Shift+D');
+ this.addAction('clearDefaultStyle', function()
+ {
+ if (graph.isEnabled())
+ {
+ ui.clearDefaultStyle();
+ }
+ }, null, null, Editor.ctrlKey + '+Shift+R');
+ this.addAction('addWaypoint', function()
+ {
+ var cell = graph.getSelectionCell();
+
+ if (cell != null && graph.getModel().isEdge(cell))
+ {
+ var handler = editor.graph.selectionCellsHandler.getHandler(cell);
+
+ if (handler instanceof mxEdgeHandler)
+ {
+ var t = graph.view.translate;
+ var s = graph.view.scale;
+ var dx = t.x;
+ var dy = t.y;
+
+ var parent = graph.getModel().getParent(cell);
+ var pgeo = graph.getCellGeometry(parent);
+
+ while (graph.getModel().isVertex(parent) && pgeo != null)
+ {
+ dx += pgeo.x;
+ dy += pgeo.y;
+
+ parent = graph.getModel().getParent(parent);
+ pgeo = graph.getCellGeometry(parent);
+ }
+
+ var x = Math.round(graph.snap(graph.popupMenuHandler.triggerX / s - dx));
+ var y = Math.round(graph.snap(graph.popupMenuHandler.triggerY / s - dy));
+
+ handler.addPointAt(handler.state, x, y);
+ }
+ }
+ });
+ this.addAction('removeWaypoint', function()
+ {
+ // TODO: Action should run with "this" set to action
+ var rmWaypointAction = ui.actions.get('removeWaypoint');
+
+ if (rmWaypointAction.handler != null)
+ {
+ // NOTE: Popupevent handled and action updated in Menus.createPopupMenu
+ rmWaypointAction.handler.removePoint(rmWaypointAction.handler.state, rmWaypointAction.index);
+ }
+ });
+ this.addAction('clearWaypoints', function(evt, trigger)
+ {
+ // Context menu click uses trigger, toolbar menu click uses evt
+ var evt = (trigger != null) ? trigger : evt;
+ var cells = graph.getSelectionCells();
+
+ if (cells != null)
+ {
+ cells = graph.getEditableCells(graph.addAllEdges(cells));
+
+ graph.getModel().beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var cell = cells[i];
+
+ if (graph.getModel().isEdge(cell))
+ {
+ var geo = graph.getCellGeometry(cell);
+
+ // Resets fixed connection point
+ if (trigger != null && mxEvent.isShiftDown(evt))
+ {
+ graph.setCellStyles(mxConstants.STYLE_EXIT_X, null, [cell]);
+ graph.setCellStyles(mxConstants.STYLE_EXIT_Y, null, [cell]);
+ graph.setCellStyles(mxConstants.STYLE_ENTRY_X, null, [cell]);
+ graph.setCellStyles(mxConstants.STYLE_ENTRY_Y, null, [cell]);
+ }
+ else if (geo != null)
+ {
+ geo = geo.clone();
+ geo.points = null;
+ geo.x = 0;
+ geo.y = 0;
+ geo.offset = null;
+ graph.getModel().setGeometry(cell, geo);
+ }
+ }
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ }, null, null, 'Alt+Shift+R');
+ action = this.addAction('subscript', mxUtils.bind(this, function()
+ {
+ if (graph.cellEditor.isContentEditing())
+ {
+ document.execCommand('subscript', false, null);
+ }
+ }), null, null, Editor.ctrlKey + '+,');
+ action = this.addAction('superscript', mxUtils.bind(this, function()
+ {
+ if (graph.cellEditor.isContentEditing())
+ {
+ document.execCommand('superscript', false, null);
+ }
+ }), null, null, Editor.ctrlKey + '+.');
+ action = this.addAction('decreaseFontSize', mxUtils.bind(this, function()
+ {
+ if (!graph.isSelectionEmpty())
+ {
+ var style = graph.getCurrentCellStyle(graph.getSelectionCell());
+ var size = mxUtils.getValue(style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
+ graph.setCellStyles(mxConstants.STYLE_FONTSIZE, Math.max(1, size - 1),
+ graph.getSelectionCells());
+ }
+ }), null, null, Editor.ctrlKey + '+Shift + (Numpad)');
+ action = this.addAction('increaseFontSize', mxUtils.bind(this, function()
+ {
+ if (!graph.isSelectionEmpty())
+ {
+ var style = graph.getCurrentCellStyle(graph.getSelectionCell());
+ var size = mxUtils.getValue(style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
+ graph.setCellStyles(mxConstants.STYLE_FONTSIZE, Math.min(100, size + 1),
+ graph.getSelectionCells());
+ }
+ }), null, null, Editor.ctrlKey + '+Shift - (Numpad)');
+
+ function applyClipPath(cell, clipPath, width, height, graph)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var geo = graph.getCellGeometry(cell);
+
+ if (geo != null && width && height) //Comparing the ratio mostly will fail since it's float
+ {
+ var scale = width / height;
+ geo = geo.clone();
+
+ if (scale > 1)
+ {
+ geo.height = geo.width / scale;
+ }
+ else
+ {
+ geo.width = geo.height * scale;
+ }
+
+ graph.getModel().setGeometry(cell, geo);
+ }
+
+ graph.setCellStyles(mxConstants.STYLE_CLIP_PATH, clipPath, [cell]); //Set/unset clipPath
+ graph.setCellStyles(mxConstants.STYLE_ASPECT, 'fixed', [cell]);
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ };
+
+ this.addAction('image...', function()
+ {
+ if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
+ {
+ var title = mxResources.get('image') + ' (' + mxResources.get('url') + '):';
+ var state = graph.getView().getState(graph.getSelectionCell());
+ var value = '', clipPath = null;
+
+ if (state != null)
+ {
+ value = state.style[mxConstants.STYLE_IMAGE] || value;
+ clipPath = state.style[mxConstants.STYLE_CLIP_PATH] || clipPath;
+ }
+
+ var selectionState = graph.cellEditor.saveSelection();
+
+ ui.showImageDialog(title, value, function(newValue, w, h, clipPath, cW, cH)
+ {
+ // Inserts image into HTML text
+ if (graph.cellEditor.isContentEditing())
+ {
+ graph.cellEditor.restoreSelection(selectionState);
+ graph.insertImage(newValue, w, h);
+ }
+ else
+ {
+ var cells = graph.getSelectionCells();
+
+ if (newValue != null && (newValue.length > 0 || cells.length > 0))
+ {
+ var select = null;
+
+ graph.getModel().beginUpdate();
+ try
+ {
+ // Inserts new cell if no cell is selected
+ if (cells.length == 0)
+ {
+ cells = [graph.insertVertex(graph.getDefaultParent(), null, '', 0, 0, w, h,
+ 'shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;')];
+ var pt = graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry(cells, true));
+ cells[0].geometry.x = pt.x;
+ cells[0].geometry.y = pt.y;
+
+ if (clipPath != null)
+ {
+ applyClipPath(cells[0], clipPath, cW, cH, graph);
+ }
+
+ select = cells;
+ graph.fireEvent(new mxEventObject('cellsInserted', 'cells', select));
+ }
+
+ graph.setCellStyles(mxConstants.STYLE_IMAGE, (newValue.length > 0) ? newValue : null, cells);
+
+ // Sets shape only if not already shape with image (label or image)
+ var style = graph.getCurrentCellStyle(cells[0]);
+
+ if (style[mxConstants.STYLE_SHAPE] != 'image' && style[mxConstants.STYLE_SHAPE] != 'label')
+ {
+ graph.setCellStyles(mxConstants.STYLE_SHAPE, 'image', cells);
+ }
+ else if (newValue.length == 0)
+ {
+ graph.setCellStyles(mxConstants.STYLE_SHAPE, null, cells);
+ }
+
+ if (clipPath == null)
+ {
+ graph.setCellStyles(mxConstants.STYLE_CLIP_PATH, null, cells); //Reset clip path
+ }
+
+ if (w != null && h != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var cell = cells[i];
+
+ if (graph.getCurrentCellStyle(cell)['expand'] != '0')
+ {
+ var geo = graph.getModel().getGeometry(cell);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ geo.width = w;
+ geo.height = h;
+ graph.getModel().setGeometry(cell, geo);
+ }
+ }
+
+ if (clipPath != null)
+ {
+ applyClipPath(cell, clipPath, cW, cH, graph);
+ }
+ }
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+
+ if (select != null)
+ {
+ graph.setSelectionCells(select);
+ graph.scrollCellToVisible(select[0]);
+ }
+ }
+ }
+ }, graph.cellEditor.isContentEditing(), !graph.cellEditor.isContentEditing(), true, clipPath);
+ }
+ }).isEnabled = isGraphEnabled;
+
+ this.addAction('crop...', function()
+ {
+ var cell = graph.getSelectionCell();
+
+ if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()) && cell != null)
+ {
+ var style = graph.getCurrentCellStyle(cell);
+
+ var value = style[mxConstants.STYLE_IMAGE], shape = style[mxConstants.STYLE_SHAPE];
+
+ if (!value || shape != 'image')
+ {
+ return; //Can only process an existing image
+ }
+
+ var dlg = new CropImageDialog(ui, value, style[mxConstants.STYLE_CLIP_PATH], function(clipPath, width, height)
+ {
+ applyClipPath(cell, clipPath, width, height, graph);
+ });
+
+ ui.showDialog(dlg.container, 300, 390, true, true);
+ }
+ }).isEnabled = isGraphEnabled;
+ action = this.addAction('layers', mxUtils.bind(this, function()
+ {
+ if (this.layersWindow == null)
+ {
+ // LATER: Check outline window for initial placement
+ this.layersWindow = new LayersWindow(ui, document.body.offsetWidth - 280, 120, 212, 200);
+ this.layersWindow.window.addListener('show', mxUtils.bind(this, function()
+ {
+ ui.fireEvent(new mxEventObject('layers'));
+ }));
+ this.layersWindow.window.addListener('hide', function()
+ {
+ ui.fireEvent(new mxEventObject('layers'));
+ });
+ this.layersWindow.window.setVisible(true);
+ ui.fireEvent(new mxEventObject('layers'));
+
+ this.layersWindow.init();
+ }
+ else
+ {
+ this.layersWindow.window.setVisible(!this.layersWindow.window.isVisible());
+ }
+ }), null, null, Editor.ctrlKey + '+Shift+L');
+ action.setToggleAction(true);
+ action.setSelectedCallback(mxUtils.bind(this, function() { return this.layersWindow != null && this.layersWindow.window.isVisible(); }));
+ action = this.addAction('format', mxUtils.bind(this, function()
+ {
+ ui.toggleFormatPanel();
+ }), null, null, Editor.ctrlKey + '+Shift+P');
+ action.setToggleAction(true);
+ action.setSelectedCallback(mxUtils.bind(this, function() { return ui.isFormatPanelVisible(); }));
+ action = this.addAction('outline', mxUtils.bind(this, function()
+ {
+ if (this.outlineWindow == null)
+ {
+ // LATER: Check layers window for initial placement
+ this.outlineWindow = new OutlineWindow(ui, document.body.offsetWidth - 260, 100, 180, 180);
+ this.outlineWindow.window.addListener('show', mxUtils.bind(this, function()
+ {
+ ui.fireEvent(new mxEventObject('outline'));
+ }));
+ this.outlineWindow.window.addListener('hide', function()
+ {
+ ui.fireEvent(new mxEventObject('outline'));
+ });
+ this.outlineWindow.window.setVisible(true);
+ ui.fireEvent(new mxEventObject('outline'));
+ }
+ else
+ {
+ this.outlineWindow.window.setVisible(!this.outlineWindow.window.isVisible());
+ }
+ }), null, null, Editor.ctrlKey + '+Shift+O');
+
+ action.setToggleAction(true);
+ action.setSelectedCallback(mxUtils.bind(this, function() { return this.outlineWindow != null && this.outlineWindow.window.isVisible(); }));
+
+ this.addAction('editConnectionPoints...', function()
+ {
+ var cell = graph.getSelectionCell();
+
+ if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()) && cell != null)
+ {
+ var dlg = new ConnectionPointsDialog(ui, cell);
+ ui.showDialog(dlg.container, 350, 450, true, false, function()
+ {
+ dlg.destroy();
+ });
+ dlg.init();
+ }
+ }, null, null, 'Alt+Shift+Q').isEnabled = isGraphEnabled;
+};
+
+/**
+ * Registers the given action under the given name.
+ */
+Actions.prototype.addAction = function(key, funct, enabled, iconCls, shortcut)
+{
+ var title;
+
+ if (key.substring(key.length - 3) == '...')
+ {
+ key = key.substring(0, key.length - 3);
+ title = mxResources.get(key) + '...';
+ }
+ else
+ {
+ title = mxResources.get(key);
+ }
+
+ return this.put(key, new Action(title, funct, enabled, iconCls, shortcut));
+};
+
+/**
+ * Registers the given action under the given name.
+ */
+Actions.prototype.put = function(name, action)
+{
+ this.actions[name] = action;
+
+ return action;
+};
+
+/**
+ * Returns the action for the given name or null if no such action exists.
+ */
+Actions.prototype.get = function(name)
+{
+ return this.actions[name];
+};
+
+/**
+ * Constructs a new action for the given parameters.
+ */
+function Action(label, funct, enabled, iconCls, shortcut)
+{
+ mxEventSource.call(this);
+ this.label = label;
+ this.funct = this.createFunction(funct);
+ this.enabled = (enabled != null) ? enabled : true;
+ this.iconCls = iconCls;
+ this.shortcut = shortcut;
+ this.visible = true;
+};
+
+// Action inherits from mxEventSource
+mxUtils.extend(Action, mxEventSource);
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.createFunction = function(funct)
+{
+ return funct;
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.setEnabled = function(value)
+{
+ if (this.enabled != value)
+ {
+ this.enabled = value;
+ this.fireEvent(new mxEventObject('stateChanged'));
+ }
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.setToggleAction = function(value)
+{
+ this.toggleAction = value;
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.setSelectedCallback = function(funct)
+{
+ this.selectedCallback = funct;
+};
+
+/**
+ * Sets the enabled state of the action and fires a stateChanged event.
+ */
+Action.prototype.isSelected = function()
+{
+ return this.selectedCallback();
+};
diff --git a/oaweb/public/cherry/drawio/Dialogs.js b/oaweb/public/cherry/drawio/Dialogs.js
new file mode 100644
index 0000000..a2f67cd
--- /dev/null
+++ b/oaweb/public/cherry/drawio/Dialogs.js
@@ -0,0 +1,2429 @@
+/**
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new open dialog.
+ */
+var OpenDialog = function() {
+ var iframe = document.createElement('iframe');
+ iframe.style.backgroundColor = 'transparent';
+ iframe.allowTransparency = 'true';
+ iframe.style.borderStyle = 'none';
+ iframe.style.borderWidth = '0px';
+ iframe.style.overflow = 'hidden';
+ iframe.style.maxWidth = '100%';
+ iframe.frameBorder = '0';
+
+ var dx = 0;
+ iframe.setAttribute('width', (((Editor.useLocalStorage) ? 640 : 320) + dx) + 'px');
+ iframe.setAttribute('height', (((Editor.useLocalStorage) ? 480 : 220) + dx) + 'px');
+ iframe.setAttribute('src', OPEN_FORM);
+ console.log(iframe)
+
+ this.container = iframe;
+};
+
+/**
+ * Constructs a new color dialog.
+ */
+var ColorDialog = function(editorUi, color, apply, cancelFn) {
+ this.editorUi = editorUi;
+
+ var input = document.createElement('input');
+ input.style.marginBottom = '10px';
+
+ // Required for picker to render in IE
+ if (mxClient.IS_IE) {
+ input.style.marginTop = '10px';
+ document.body.appendChild(input);
+ }
+
+ var applyFunction = (apply != null) ? apply : this.createApplyFunction();
+
+ function doApply() {
+ var color = input.value;
+
+ // Blocks any non-alphabetic chars in colors
+ if (/(^#?[a-zA-Z0-9]*$)/.test(color)) {
+ if (color != 'none' && color.charAt(0) != '#') {
+ color = '#' + color;
+ }
+
+ ColorDialog.addRecentColor((color != 'none') ? color.substring(1) : color, 12);
+ applyFunction(color);
+ editorUi.hideDialog();
+ }
+ else {
+ editorUi.handleError({ message: mxResources.get('invalidInput') });
+ }
+ };
+
+ this.init = function() {
+ if (!mxClient.IS_TOUCH) {
+ input.focus();
+ }
+ };
+
+ var picker = new mxJSColor.color(input);
+ picker.pickerOnfocus = false;
+ picker.showPicker();
+
+ var div = document.createElement('div');
+ mxJSColor.picker.box.style.position = 'relative';
+ mxJSColor.picker.box.style.width = '230px';
+ mxJSColor.picker.box.style.height = '100px';
+ mxJSColor.picker.box.style.paddingBottom = '10px';
+ div.appendChild(mxJSColor.picker.box);
+
+ var center = document.createElement('center');
+
+ function createRecentColorTable() {
+ var table = addPresets((ColorDialog.recentColors.length == 0) ? ['FFFFFF'] :
+ ColorDialog.recentColors, 11, 'FFFFFF', true);
+ table.style.marginBottom = '8px';
+
+ return table;
+ };
+
+ var addPresets = mxUtils.bind(this, function(presets, rowLength, defaultColor, addResetOption) {
+ rowLength = (rowLength != null) ? rowLength : 12;
+ var table = document.createElement('table');
+ table.style.borderCollapse = 'collapse';
+ table.setAttribute('cellspacing', '0');
+ table.style.marginBottom = '20px';
+ table.style.cellSpacing = '0px';
+ table.style.marginLeft = '1px';
+ var tbody = document.createElement('tbody');
+ table.appendChild(tbody);
+
+ var rows = presets.length / rowLength;
+
+ for (var row = 0; row < rows; row++) {
+ var tr = document.createElement('tr');
+
+ for (var i = 0; i < rowLength; i++) {
+ (mxUtils.bind(this, function(clr) {
+ var td = document.createElement('td');
+ td.style.border = '0px solid black';
+ td.style.padding = '0px';
+ td.style.width = '16px';
+ td.style.height = '16px';
+
+ if (clr == null) {
+ clr = defaultColor;
+ }
+
+ if (clr != null) {
+ td.style.borderWidth = '1px';
+
+ if (clr == 'none') {
+ td.style.background = 'url(\'' + Dialog.prototype.noColorImage + '\')';
+ }
+ else {
+ td.style.backgroundColor = '#' + clr;
+ }
+
+ var name = this.colorNames[clr.toUpperCase()];
+
+ if (name != null) {
+ td.setAttribute('title', name);
+ }
+ }
+
+ tr.appendChild(td);
+
+ if (clr != null) {
+ td.style.cursor = 'pointer';
+
+ mxEvent.addListener(td, 'click', function() {
+ if (clr == 'none') {
+ picker.fromString('ffffff');
+ input.value = 'none';
+ }
+ else {
+ picker.fromString(clr);
+ }
+ });
+
+ mxEvent.addListener(td, 'dblclick', doApply);
+ }
+ }))(presets[row * rowLength + i]);
+ }
+
+ tbody.appendChild(tr);
+ }
+
+ if (addResetOption) {
+ var td = document.createElement('td');
+ td.setAttribute('title', mxResources.get('reset'));
+ td.style.border = '1px solid black';
+ td.style.padding = '0px';
+ td.style.width = '16px';
+ td.style.height = '16px';
+ td.style.backgroundImage = 'url(\'' + Dialog.prototype.closeImage + '\')';
+ td.style.backgroundPosition = 'center center';
+ td.style.backgroundRepeat = 'no-repeat';
+ td.style.cursor = 'pointer';
+
+ tr.appendChild(td);
+
+ mxEvent.addListener(td, 'click', function() {
+ ColorDialog.resetRecentColors();
+ table.parentNode.replaceChild(createRecentColorTable(), table);
+ });
+ }
+
+ center.appendChild(table);
+
+ return table;
+ });
+
+ div.appendChild(input);
+
+ if (!mxClient.IS_IE && !mxClient.IS_IE11) {
+ input.style.width = '182px';
+
+ var clrInput = document.createElement('input');
+ clrInput.setAttribute('type', 'color');
+ clrInput.style.position = 'relative';
+ clrInput.style.visibility = 'hidden';
+ clrInput.style.top = '10px';
+ clrInput.style.width = '0px';
+ clrInput.style.height = '0px';
+ clrInput.style.border = 'none';
+
+ div.style.whiteSpace = 'nowrap';
+ div.appendChild(clrInput);
+
+ var dropperBtn = mxUtils.button('', function() {
+ // LATER: Check if clrInput is expanded
+ if (document.activeElement == clrInput) {
+ input.focus();
+ }
+ else {
+ clrInput.value = '#' + input.value;
+ clrInput.click();
+ }
+ });
+
+ var dropper = document.createElement('img');
+ dropper.src = Editor.colorDropperImage;
+ dropper.className = 'geAdaptiveAsset';
+ dropper.style.position = 'relative';
+ dropper.style.verticalAlign = 'middle';
+ dropper.style.width = 'auto';
+ dropper.style.height = '14px';
+
+ dropperBtn.appendChild(dropper);
+ div.appendChild(dropperBtn);
+
+ mxEvent.addListener(clrInput, 'change', function() {
+ picker.fromString(clrInput.value.substring(1));
+ });
+ }
+ else {
+ input.style.width = '216px';
+ }
+
+ mxUtils.br(div);
+
+ // Adds recent colors
+ createRecentColorTable();
+
+ // Adds presets
+ var table = addPresets(this.presetColors);
+ table.style.marginBottom = '8px';
+ table = addPresets(this.defaultColors);
+ table.style.marginBottom = '16px';
+
+ div.appendChild(center);
+
+ var buttons = document.createElement('div');
+ buttons.style.textAlign = 'right';
+ buttons.style.whiteSpace = 'nowrap';
+
+ var cancelBtn = mxUtils.button(mxResources.get('cancel'), function() {
+ editorUi.hideDialog();
+
+ if (cancelFn != null) {
+ cancelFn();
+ }
+ });
+ cancelBtn.className = 'geBtn';
+
+ if (editorUi.editor.cancelFirst) {
+ buttons.appendChild(cancelBtn);
+ }
+
+ var applyBtn = mxUtils.button(mxResources.get('apply'), doApply);
+ applyBtn.className = 'geBtn gePrimaryBtn';
+ buttons.appendChild(applyBtn);
+
+ if (!editorUi.editor.cancelFirst) {
+ buttons.appendChild(cancelBtn);
+ }
+
+ if (color != null) {
+ if (color == 'none') {
+ picker.fromString('ffffff');
+ input.value = 'none';
+ }
+ else {
+ picker.fromString(color);
+ }
+ }
+
+ div.appendChild(buttons);
+ this.picker = picker;
+ this.colorInput = input;
+
+ // LATER: Only fires if input if focused, should always
+ // fire if this dialog is showing.
+ mxEvent.addListener(div, 'keydown', function(e) {
+ if (e.keyCode == 27) {
+ editorUi.hideDialog();
+
+ if (cancelFn != null) {
+ cancelFn();
+ }
+
+ mxEvent.consume(e);
+ }
+ });
+
+ this.container = div;
+};
+
+/**
+ * Creates function to apply value
+ */
+ColorDialog.prototype.presetColors = ['E6D0DE', 'CDA2BE', 'B5739D', 'E1D5E7', 'C3ABD0', 'A680B8', 'D4E1F5', 'A9C4EB', '7EA6E0', 'D5E8D4', '9AC7BF', '67AB9F', 'D5E8D4', 'B9E0A5', '97D077', 'FFF2CC', 'FFE599', 'FFD966', 'FFF4C3', 'FFCE9F', 'FFB570', 'F8CECC', 'F19C99', 'EA6B66'];
+
+/**
+ * Creates function to apply value
+ */
+ColorDialog.prototype.colorNames = {};
+
+/**
+ * Creates function to apply value
+ */
+ColorDialog.prototype.defaultColors = ['none', 'FFFFFF', 'E6E6E6', 'CCCCCC', 'B3B3B3', '999999', '808080', '666666', '4D4D4D', '333333', '1A1A1A', '000000', 'FFCCCC', 'FFE6CC', 'FFFFCC', 'E6FFCC', 'CCFFCC', 'CCFFE6', 'CCFFFF', 'CCE5FF', 'CCCCFF', 'E5CCFF', 'FFCCFF', 'FFCCE6',
+ 'FF9999', 'FFCC99', 'FFFF99', 'CCFF99', '99FF99', '99FFCC', '99FFFF', '99CCFF', '9999FF', 'CC99FF', 'FF99FF', 'FF99CC', 'FF6666', 'FFB366', 'FFFF66', 'B3FF66', '66FF66', '66FFB3', '66FFFF', '66B2FF', '6666FF', 'B266FF', 'FF66FF', 'FF66B3', 'FF3333', 'FF9933', 'FFFF33',
+ '99FF33', '33FF33', '33FF99', '33FFFF', '3399FF', '3333FF', '9933FF', 'FF33FF', 'FF3399', 'FF0000', 'FF8000', 'FFFF00', '80FF00', '00FF00', '00FF80', '00FFFF', '007FFF', '0000FF', '7F00FF', 'FF00FF', 'FF0080', 'CC0000', 'CC6600', 'CCCC00', '66CC00', '00CC00', '00CC66',
+ '00CCCC', '0066CC', '0000CC', '6600CC', 'CC00CC', 'CC0066', '990000', '994C00', '999900', '4D9900', '009900', '00994D', '009999', '004C99', '000099', '4C0099', '990099', '99004D', '660000', '663300', '666600', '336600', '006600', '006633', '006666', '003366', '000066',
+ '330066', '660066', '660033', '330000', '331A00', '333300', '1A3300', '003300', '00331A', '003333', '001933', '000033', '190033', '330033', '33001A'];
+
+/**
+ * Creates function to apply value
+ */
+ColorDialog.prototype.createApplyFunction = function() {
+ return mxUtils.bind(this, function(color) {
+ var graph = this.editorUi.editor.graph;
+
+ graph.getModel().beginUpdate();
+ try {
+ graph.setCellStyles(this.currentColorKey, color);
+ this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', [this.currentColorKey],
+ 'values', [color], 'cells', graph.getSelectionCells()));
+ }
+ finally {
+ graph.getModel().endUpdate();
+ }
+ });
+};
+
+/**
+ *
+ */
+ColorDialog.recentColors = [];
+
+/**
+ * Adds recent color for later use.
+ */
+ColorDialog.addRecentColor = function(color, max) {
+ if (color != null) {
+ mxUtils.remove(color, ColorDialog.recentColors);
+ ColorDialog.recentColors.splice(0, 0, color);
+
+ if (ColorDialog.recentColors.length >= max) {
+ ColorDialog.recentColors.pop();
+ }
+ }
+};
+
+/**
+ * Adds recent color for later use.
+ */
+ColorDialog.resetRecentColors = function() {
+ ColorDialog.recentColors = [];
+};
+
+/**
+ * Constructs a new about dialog.
+ */
+var AboutDialog = function(editorUi) {
+ var div = document.createElement('div');
+ div.setAttribute('align', 'center');
+ var h3 = document.createElement('h3');
+ mxUtils.write(h3, mxResources.get('about') + ' GraphEditor');
+ div.appendChild(h3);
+ var img = document.createElement('img');
+ img.style.border = '0px';
+ img.setAttribute('width', '176');
+ img.setAttribute('width', '151');
+ img.setAttribute('src', IMAGE_PATH + '/logo.png');
+ div.appendChild(img);
+ mxUtils.br(div);
+ mxUtils.write(div, 'Powered by mxGraph ' + mxClient.VERSION);
+ mxUtils.br(div);
+ var link = document.createElement('a');
+ link.setAttribute('href', 'http://www.jgraph.com/');
+ link.setAttribute('target', '_blank');
+ mxUtils.write(link, 'www.jgraph.com');
+ div.appendChild(link);
+ mxUtils.br(div);
+ mxUtils.br(div);
+ var closeBtn = mxUtils.button(mxResources.get('close'), function() {
+ editorUi.hideDialog();
+ });
+ closeBtn.className = 'geBtn gePrimaryBtn';
+ div.appendChild(closeBtn);
+
+ this.container = div;
+};
+
+/**
+ * Constructs a new textarea dialog.
+ */
+var TextareaDialog = function(editorUi, title, url, fn, cancelFn, cancelTitle, w, h,
+ addButtons, noHide, noWrap, applyTitle, helpLink, customButtons, header) {
+ w = (w != null) ? w : 300;
+ h = (h != null) ? h : 120;
+ noHide = (noHide != null) ? noHide : false;
+
+ var div = document.createElement('div');
+ div.style.position = 'absolute';
+ div.style.top = '20px';
+ div.style.bottom = '20px';
+ div.style.left = '20px';
+ div.style.right = '20px';
+
+ var top = document.createElement('div');
+
+ top.style.position = 'absolute';
+ top.style.left = '0px';
+ top.style.right = '0px';
+
+ var main = top.cloneNode(false);
+ var buttons = top.cloneNode(false);
+
+ top.style.top = '0px';
+ top.style.height = '20px';
+ main.style.top = '20px';
+ main.style.bottom = '64px';
+ buttons.style.bottom = '0px';
+ buttons.style.height = '60px';
+ buttons.style.textAlign = 'center';
+
+ mxUtils.write(top, title);
+
+ div.appendChild(top);
+ div.appendChild(main);
+ div.appendChild(buttons);
+
+ if (header != null) {
+ top.appendChild(header);
+ }
+
+ var nameInput = document.createElement('textarea');
+
+ if (noWrap) {
+ nameInput.setAttribute('wrap', 'off');
+ }
+
+ nameInput.setAttribute('spellcheck', 'false');
+ nameInput.setAttribute('autocorrect', 'off');
+ nameInput.setAttribute('autocomplete', 'off');
+ nameInput.setAttribute('autocapitalize', 'off');
+
+ mxUtils.write(nameInput, url || '');
+ nameInput.style.resize = 'none';
+ nameInput.style.outline = 'none';
+ nameInput.style.position = 'absolute';
+ nameInput.style.boxSizing = 'border-box';
+ nameInput.style.top = '0px';
+ nameInput.style.left = '0px';
+ nameInput.style.height = '100%';
+ nameInput.style.width = '100%';
+
+ this.textarea = nameInput;
+
+ this.init = function() {
+ nameInput.focus();
+ nameInput.scrollTop = 0;
+ };
+
+ main.appendChild(nameInput);
+
+ if (helpLink != null) {
+ var helpBtn = mxUtils.button(mxResources.get('help'), function() {
+ editorUi.editor.graph.openLink(helpLink);
+ });
+ helpBtn.className = 'geBtn';
+
+ buttons.appendChild(helpBtn);
+ }
+
+ if (customButtons != null) {
+ for (var i = 0; i < customButtons.length; i++) {
+ (function(label, fn, title) {
+ var customBtn = mxUtils.button(label, function(e) {
+ fn(e, nameInput);
+ });
+
+ if (title != null) {
+ customBtn.setAttribute('title', title);
+ }
+
+ customBtn.className = 'geBtn';
+
+ buttons.appendChild(customBtn);
+ })(customButtons[i][0], customButtons[i][1], customButtons[i][2]);
+ }
+ }
+
+ var cancelBtn = mxUtils.button(cancelTitle || mxResources.get('cancel'), function() {
+ editorUi.hideDialog();
+
+ if (cancelFn != null) {
+ cancelFn();
+ }
+ });
+
+ cancelBtn.setAttribute('title', 'Escape');
+ cancelBtn.className = 'geBtn';
+
+ if (editorUi.editor.cancelFirst) {
+ buttons.appendChild(cancelBtn);
+ }
+
+ if (addButtons != null) {
+ addButtons(buttons, nameInput);
+ }
+
+ if (fn != null) {
+ var genericBtn = mxUtils.button(applyTitle || mxResources.get('apply'), function() {
+ if (!noHide) {
+ editorUi.hideDialog();
+ }
+
+ fn(nameInput.value);
+ });
+
+ genericBtn.setAttribute('title', 'Ctrl+Enter');
+ genericBtn.className = 'geBtn gePrimaryBtn';
+ buttons.appendChild(genericBtn);
+
+ mxEvent.addListener(nameInput, 'keypress', function(e) {
+ if (e.keyCode == 13 && mxEvent.isControlDown(e)) {
+ genericBtn.click();
+ }
+ });
+ }
+
+ if (!editorUi.editor.cancelFirst) {
+ buttons.appendChild(cancelBtn);
+ }
+
+ this.container = div;
+};
+
+/**
+ * Constructs a new edit file dialog.
+ */
+var EditDiagramDialog = function(editorUi) {
+ var div = document.createElement('div');
+ div.style.textAlign = 'right';
+ var textarea = document.createElement('textarea');
+ textarea.setAttribute('wrap', 'off');
+ textarea.setAttribute('spellcheck', 'false');
+ textarea.setAttribute('autocorrect', 'off');
+ textarea.setAttribute('autocomplete', 'off');
+ textarea.setAttribute('autocapitalize', 'off');
+ textarea.style.overflow = 'auto';
+ textarea.style.resize = 'none';
+ textarea.style.width = '600px';
+ textarea.style.height = '360px';
+ textarea.style.marginBottom = '16px';
+
+ textarea.value = mxUtils.getPrettyXml(editorUi.editor.getGraphXml());
+ div.appendChild(textarea);
+
+ this.init = function() {
+ textarea.focus();
+ };
+
+ // Enables dropping files
+ if (Graph.fileSupport) {
+ function handleDrop(evt) {
+ evt.stopPropagation();
+ evt.preventDefault();
+
+ if (evt.dataTransfer.files.length > 0) {
+ var file = evt.dataTransfer.files[0];
+ var reader = new FileReader();
+
+ reader.onload = function(e) {
+ textarea.value = e.target.result;
+ };
+
+ reader.readAsText(file);
+ }
+ else {
+ textarea.value = editorUi.extractGraphModelFromEvent(evt);
+ }
+ };
+
+ function handleDragOver(evt) {
+ evt.stopPropagation();
+ evt.preventDefault();
+ };
+
+ // Setup the dnd listeners.
+ textarea.addEventListener('dragover', handleDragOver, false);
+ textarea.addEventListener('drop', handleDrop, false);
+ }
+
+ var cancelBtn = mxUtils.button(mxResources.get('cancel'), function() {
+ editorUi.hideDialog();
+ });
+ cancelBtn.className = 'geBtn';
+
+ if (editorUi.editor.cancelFirst) {
+ div.appendChild(cancelBtn);
+ }
+
+ var select = document.createElement('select');
+ select.style.width = '180px';
+ select.className = 'geBtn';
+
+ if (editorUi.editor.graph.isEnabled()) {
+ var replaceOption = document.createElement('option');
+ replaceOption.setAttribute('value', 'replace');
+ mxUtils.write(replaceOption, mxResources.get('replaceExistingDrawing'));
+ select.appendChild(replaceOption);
+ }
+
+ var newOption = document.createElement('option');
+ newOption.setAttribute('value', 'new');
+ mxUtils.write(newOption, mxResources.get('openInNewWindow'));
+
+ if (EditDiagramDialog.showNewWindowOption) {
+ select.appendChild(newOption);
+ }
+
+ if (editorUi.editor.graph.isEnabled()) {
+ var importOption = document.createElement('option');
+ importOption.setAttribute('value', 'import');
+ mxUtils.write(importOption, mxResources.get('addToExistingDrawing'));
+ select.appendChild(importOption);
+ }
+
+ div.appendChild(select);
+
+ var okBtn = mxUtils.button(mxResources.get('ok'), function() {
+ // Removes all illegal control characters before parsing
+ var data = Graph.zapGremlins(mxUtils.trim(textarea.value));
+ var error = null;
+
+ if (select.value == 'new') {
+ editorUi.hideDialog();
+ editorUi.editor.editAsNew(data);
+ }
+ else if (select.value == 'replace') {
+ editorUi.editor.graph.model.beginUpdate();
+ try {
+ editorUi.editor.setGraphXml(mxUtils.parseXml(data).documentElement);
+ // LATER: Why is hideDialog between begin-/endUpdate faster?
+ editorUi.hideDialog();
+ }
+ catch (e) {
+ error = e;
+ }
+ finally {
+ editorUi.editor.graph.model.endUpdate();
+ }
+ }
+ else if (select.value == 'import') {
+ editorUi.editor.graph.model.beginUpdate();
+ try {
+ var doc = mxUtils.parseXml(data);
+ var model = new mxGraphModel();
+ var codec = new mxCodec(doc);
+ codec.decode(doc.documentElement, model);
+
+ var children = model.getChildren(model.getChildAt(model.getRoot(), 0));
+ editorUi.editor.graph.setSelectionCells(editorUi.editor.graph.importCells(children));
+
+ // LATER: Why is hideDialog between begin-/endUpdate faster?
+ editorUi.hideDialog();
+ }
+ catch (e) {
+ error = e;
+ }
+ finally {
+ editorUi.editor.graph.model.endUpdate();
+ }
+ }
+
+ if (error != null) {
+ mxUtils.alert(error.message);
+ }
+ });
+ okBtn.className = 'geBtn gePrimaryBtn';
+ div.appendChild(okBtn);
+
+ if (!editorUi.editor.cancelFirst) {
+ div.appendChild(cancelBtn);
+ }
+
+ this.container = div;
+};
+
+/**
+ *
+ */
+EditDiagramDialog.showNewWindowOption = true;
+
+/**
+ * Constructs a new export dialog.
+ */
+var ExportDialog = function(editorUi) {
+ var graph = editorUi.editor.graph;
+ var bounds = graph.getGraphBounds();
+ var scale = graph.view.scale;
+
+ var width = Math.ceil(bounds.width / scale);
+ var height = Math.ceil(bounds.height / scale);
+
+ var row, td;
+
+ var table = document.createElement('table');
+ var tbody = document.createElement('tbody');
+ table.setAttribute('cellpadding', (mxClient.IS_SF) ? '0' : '2');
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ td.style.width = '100px';
+ mxUtils.write(td, mxResources.get('filename') + ':');
+
+ row.appendChild(td);
+
+ var nameInput = document.createElement('input');
+ nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
+ nameInput.style.width = '180px';
+
+ td = document.createElement('td');
+ td.appendChild(nameInput);
+ row.appendChild(td);
+
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ mxUtils.write(td, mxResources.get('format') + ':');
+
+ row.appendChild(td);
+
+ var imageFormatSelect = document.createElement('select');
+ imageFormatSelect.style.width = '180px';
+
+ var pngOption = document.createElement('option');
+ pngOption.setAttribute('value', 'png');
+ mxUtils.write(pngOption, mxResources.get('formatPng'));
+ imageFormatSelect.appendChild(pngOption);
+
+ var gifOption = document.createElement('option');
+
+ if (ExportDialog.showGifOption) {
+ gifOption.setAttribute('value', 'gif');
+ mxUtils.write(gifOption, mxResources.get('formatGif'));
+ imageFormatSelect.appendChild(gifOption);
+ }
+
+ var jpgOption = document.createElement('option');
+ jpgOption.setAttribute('value', 'jpg');
+ mxUtils.write(jpgOption, mxResources.get('formatJpg'));
+ imageFormatSelect.appendChild(jpgOption);
+
+ if (!editorUi.printPdfExport) {
+ var pdfOption = document.createElement('option');
+ pdfOption.setAttribute('value', 'pdf');
+ mxUtils.write(pdfOption, mxResources.get('formatPdf'));
+ imageFormatSelect.appendChild(pdfOption);
+ }
+
+ var svgOption = document.createElement('option');
+ svgOption.setAttribute('value', 'svg');
+ mxUtils.write(svgOption, mxResources.get('formatSvg'));
+ imageFormatSelect.appendChild(svgOption);
+
+ if (ExportDialog.showXmlOption) {
+ var xmlOption = document.createElement('option');
+ xmlOption.setAttribute('value', 'xml');
+ mxUtils.write(xmlOption, mxResources.get('formatXml'));
+ imageFormatSelect.appendChild(xmlOption);
+ }
+
+ td = document.createElement('td');
+ td.appendChild(imageFormatSelect);
+ row.appendChild(td);
+
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ mxUtils.write(td, mxResources.get('zoom') + ' (%):');
+
+ row.appendChild(td);
+
+ var zoomInput = document.createElement('input');
+ zoomInput.setAttribute('type', 'number');
+ zoomInput.setAttribute('value', '100');
+ zoomInput.style.width = '180px';
+
+ td = document.createElement('td');
+ td.appendChild(zoomInput);
+ row.appendChild(td);
+
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ mxUtils.write(td, mxResources.get('width') + ':');
+
+ row.appendChild(td);
+
+ var widthInput = document.createElement('input');
+ widthInput.setAttribute('value', width);
+ widthInput.style.width = '180px';
+
+ td = document.createElement('td');
+ td.appendChild(widthInput);
+ row.appendChild(td);
+
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ mxUtils.write(td, mxResources.get('height') + ':');
+
+ row.appendChild(td);
+
+ var heightInput = document.createElement('input');
+ heightInput.setAttribute('value', height);
+ heightInput.style.width = '180px';
+
+ td = document.createElement('td');
+ td.appendChild(heightInput);
+ row.appendChild(td);
+
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ mxUtils.write(td, mxResources.get('dpi') + ':');
+
+ row.appendChild(td);
+
+ var dpiSelect = document.createElement('select');
+ dpiSelect.style.width = '180px';
+
+ var dpi100Option = document.createElement('option');
+ dpi100Option.setAttribute('value', '100');
+ mxUtils.write(dpi100Option, '100dpi');
+ dpiSelect.appendChild(dpi100Option);
+
+ var dpi200Option = document.createElement('option');
+ dpi200Option.setAttribute('value', '200');
+ mxUtils.write(dpi200Option, '200dpi');
+ dpiSelect.appendChild(dpi200Option);
+
+ var dpi300Option = document.createElement('option');
+ dpi300Option.setAttribute('value', '300');
+ mxUtils.write(dpi300Option, '300dpi');
+ dpiSelect.appendChild(dpi300Option);
+
+ var dpi400Option = document.createElement('option');
+ dpi400Option.setAttribute('value', '400');
+ mxUtils.write(dpi400Option, '400dpi');
+ dpiSelect.appendChild(dpi400Option);
+
+ var dpiCustOption = document.createElement('option');
+ dpiCustOption.setAttribute('value', 'custom');
+ mxUtils.write(dpiCustOption, mxResources.get('custom'));
+ dpiSelect.appendChild(dpiCustOption);
+
+ var customDpi = document.createElement('input');
+ customDpi.style.width = '180px';
+ customDpi.style.display = 'none';
+ customDpi.setAttribute('value', '100');
+ customDpi.setAttribute('type', 'number');
+ customDpi.setAttribute('min', '50');
+ customDpi.setAttribute('step', '50');
+
+ var zoomUserChanged = false;
+
+ mxEvent.addListener(dpiSelect, 'change', function() {
+ if (this.value == 'custom') {
+ this.style.display = 'none';
+ customDpi.style.display = '';
+ customDpi.focus();
+ }
+ else {
+ customDpi.value = this.value;
+
+ if (!zoomUserChanged) {
+ zoomInput.value = this.value;
+ }
+ }
+ });
+
+ mxEvent.addListener(customDpi, 'change', function() {
+ var dpi = parseInt(customDpi.value);
+
+ if (isNaN(dpi) || dpi <= 0) {
+ customDpi.style.backgroundColor = 'red';
+ }
+ else {
+ customDpi.style.backgroundColor = '';
+
+ if (!zoomUserChanged) {
+ zoomInput.value = dpi;
+ }
+ }
+ });
+
+ td = document.createElement('td');
+ td.appendChild(dpiSelect);
+ td.appendChild(customDpi);
+ row.appendChild(td);
+
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ mxUtils.write(td, mxResources.get('background') + ':');
+
+ row.appendChild(td);
+
+ var transparentCheckbox = document.createElement('input');
+ transparentCheckbox.setAttribute('type', 'checkbox');
+ transparentCheckbox.checked = graph.background == null || graph.background == mxConstants.NONE;
+
+ td = document.createElement('td');
+ td.appendChild(transparentCheckbox);
+ mxUtils.write(td, mxResources.get('transparent'));
+
+ row.appendChild(td);
+
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ mxUtils.write(td, mxResources.get('grid') + ':');
+
+ row.appendChild(td);
+
+ var gridCheckbox = document.createElement('input');
+ gridCheckbox.setAttribute('type', 'checkbox');
+ gridCheckbox.checked = false;
+
+ td = document.createElement('td');
+ td.appendChild(gridCheckbox);
+
+ row.appendChild(td);
+
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ mxUtils.write(td, mxResources.get('borderWidth') + ':');
+
+ row.appendChild(td);
+
+ var borderInput = document.createElement('input');
+ borderInput.setAttribute('type', 'number');
+ borderInput.setAttribute('value', ExportDialog.lastBorderValue);
+ borderInput.style.width = '180px';
+
+ td = document.createElement('td');
+ td.appendChild(borderInput);
+ row.appendChild(td);
+
+ tbody.appendChild(row);
+ table.appendChild(tbody);
+
+ // Handles changes in the export format
+ function formatChanged() {
+ var name = nameInput.value;
+ var dot = name.lastIndexOf('.');
+
+ if (dot > 0) {
+ nameInput.value = name.substring(0, dot + 1) + imageFormatSelect.value;
+ }
+ else {
+ nameInput.value = name + '.' + imageFormatSelect.value;
+ }
+
+ if (imageFormatSelect.value === 'xml') {
+ zoomInput.setAttribute('disabled', 'true');
+ widthInput.setAttribute('disabled', 'true');
+ heightInput.setAttribute('disabled', 'true');
+ borderInput.setAttribute('disabled', 'true');
+ }
+ else {
+ zoomInput.removeAttribute('disabled');
+ widthInput.removeAttribute('disabled');
+ heightInput.removeAttribute('disabled');
+ borderInput.removeAttribute('disabled');
+ }
+
+ if (imageFormatSelect.value === 'png' || imageFormatSelect.value === 'svg' || imageFormatSelect.value === 'pdf') {
+ transparentCheckbox.removeAttribute('disabled');
+ }
+ else {
+ transparentCheckbox.setAttribute('disabled', 'disabled');
+ }
+
+ if (imageFormatSelect.value === 'png' || imageFormatSelect.value === 'jpg' || imageFormatSelect.value === 'pdf') {
+ gridCheckbox.removeAttribute('disabled');
+ }
+ else {
+ gridCheckbox.setAttribute('disabled', 'disabled');
+ }
+
+ if (imageFormatSelect.value === 'png') {
+ dpiSelect.removeAttribute('disabled');
+ customDpi.removeAttribute('disabled');
+ }
+ else {
+ dpiSelect.setAttribute('disabled', 'disabled');
+ customDpi.setAttribute('disabled', 'disabled');
+ }
+ };
+
+ mxEvent.addListener(imageFormatSelect, 'change', formatChanged);
+ formatChanged();
+
+ function checkValues() {
+ if (widthInput.value * heightInput.value > MAX_AREA || widthInput.value <= 0) {
+ widthInput.style.backgroundColor = 'red';
+ }
+ else {
+ widthInput.style.backgroundColor = '';
+ }
+
+ if (widthInput.value * heightInput.value > MAX_AREA || heightInput.value <= 0) {
+ heightInput.style.backgroundColor = 'red';
+ }
+ else {
+ heightInput.style.backgroundColor = '';
+ }
+ };
+
+ mxEvent.addListener(zoomInput, 'change', function() {
+ zoomUserChanged = true;
+ var s = Math.max(0, parseFloat(zoomInput.value) || 100) / 100;
+ zoomInput.value = parseFloat((s * 100).toFixed(2));
+
+ if (width > 0) {
+ widthInput.value = Math.floor(width * s);
+ heightInput.value = Math.floor(height * s);
+ }
+ else {
+ zoomInput.value = '100';
+ widthInput.value = width;
+ heightInput.value = height;
+ }
+
+ checkValues();
+ });
+
+ mxEvent.addListener(widthInput, 'change', function() {
+ var s = parseInt(widthInput.value) / width;
+
+ if (s > 0) {
+ zoomInput.value = parseFloat((s * 100).toFixed(2));
+ heightInput.value = Math.floor(height * s);
+ }
+ else {
+ zoomInput.value = '100';
+ widthInput.value = width;
+ heightInput.value = height;
+ }
+
+ checkValues();
+ });
+
+ mxEvent.addListener(heightInput, 'change', function() {
+ var s = parseInt(heightInput.value) / height;
+
+ if (s > 0) {
+ zoomInput.value = parseFloat((s * 100).toFixed(2));
+ widthInput.value = Math.floor(width * s);
+ }
+ else {
+ zoomInput.value = '100';
+ widthInput.value = width;
+ heightInput.value = height;
+ }
+
+ checkValues();
+ });
+
+ row = document.createElement('tr');
+ td = document.createElement('td');
+ td.setAttribute('align', 'right');
+ td.style.paddingTop = '22px';
+ td.colSpan = 2;
+
+ var saveBtn = mxUtils.button(mxResources.get('export'), mxUtils.bind(this, function() {
+ if (parseInt(zoomInput.value) <= 0) {
+ mxUtils.alert(mxResources.get('drawingEmpty'));
+ }
+ else {
+ var name = nameInput.value;
+ var format = imageFormatSelect.value;
+ var s = Math.max(0, parseFloat(zoomInput.value) || 100) / 100;
+ var b = Math.max(0, parseInt(borderInput.value));
+ var bg = graph.background;
+ var dpi = Math.max(1, parseInt(customDpi.value));
+
+ if ((format == 'svg' || format == 'png' || format == 'pdf') && transparentCheckbox.checked) {
+ bg = null;
+ }
+ else if (bg == null || bg == mxConstants.NONE) {
+ bg = '#ffffff';
+ }
+
+ ExportDialog.lastBorderValue = b;
+ ExportDialog.exportFile(editorUi, name, format, bg, s, b, dpi, gridCheckbox.checked);
+ }
+ }));
+ saveBtn.className = 'geBtn gePrimaryBtn';
+
+ var cancelBtn = mxUtils.button(mxResources.get('cancel'), function() {
+ editorUi.hideDialog();
+ });
+ cancelBtn.className = 'geBtn';
+
+ if (editorUi.editor.cancelFirst) {
+ td.appendChild(cancelBtn);
+ td.appendChild(saveBtn);
+ }
+ else {
+ td.appendChild(saveBtn);
+ td.appendChild(cancelBtn);
+ }
+
+ row.appendChild(td);
+ tbody.appendChild(row);
+ table.appendChild(tbody);
+ this.container = table;
+};
+
+/**
+ * Remembers last value for border.
+ */
+ExportDialog.lastBorderValue = 0;
+
+/**
+ * Global switches for the export dialog.
+ */
+ExportDialog.showGifOption = true;
+
+/**
+ * Global switches for the export dialog.
+ */
+ExportDialog.showXmlOption = true;
+
+/**
+ * Hook for getting the export format. Returns null for the default
+ * intermediate XML export format or a function that returns the
+ * parameter and value to be used in the request in the form
+ * key=value, where value should be URL encoded.
+ */
+ExportDialog.exportFile = function(editorUi, name, format, bg, s, b, dpi, grid) {
+ var graph = editorUi.editor.graph;
+
+ if (format == 'xml') {
+ ExportDialog.saveLocalFile(editorUi, mxUtils.getXml(editorUi.editor.getGraphXml()), name, format);
+ }
+ else if (format == 'svg') {
+ ExportDialog.saveLocalFile(editorUi, mxUtils.getXml(graph.getSvg(bg, s, b)), name, format);
+ }
+ else {
+ var bounds = graph.getGraphBounds();
+
+ // New image export
+ var xmlDoc = mxUtils.createXmlDocument();
+ var root = xmlDoc.createElement('output');
+ xmlDoc.appendChild(root);
+
+ // Renders graph. Offset will be multiplied with state's scale when painting state.
+ var xmlCanvas = new mxXmlCanvas2D(root);
+ xmlCanvas.translate(Math.floor((b / s - bounds.x) / graph.view.scale),
+ Math.floor((b / s - bounds.y) / graph.view.scale));
+ xmlCanvas.scale(s / graph.view.scale);
+
+ var imgExport = new mxImageExport()
+ imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
+
+ // Puts request data together
+ var param = 'xml=' + encodeURIComponent(mxUtils.getXml(root));
+ var w = Math.ceil(bounds.width * s / graph.view.scale + 2 * b);
+ var h = Math.ceil(bounds.height * s / graph.view.scale + 2 * b);
+
+ // Requests image if request is valid
+ if (param.length <= MAX_REQUEST_SIZE && w * h < MAX_AREA) {
+ editorUi.hideDialog();
+ var req = new mxXmlRequest(EXPORT_URL, 'format=' + format +
+ '&filename=' + encodeURIComponent(name) +
+ '&bg=' + ((bg != null) ? bg : 'none') +
+ '&w=' + w + '&h=' + h + '&' + param +
+ '&dpi=' + dpi);
+ req.simulate(document, '_blank');
+ }
+ else {
+ mxUtils.alert(mxResources.get('drawingTooLarge'));
+ }
+ }
+};
+
+/**
+ * Hook for getting the export format. Returns null for the default
+ * intermediate XML export format or a function that returns the
+ * parameter and value to be used in the request in the form
+ * key=value, where value should be URL encoded.
+ */
+ExportDialog.saveLocalFile = function(editorUi, data, filename, format) {
+ if (data.length < MAX_REQUEST_SIZE) {
+ editorUi.hideDialog();
+ var req = new mxXmlRequest(SAVE_URL, 'xml=' + encodeURIComponent(data) + '&filename=' +
+ encodeURIComponent(filename) + '&format=' + format);
+ req.simulate(document, '_blank');
+ }
+ else {
+ mxUtils.alert(mxResources.get('drawingTooLarge'));
+ mxUtils.popup(xml);
+ }
+};
+
+/**
+ * Constructs a new metadata dialog.
+ */
+var EditDataDialog = function(ui, cell) {
+ var div = document.createElement('div');
+ var graph = ui.editor.graph;
+
+ var value = graph.getModel().getValue(cell);
+
+ // Converts the value to an XML node
+ if (!mxUtils.isNode(value)) {
+ var doc = mxUtils.createXmlDocument();
+ var obj = doc.createElement('object');
+ obj.setAttribute('label', value || '');
+ value = obj;
+ }
+
+ var meta = {};
+
+ try {
+ var temp = mxUtils.getValue(ui.editor.graph.getCurrentCellStyle(cell), 'metaData', null);
+
+ if (temp != null) {
+ meta = JSON.parse(temp);
+ }
+ }
+ catch (e) {
+ // ignore
+ }
+
+ // Creates the dialog contents
+ var form = new mxForm('properties');
+ form.table.style.width = '100%';
+
+ var attrs = value.attributes;
+ var names = [];
+ var texts = [];
+ var count = 0;
+
+ var id = (EditDataDialog.getDisplayIdForCell != null) ?
+ EditDataDialog.getDisplayIdForCell(ui, cell) : null;
+
+ var addRemoveButton = function(text, name) {
+ var wrapper = document.createElement('div');
+ wrapper.style.position = 'relative';
+ wrapper.style.paddingRight = '20px';
+ wrapper.style.boxSizing = 'border-box';
+ wrapper.style.width = '100%';
+
+ var removeAttr = document.createElement('a');
+ var img = mxUtils.createImage(Dialog.prototype.closeImage);
+ img.style.height = '9px';
+ img.style.fontSize = '9px';
+ img.style.marginBottom = (mxClient.IS_IE11) ? '-1px' : '5px';
+
+ removeAttr.className = 'geButton';
+ removeAttr.setAttribute('title', mxResources.get('delete'));
+ removeAttr.style.position = 'absolute';
+ removeAttr.style.top = '4px';
+ removeAttr.style.right = '0px';
+ removeAttr.style.margin = '0px';
+ removeAttr.style.width = '9px';
+ removeAttr.style.height = '9px';
+ removeAttr.style.cursor = 'pointer';
+ removeAttr.appendChild(img);
+
+ var removeAttrFn = (function(name) {
+ return function() {
+ var count = 0;
+
+ for (var j = 0; j < names.length; j++) {
+ if (names[j] == name) {
+ texts[j] = null;
+ form.table.deleteRow(count + ((id != null) ? 1 : 0));
+
+ break;
+ }
+
+ if (texts[j] != null) {
+ count++;
+ }
+ }
+ };
+ })(name);
+
+ mxEvent.addListener(removeAttr, 'click', removeAttrFn);
+
+ var parent = text.parentNode;
+ wrapper.appendChild(text);
+ wrapper.appendChild(removeAttr);
+ parent.appendChild(wrapper);
+ };
+
+ var addTextArea = function(index, name, value) {
+ names[index] = name;
+ texts[index] = form.addTextarea(names[count] + ':', value, 2);
+ texts[index].style.width = '100%';
+
+ if (value.indexOf('\n') > 0) {
+ texts[index].setAttribute('rows', '2');
+ }
+
+ addRemoveButton(texts[index], name);
+
+ if (meta[name] != null && meta[name].editable == false) {
+ texts[index].setAttribute('disabled', 'disabled');
+ }
+ };
+
+ var temp = [];
+ var isLayer = graph.getModel().getParent(cell) == graph.getModel().getRoot();
+
+ for (var i = 0; i < attrs.length; i++) {
+ if ((attrs[i].nodeName != 'label' || Graph.translateDiagram ||
+ isLayer) && attrs[i].nodeName != 'placeholders') {
+ temp.push({ name: attrs[i].nodeName, value: attrs[i].nodeValue });
+ }
+ }
+
+ // Sorts by name
+ temp.sort(function(a, b) {
+ if (a.name < b.name) {
+ return -1;
+ }
+ else if (a.name > b.name) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ });
+
+ if (id != null) {
+ var text = document.createElement('div');
+ text.style.width = '100%';
+ text.style.fontSize = '11px';
+ text.style.textAlign = 'center';
+ mxUtils.write(text, id);
+
+ var idInput = form.addField(mxResources.get('id') + ':', text);
+
+ mxEvent.addListener(text, 'dblclick', function(evt) {
+ var dlg = new FilenameDialog(ui, id, mxResources.get('apply'), mxUtils.bind(this, function(value) {
+ if (value != null && value.length > 0 && value != id) {
+ if (graph.model.isRoot(cell)) {
+ var page = ui.getPageById(id);
+
+ if (page != null) {
+ if (ui.getPageById(value) == null) {
+ var index = ui.getPageIndex(page);
+
+ if (index >= 0) {
+ ui.removePage(page);
+ page.node.setAttribute('id', value);
+ id = value;
+ idInput.innerHTML = mxUtils.htmlEntities(value);
+ ui.insertPage(page, index);
+ }
+ }
+ else {
+ ui.handleError({ message: mxResources.get('alreadyExst', [mxResources.get('page')]) });
+ }
+ }
+ }
+ else {
+ if (graph.getModel().getCell(value) == null) {
+ graph.getModel().cellRemoved(cell);
+ cell.setId(value);
+ id = value;
+ idInput.innerHTML = mxUtils.htmlEntities(value);
+ graph.getModel().cellAdded(cell);
+ }
+ else {
+ ui.handleError({ message: mxResources.get('alreadyExst', [value]) });
+ }
+ }
+ }
+ }), mxResources.get('id'), null, null, null, null, null, null, 200);
+ ui.showDialog(dlg.container, 300, 80, true, true);
+ dlg.init();
+ });
+ }
+
+ for (var i = 0; i < temp.length; i++) {
+ addTextArea(count, temp[i].name, temp[i].value);
+ count++;
+ }
+
+ var top = document.createElement('div');
+ top.style.position = 'absolute';
+ top.style.top = '30px';
+ top.style.left = '30px';
+ top.style.right = '30px';
+ top.style.bottom = '80px';
+ top.style.overflowY = 'auto';
+
+ top.appendChild(form.table);
+
+ var newProp = document.createElement('div');
+ newProp.style.display = 'flex';
+ newProp.style.alignItems = 'center';
+ newProp.style.boxSizing = 'border-box';
+ newProp.style.paddingRight = '160px';
+ newProp.style.whiteSpace = 'nowrap';
+ newProp.style.marginTop = '6px';
+ newProp.style.width = '100%';
+
+ var nameInput = document.createElement('input');
+ nameInput.setAttribute('placeholder', mxResources.get('enterPropertyName'));
+ nameInput.setAttribute('type', 'text');
+ nameInput.setAttribute('size', (mxClient.IS_IE || mxClient.IS_IE11) ? '36' : '40');
+ nameInput.style.boxSizing = 'border-box';
+ nameInput.style.borderWidth = '1px';
+ nameInput.style.borderStyle = 'solid';
+ nameInput.style.marginLeft = '2px';
+ nameInput.style.padding = '4px';
+ nameInput.style.width = '100%';
+
+ newProp.appendChild(nameInput);
+ top.appendChild(newProp);
+ div.appendChild(top);
+
+ var addBtn = mxUtils.button(mxResources.get('addProperty'), function() {
+ var name = nameInput.value;
+
+ // Avoid ':' in attribute names which seems to be valid in Chrome
+ if (name.length > 0 && name != 'label' && name != 'id' &&
+ name != 'placeholders' && name.indexOf(':') < 0) {
+ try {
+ var idx = mxUtils.indexOf(names, name);
+
+ if (idx >= 0 && texts[idx] != null) {
+ texts[idx].focus();
+ }
+ else {
+ // Checks if the name is valid
+ var clone = value.cloneNode(false);
+ clone.setAttribute(name, '');
+
+ if (idx >= 0) {
+ names.splice(idx, 1);
+ texts.splice(idx, 1);
+ }
+
+ names.push(name);
+ var text = form.addTextarea(name + ':', '', 2);
+ text.style.width = '100%';
+ texts.push(text);
+ addRemoveButton(text, name);
+
+ text.focus();
+ }
+
+ addBtn.setAttribute('disabled', 'disabled');
+ nameInput.value = '';
+ }
+ catch (e) {
+ mxUtils.alert(e);
+ }
+ }
+ else {
+ mxUtils.alert(mxResources.get('invalidName'));
+ }
+ });
+
+ mxEvent.addListener(nameInput, 'keypress', function(e) {
+ if (e.keyCode == 13) {
+ addBtn.click();
+ }
+ });
+
+ this.init = function() {
+ if (texts.length > 0) {
+ texts[0].focus();
+ }
+ else {
+ nameInput.focus();
+ }
+ };
+
+ addBtn.setAttribute('title', mxResources.get('addProperty'));
+ addBtn.setAttribute('disabled', 'disabled');
+ addBtn.style.textOverflow = 'ellipsis';
+ addBtn.style.position = 'absolute';
+ addBtn.style.overflow = 'hidden';
+ addBtn.style.width = '144px';
+ addBtn.style.right = '0px';
+ addBtn.className = 'geBtn';
+ newProp.appendChild(addBtn);
+
+ var cancelBtn = mxUtils.button(mxResources.get('cancel'), function() {
+ ui.hideDialog.apply(ui, arguments);
+ });
+
+ cancelBtn.setAttribute('title', 'Escape');
+ cancelBtn.className = 'geBtn';
+
+ var exportBtn = mxUtils.button(mxResources.get('export'), mxUtils.bind(this, function(evt) {
+ var result = graph.getDataForCells([cell]);
+
+ var dlg = new EmbedDialog(ui, JSON.stringify(result, null, 2), null, null, function() {
+ console.log(result);
+ ui.alert('Written to Console (Dev Tools)');
+ }, mxResources.get('export'), null, 'Console', 'data.json');
+ ui.showDialog(dlg.container, 450, 240, true, true);
+ dlg.init();
+ }));
+
+ exportBtn.setAttribute('title', mxResources.get('export'));
+ exportBtn.className = 'geBtn';
+
+ var applyBtn = mxUtils.button(mxResources.get('apply'), function() {
+ try {
+ ui.hideDialog.apply(ui, arguments);
+
+ // Clones and updates the value
+ value = value.cloneNode(true);
+ var removeLabel = false;
+
+ for (var i = 0; i < names.length; i++) {
+ if (texts[i] == null) {
+ value.removeAttribute(names[i]);
+ }
+ else {
+ value.setAttribute(names[i], texts[i].value);
+ removeLabel = removeLabel || (names[i] == 'placeholder' &&
+ value.getAttribute('placeholders') == '1');
+ }
+ }
+
+ // Removes label if placeholder is assigned
+ if (removeLabel) {
+ value.removeAttribute('label');
+ }
+
+ // Updates the value of the cell (undoable)
+ graph.getModel().setValue(cell, value);
+ }
+ catch (e) {
+ mxUtils.alert(e);
+ }
+ });
+
+ applyBtn.setAttribute('title', 'Ctrl+Enter');
+ applyBtn.className = 'geBtn gePrimaryBtn';
+
+ mxEvent.addListener(div, 'keypress', function(e) {
+ if (e.keyCode == 13 && mxEvent.isControlDown(e)) {
+ applyBtn.click();
+ }
+ });
+
+ function updateAddBtn() {
+ if (nameInput.value.length > 0) {
+ addBtn.removeAttribute('disabled');
+ }
+ else {
+ addBtn.setAttribute('disabled', 'disabled');
+ }
+ };
+
+ mxEvent.addListener(nameInput, 'keyup', updateAddBtn);
+
+ // Catches all changes that don't fire a keyup (such as paste via mouse)
+ mxEvent.addListener(nameInput, 'change', updateAddBtn);
+
+ var buttons = document.createElement('div');
+ buttons.style.cssText = 'position:absolute;left:30px;right:30px;text-align:right;bottom:30px;height:40px;'
+
+ if (ui.editor.graph.getModel().isVertex(cell) || ui.editor.graph.getModel().isEdge(cell)) {
+ var replace = document.createElement('span');
+ replace.style.marginRight = '10px';
+ var input = document.createElement('input');
+ input.setAttribute('type', 'checkbox');
+ input.style.marginRight = '6px';
+
+ if (value.getAttribute('placeholders') == '1') {
+ input.setAttribute('checked', 'checked');
+ input.defaultChecked = true;
+ }
+
+ mxEvent.addListener(input, 'click', function() {
+ if (value.getAttribute('placeholders') == '1') {
+ value.removeAttribute('placeholders');
+ }
+ else {
+ value.setAttribute('placeholders', '1');
+ }
+ });
+
+ replace.appendChild(input);
+ mxUtils.write(replace, mxResources.get('placeholders'));
+
+ if (EditDataDialog.placeholderHelpLink != null) {
+ var link = document.createElement('a');
+ link.setAttribute('href', EditDataDialog.placeholderHelpLink);
+ link.setAttribute('title', mxResources.get('help'));
+ link.setAttribute('target', '_blank');
+ link.style.marginLeft = '8px';
+ link.style.cursor = 'help';
+
+ var icon = document.createElement('img');
+ mxUtils.setOpacity(icon, 50);
+ icon.style.height = '16px';
+ icon.style.width = '16px';
+ icon.setAttribute('border', '0');
+ icon.setAttribute('valign', 'middle');
+ icon.style.marginTop = (mxClient.IS_IE11) ? '0px' : '-4px';
+ icon.setAttribute('src', Editor.helpImage);
+ link.appendChild(icon);
+
+ replace.appendChild(link);
+ }
+
+ buttons.appendChild(replace);
+ }
+
+ if (ui.editor.cancelFirst) {
+ buttons.appendChild(cancelBtn);
+ }
+
+ buttons.appendChild(exportBtn);
+ buttons.appendChild(applyBtn);
+
+ if (!ui.editor.cancelFirst) {
+ buttons.appendChild(cancelBtn);
+ }
+
+ div.appendChild(buttons);
+ this.container = div;
+};
+
+/**
+ * Optional help link.
+ */
+EditDataDialog.getDisplayIdForCell = function(ui, cell) {
+ var id = null;
+
+ if (ui.editor.graph.getModel().getParent(cell) != null) {
+ id = cell.getId();
+ }
+
+ return id;
+};
+
+/**
+ * Optional help link.
+ */
+EditDataDialog.placeholderHelpLink = null;
+
+/**
+ * Constructs a new link dialog.
+ */
+var LinkDialog = function(editorUi, initialValue, btnLabel, fn) {
+ var div = document.createElement('div');
+ mxUtils.write(div, mxResources.get('editLink') + ':');
+
+ var inner = document.createElement('div');
+ inner.className = 'geTitle';
+ inner.style.backgroundColor = 'transparent';
+ inner.style.borderColor = 'transparent';
+ inner.style.whiteSpace = 'nowrap';
+ inner.style.textOverflow = 'clip';
+ inner.style.cursor = 'default';
+ inner.style.paddingRight = '20px';
+
+ var linkInput = document.createElement('input');
+ linkInput.setAttribute('value', initialValue);
+ linkInput.setAttribute('placeholder', 'http://www.example.com/');
+ linkInput.setAttribute('type', 'text');
+ linkInput.style.marginTop = '6px';
+ linkInput.style.width = '400px';
+ linkInput.style.backgroundImage = 'url(\'' + Dialog.prototype.clearImage + '\')';
+ linkInput.style.backgroundRepeat = 'no-repeat';
+ linkInput.style.backgroundPosition = '100% 50%';
+ linkInput.style.paddingRight = '14px';
+
+ var cross = document.createElement('div');
+ cross.setAttribute('title', mxResources.get('reset'));
+ cross.style.position = 'relative';
+ cross.style.left = '-16px';
+ cross.style.width = '12px';
+ cross.style.height = '14px';
+ cross.style.cursor = 'pointer';
+
+ // Workaround for inline-block not supported in IE
+ cross.style.display = 'inline-block';
+ cross.style.top = '3px';
+
+ // Needed to block event transparency in IE
+ cross.style.background = 'url(' + IMAGE_PATH + '/transparent.gif)';
+
+ mxEvent.addListener(cross, 'click', function() {
+ linkInput.value = '';
+ linkInput.focus();
+ });
+
+ inner.appendChild(linkInput);
+ inner.appendChild(cross);
+ div.appendChild(inner);
+
+ this.init = function() {
+ linkInput.focus();
+
+ if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5) {
+ linkInput.select();
+ }
+ else {
+ document.execCommand('selectAll', false, null);
+ }
+ };
+
+ var btns = document.createElement('div');
+ btns.style.marginTop = '18px';
+ btns.style.textAlign = 'right';
+
+ mxEvent.addListener(linkInput, 'keypress', function(e) {
+ if (e.keyCode == 13) {
+ editorUi.hideDialog();
+ fn(linkInput.value);
+ }
+ });
+
+ var cancelBtn = mxUtils.button(mxResources.get('cancel'), function() {
+ editorUi.hideDialog();
+ });
+ cancelBtn.className = 'geBtn';
+
+ if (editorUi.editor.cancelFirst) {
+ btns.appendChild(cancelBtn);
+ }
+
+ var mainBtn = mxUtils.button(btnLabel, function() {
+ editorUi.hideDialog();
+ fn(linkInput.value);
+ });
+ mainBtn.className = 'geBtn gePrimaryBtn';
+ btns.appendChild(mainBtn);
+
+ if (!editorUi.editor.cancelFirst) {
+ btns.appendChild(cancelBtn);
+ }
+
+ div.appendChild(btns);
+
+ this.container = div;
+};
+
+/**
+ *
+ */
+var OutlineWindow = function(editorUi, x, y, w, h) {
+ var graph = editorUi.editor.graph;
+
+ var div = document.createElement('div');
+ div.style.position = 'absolute';
+ div.style.width = '100%';
+ div.style.height = '100%';
+ div.style.overflow = 'hidden';
+
+ this.window = new mxWindow(mxResources.get('outline'), div, x, y, w, h, true, true);
+ this.window.minimumSize = new mxRectangle(0, 0, 80, 80);
+ this.window.destroyOnClose = false;
+ this.window.setMaximizable(false);
+ this.window.setResizable(true);
+ this.window.setClosable(true);
+ this.window.setVisible(true);
+
+ var outline = editorUi.createOutline(this.window);
+
+ editorUi.installResizeHandler(this, true, function() {
+ outline.destroy();
+ });
+
+ this.window.addListener(mxEvent.SHOW, mxUtils.bind(this, function() {
+ this.window.fit();
+ outline.setSuspended(false);
+ }));
+
+ this.window.addListener(mxEvent.HIDE, mxUtils.bind(this, function() {
+ outline.setSuspended(true);
+ }));
+
+ this.window.addListener(mxEvent.NORMALIZE, mxUtils.bind(this, function() {
+ outline.setSuspended(false);
+ }));
+
+ this.window.addListener(mxEvent.MINIMIZE, mxUtils.bind(this, function() {
+ outline.setSuspended(true);
+ }));
+
+ outline.init(div);
+
+ mxEvent.addMouseWheelListener(function(evt, up) {
+ var outlineWheel = false;
+ var source = mxEvent.getSource(evt);
+
+ while (source != null) {
+ if (source == outline.svg) {
+ outlineWheel = true;
+ break;
+ }
+
+ source = source.parentNode;
+ }
+
+ if (outlineWheel) {
+ var factor = graph.zoomFactor;
+
+ // Slower zoom for pinch gesture on trackpad
+ if (evt.deltaY != null && Math.round(evt.deltaY) != evt.deltaY) {
+ factor = 1 + (Math.abs(evt.deltaY) / 20) * (factor - 1);
+ }
+
+ graph.lazyZoom(up, null, null, factor);
+ mxEvent.consume(evt);
+ }
+ });
+};
+
+/**
+ *
+ */
+var LayersWindow = function(editorUi, x, y, w, h) {
+ var graph = editorUi.editor.graph;
+
+ var div = document.createElement('div');
+ div.className = 'geBackground';
+ div.style.userSelect = 'none';
+ div.style.border = '1px solid whiteSmoke';
+ div.style.height = '100%';
+ div.style.marginBottom = '10px';
+ div.style.overflow = 'auto';
+
+ var tbarHeight = (!EditorUi.compactUi) ? '30px' : '26px';
+
+ var listDiv = document.createElement('div')
+ listDiv.className = 'geBackground';
+ listDiv.style.position = 'absolute';
+ listDiv.style.overflow = 'auto';
+ listDiv.style.left = '0px';
+ listDiv.style.right = '0px';
+ listDiv.style.top = '0px';
+ listDiv.style.bottom = (parseInt(tbarHeight) + 7) + 'px';
+ div.appendChild(listDiv);
+
+ var dragSource = null;
+ var dropIndex = null;
+
+ mxEvent.addListener(div, 'dragover', function(evt) {
+ evt.dataTransfer.dropEffect = 'move';
+ dropIndex = 0;
+ evt.stopPropagation();
+ evt.preventDefault();
+ });
+
+ // Workaround for "no element found" error in FF
+ mxEvent.addListener(div, 'drop', function(evt) {
+ evt.stopPropagation();
+ evt.preventDefault();
+ });
+
+ var layerCount = null;
+ var selectionLayer = null;
+ var ldiv = document.createElement('div');
+
+ ldiv.className = 'geToolbarContainer';
+ ldiv.style.position = 'absolute';
+ ldiv.style.bottom = '0px';
+ ldiv.style.left = '0px';
+ ldiv.style.right = '0px';
+ ldiv.style.height = tbarHeight;
+ ldiv.style.overflow = 'hidden';
+ ldiv.style.padding = (!EditorUi.compactUi) ? '1px' : '4px 0px 3px 0px';
+ ldiv.style.borderWidth = '1px 0px 0px 0px';
+ ldiv.style.borderStyle = 'solid';
+ ldiv.style.display = 'block';
+ ldiv.style.whiteSpace = 'nowrap';
+
+ var link = document.createElement('a');
+ link.className = 'geButton';
+
+ var removeLink = link.cloneNode(false);
+ var img = document.createElement('img');
+ img.setAttribute('border', '0');
+ img.setAttribute('width', '22');
+ img.setAttribute('src', Editor.trashImage);
+ img.style.opacity = '0.9';
+
+ if (Editor.isDarkMode()) {
+ img.style.filter = 'invert(100%)';
+ }
+
+ removeLink.appendChild(img);
+
+ mxEvent.addListener(removeLink, 'click', function(evt) {
+ if (graph.isEnabled()) {
+ graph.model.beginUpdate();
+ try {
+ var index = graph.model.root.getIndex(selectionLayer);
+ graph.removeCells([selectionLayer], false);
+
+ // Creates default layer if no layer exists
+ if (graph.model.getChildCount(graph.model.root) == 0) {
+ graph.model.add(graph.model.root, new mxCell());
+ graph.setDefaultParent(null);
+ }
+ else if (index > 0 && index <= graph.model.getChildCount(graph.model.root)) {
+ graph.setDefaultParent(graph.model.getChildAt(graph.model.root, index - 1));
+ }
+ else {
+ graph.setDefaultParent(null);
+ }
+ }
+ finally {
+ graph.model.endUpdate();
+ }
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ if (!graph.isEnabled()) {
+ removeLink.className = 'geButton mxDisabled';
+ }
+
+ ldiv.appendChild(removeLink);
+
+ var insertLink = link.cloneNode();
+ insertLink.setAttribute('title', mxUtils.trim(mxResources.get('moveSelectionTo', ['...'])));
+
+ img = img.cloneNode(false);
+ img.setAttribute('src', Editor.verticalDotsImage);
+ insertLink.appendChild(img);
+
+ mxEvent.addListener(insertLink, 'click', function(evt) {
+ if (graph.isEnabled() && !graph.isSelectionEmpty()) {
+ var offset = mxUtils.getOffset(insertLink);
+
+ editorUi.showPopupMenu(mxUtils.bind(this, function(menu, parent) {
+ for (var i = layerCount - 1; i >= 0; i--) {
+ (mxUtils.bind(this, function(child) {
+ var item = menu.addItem(graph.convertValueToString(child) ||
+ mxResources.get('background'), null, mxUtils.bind(this, function() {
+ graph.moveCells(graph.getSelectionCells(), 0, 0, false, child);
+ }), parent);
+
+ if (graph.getSelectionCount() == 1 && graph.model.isAncestor(child, graph.getSelectionCell())) {
+ menu.addCheckmark(item, Editor.checkmarkImage);
+ }
+
+ }))(graph.model.getChildAt(graph.model.root, i));
+ }
+ }), offset.x, offset.y + insertLink.offsetHeight, evt);
+ }
+ });
+
+ ldiv.appendChild(insertLink);
+
+ var dataLink = link.cloneNode(false);
+ dataLink.setAttribute('title', mxResources.get('editData'));
+
+ img = img.cloneNode(false);
+ img.setAttribute('src', Editor.editImage);
+ dataLink.appendChild(img);
+
+ mxEvent.addListener(dataLink, 'click', function(evt) {
+ if (graph.isEnabled()) {
+ editorUi.showDataDialog(selectionLayer);
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ if (!graph.isEnabled()) {
+ dataLink.className = 'geButton mxDisabled';
+ }
+
+ ldiv.appendChild(dataLink);
+
+ function renameLayer(layer) {
+ if (graph.isEnabled() && layer != null) {
+ var label = graph.convertValueToString(layer);
+ var dlg = new FilenameDialog(editorUi, label || mxResources.get('background'),
+ mxResources.get('rename'), mxUtils.bind(this, function(newValue) {
+ if (newValue != null) {
+ graph.cellLabelChanged(layer, newValue);
+ }
+ }), mxResources.get('enterName'));
+ editorUi.showDialog(dlg.container, 300, 100, true, true);
+ dlg.init();
+ }
+ };
+
+ var duplicateLink = link.cloneNode(false);
+ duplicateLink.setAttribute('title', mxResources.get('duplicate'));
+
+ img = img.cloneNode(false);
+ img.setAttribute('src', Editor.duplicateImage);
+ duplicateLink.appendChild(img);
+
+ mxEvent.addListener(duplicateLink, 'click', function(evt) {
+ if (graph.isEnabled()) {
+ var newCell = null;
+ graph.model.beginUpdate();
+ try {
+ newCell = graph.cloneCell(selectionLayer);
+ graph.cellLabelChanged(newCell, mxResources.get('untitledLayer'));
+ newCell.setVisible(true);
+ newCell = graph.addCell(newCell, graph.model.root);
+ graph.setDefaultParent(newCell);
+ }
+ finally {
+ graph.model.endUpdate();
+ }
+
+ if (newCell != null && !graph.isCellLocked(newCell)) {
+ graph.selectAll(newCell);
+ }
+ }
+ });
+
+ if (!graph.isEnabled()) {
+ duplicateLink.className = 'geButton mxDisabled';
+ }
+
+ ldiv.appendChild(duplicateLink);
+
+ var addLink = link.cloneNode(false);
+ addLink.setAttribute('title', mxResources.get('addLayer'));
+
+ img = img.cloneNode(false);
+ img.setAttribute('src', Editor.addImage);
+ addLink.appendChild(img);
+
+ mxEvent.addListener(addLink, 'click', function(evt) {
+ if (graph.isEnabled()) {
+ graph.model.beginUpdate();
+
+ try {
+ var cell = graph.addCell(new mxCell(mxResources.get('untitledLayer')), graph.model.root);
+ graph.setDefaultParent(cell);
+ }
+ finally {
+ graph.model.endUpdate();
+ }
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ if (!graph.isEnabled()) {
+ addLink.className = 'geButton mxDisabled';
+ }
+
+ ldiv.appendChild(addLink);
+ div.appendChild(ldiv);
+
+ var layerDivs = new mxDictionary();
+
+ var dot = document.createElement('span');
+ dot.setAttribute('title', mxResources.get('selectionOnly'));
+ dot.innerHTML = '•';
+ dot.style.position = 'absolute';
+ dot.style.fontWeight = 'bold';
+ dot.style.fontSize = '16pt';
+ dot.style.right = '2px';
+ dot.style.top = '2px';
+
+ function updateLayerDot() {
+ var div = layerDivs.get(graph.getLayerForCells(graph.getSelectionCells()));
+
+ if (div != null) {
+ div.appendChild(dot);
+ }
+ else if (dot.parentNode != null) {
+ dot.parentNode.removeChild(dot);
+ }
+ };
+
+ function refresh() {
+ layerCount = graph.model.getChildCount(graph.model.root)
+ listDiv.innerText = '';
+ layerDivs.clear();
+
+ function addLayer(index, label, child, defaultParent) {
+ var ldiv = document.createElement('div');
+ ldiv.className = 'geToolbarContainer';
+ layerDivs.put(child, ldiv);
+
+ ldiv.style.overflow = 'hidden';
+ ldiv.style.position = 'relative';
+ ldiv.style.padding = '4px';
+ ldiv.style.height = '22px';
+ ldiv.style.display = 'block';
+ ldiv.style.backgroundColor = (Editor.isDarkMode()) ?
+ Editor.darkColor : 'whiteSmoke';
+ ldiv.style.borderWidth = '0px 0px 1px 0px';
+ ldiv.style.borderColor = '#c3c3c3';
+ ldiv.style.borderStyle = 'solid';
+ ldiv.style.whiteSpace = 'nowrap';
+ ldiv.setAttribute('title', label);
+
+ var left = document.createElement('div');
+ left.style.display = 'inline-block';
+ left.style.width = '100%';
+ left.style.textOverflow = 'ellipsis';
+ left.style.overflow = 'hidden';
+
+ mxEvent.addListener(ldiv, 'dragover', function(evt) {
+ evt.dataTransfer.dropEffect = 'move';
+ dropIndex = index;
+ evt.stopPropagation();
+ evt.preventDefault();
+ });
+
+ mxEvent.addListener(ldiv, 'dragstart', function(evt) {
+ dragSource = ldiv;
+
+ // Workaround for no DnD on DIV in FF
+ if (mxClient.IS_FF) {
+ // LATER: Check what triggers a parse as XML on this in FF after drop
+ evt.dataTransfer.setData('Text', '');
+ }
+ });
+
+ mxEvent.addListener(ldiv, 'dragend', function(evt) {
+ if (dragSource != null && dropIndex != null) {
+ graph.addCell(child, graph.model.root, dropIndex);
+ }
+
+ dragSource = null;
+ dropIndex = null;
+ evt.stopPropagation();
+ evt.preventDefault();
+ });
+
+ var inp = document.createElement('img');
+ inp.setAttribute('draggable', 'false');
+ inp.setAttribute('align', 'top');
+ inp.setAttribute('border', '0');
+ inp.style.width = '16px';
+ inp.style.padding = '0px 6px 0 4px';
+ inp.style.marginTop = '2px';
+ inp.style.cursor = 'pointer';
+ inp.setAttribute('title', mxResources.get(
+ graph.model.isVisible(child) ?
+ 'hide' : 'show'));
+
+ if (graph.model.isVisible(child)) {
+ inp.setAttribute('src', Editor.visibleImage);
+ mxUtils.setOpacity(ldiv, 75);
+ }
+ else {
+ inp.setAttribute('src', Editor.hiddenImage);
+ mxUtils.setOpacity(ldiv, 25);
+ }
+
+ if (Editor.isDarkMode()) {
+ inp.style.filter = 'invert(100%)';
+ }
+
+ left.appendChild(inp);
+
+ mxEvent.addListener(inp, 'click', function(evt) {
+ graph.model.setVisible(child, !graph.model.isVisible(child));
+ mxEvent.consume(evt);
+ });
+
+ var btn = document.createElement('img');
+ btn.setAttribute('draggable', 'false');
+ btn.setAttribute('align', 'top');
+ btn.setAttribute('border', '0');
+ btn.style.width = '16px';
+ btn.style.padding = '0px 6px 0 0';
+ btn.style.marginTop = '2px';
+ btn.setAttribute('title', mxResources.get('lockUnlock'));
+
+ var style = graph.getCurrentCellStyle(child);
+
+ if (mxUtils.getValue(style, 'locked', '0') == '1') {
+ btn.setAttribute('src', Editor.lockedImage);
+ mxUtils.setOpacity(btn, 75);
+ }
+ else {
+ btn.setAttribute('src', Editor.unlockedImage);
+ mxUtils.setOpacity(btn, 25);
+ }
+
+ if (Editor.isDarkMode()) {
+ btn.style.filter = 'invert(100%)';
+ }
+
+ if (graph.isEnabled()) {
+ btn.style.cursor = 'pointer';
+ }
+
+ mxEvent.addListener(btn, 'click', function(evt) {
+ if (graph.isEnabled()) {
+ var value = null;
+
+ graph.getModel().beginUpdate();
+ try {
+ value = (mxUtils.getValue(style, 'locked', '0') == '1') ? null : '1';
+ graph.setCellStyles('locked', value, [child]);
+ }
+ finally {
+ graph.getModel().endUpdate();
+ }
+
+ if (value == '1') {
+ graph.removeSelectionCells(graph.getModel().getDescendants(child));
+ }
+
+ mxEvent.consume(evt);
+ }
+ });
+
+ left.appendChild(btn);
+
+ var span = document.createElement('span');
+ mxUtils.write(span, label);
+ span.style.display = 'block';
+ span.style.whiteSpace = 'nowrap';
+ span.style.overflow = 'hidden';
+ span.style.textOverflow = 'ellipsis';
+ span.style.position = 'absolute';
+ span.style.left = '52px';
+ span.style.right = '8px';
+ span.style.top = '8px';
+
+ left.appendChild(span);
+ ldiv.appendChild(left);
+
+ if (graph.isEnabled()) {
+ // Fallback if no drag and drop is available
+ if (mxClient.IS_TOUCH || mxClient.IS_POINTER ||
+ (mxClient.IS_IE && document.documentMode < 10)) {
+ var right = document.createElement('div');
+ right.style.display = 'block';
+ right.style.textAlign = 'right';
+ right.style.whiteSpace = 'nowrap';
+ right.style.position = 'absolute';
+ right.style.right = '16px';
+ right.style.top = '6px';
+
+ // Poor man's change layer order
+ if (index > 0) {
+ var img2 = document.createElement('a');
+
+ img2.setAttribute('title', mxResources.get('toBack'));
+
+ img2.className = 'geButton';
+ img2.style.cssFloat = 'none';
+ img2.innerHTML = '▼';
+ img2.style.width = '14px';
+ img2.style.height = '14px';
+ img2.style.fontSize = '14px';
+ img2.style.margin = '0px';
+ img2.style.marginTop = '-1px';
+ right.appendChild(img2);
+
+ mxEvent.addListener(img2, 'click', function(evt) {
+ if (graph.isEnabled()) {
+ graph.addCell(child, graph.model.root, index - 1);
+ }
+
+ mxEvent.consume(evt);
+ });
+ }
+
+ if (index >= 0 && index < layerCount - 1) {
+ var img1 = document.createElement('a');
+
+ img1.setAttribute('title', mxResources.get('toFront'));
+
+ img1.className = 'geButton';
+ img1.style.cssFloat = 'none';
+ img1.innerHTML = '▲';
+ img1.style.width = '14px';
+ img1.style.height = '14px';
+ img1.style.fontSize = '14px';
+ img1.style.margin = '0px';
+ img1.style.marginTop = '-1px';
+ right.appendChild(img1);
+
+ mxEvent.addListener(img1, 'click', function(evt) {
+ if (graph.isEnabled()) {
+ graph.addCell(child, graph.model.root, index + 1);
+ }
+
+ mxEvent.consume(evt);
+ });
+ }
+
+ ldiv.appendChild(right);
+ }
+
+ if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10)) {
+ ldiv.setAttribute('draggable', 'true');
+ ldiv.style.cursor = 'move';
+ }
+ }
+
+ mxEvent.addListener(ldiv, 'dblclick', function(evt) {
+ var nodeName = mxEvent.getSource(evt).nodeName;
+
+ if (nodeName != 'INPUT' && nodeName != 'IMG') {
+ renameLayer(child);
+ mxEvent.consume(evt);
+ }
+ });
+
+ if (graph.getDefaultParent() == child) {
+ ldiv.style.background = (!Editor.isDarkMode()) ? '#e6eff8' : '#505759';
+ ldiv.style.fontWeight = (graph.isEnabled()) ? 'bold' : '';
+ selectionLayer = child;
+ }
+
+ mxEvent.addListener(ldiv, 'click', function(evt) {
+ if (graph.isEnabled()) {
+ graph.setDefaultParent(defaultParent);
+ graph.view.setCurrentRoot(null);
+
+ if (mxEvent.isShiftDown(evt)) {
+ graph.setSelectionCells(child.children);
+ }
+
+ mxEvent.consume(evt);
+ }
+ });
+
+ listDiv.appendChild(ldiv);
+ };
+
+ // Cannot be moved or deleted
+ for (var i = layerCount - 1; i >= 0; i--) {
+ (mxUtils.bind(this, function(child) {
+ addLayer(i, graph.convertValueToString(child) ||
+ mxResources.get('background'), child, child);
+ }))(graph.model.getChildAt(graph.model.root, i));
+ }
+
+ var label = graph.convertValueToString(selectionLayer) || mxResources.get('background');
+ removeLink.setAttribute('title', mxResources.get('removeIt', [label]));
+ duplicateLink.setAttribute('title', mxResources.get('duplicateIt', [label]));
+
+ if (graph.isSelectionEmpty()) {
+ insertLink.className = 'geButton mxDisabled';
+ }
+
+ updateLayerDot();
+ };
+
+ refresh();
+ graph.model.addListener(mxEvent.CHANGE, refresh);
+ graph.addListener('defaultParentChanged', refresh);
+
+ graph.selectionModel.addListener(mxEvent.CHANGE, function() {
+ if (graph.isSelectionEmpty()) {
+ insertLink.className = 'geButton mxDisabled';
+ }
+ else {
+ insertLink.className = 'geButton';
+ }
+
+ updateLayerDot();
+ });
+
+ this.window = new mxWindow(mxResources.get('layers'), div, x, y, w, h, true, true);
+ this.window.minimumSize = new mxRectangle(0, 0, 150, 120);
+ this.window.destroyOnClose = false;
+ this.window.setMaximizable(false);
+ this.window.setResizable(true);
+ this.window.setClosable(true);
+ this.window.setVisible(true);
+
+ this.init = function() {
+ listDiv.scrollTop = listDiv.scrollHeight - listDiv.clientHeight;
+ };
+
+ this.window.addListener(mxEvent.SHOW, mxUtils.bind(this, function() {
+ this.window.fit();
+ }));
+
+ // Make refresh available via instance
+ this.refreshLayers = refresh;
+ editorUi.installResizeHandler(this, true);
+};
diff --git a/oaweb/public/cherry/drawio/Editor.js b/oaweb/public/cherry/drawio/Editor.js
new file mode 100644
index 0000000..54bfebd
--- /dev/null
+++ b/oaweb/public/cherry/drawio/Editor.js
@@ -0,0 +1,3212 @@
+/**
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Editor constructor executed on page load.
+ */
+Editor = function(chromeless, themes, model, graph, editable)
+{
+ mxEventSource.call(this);
+ this.chromeless = (chromeless != null) ? chromeless : this.chromeless;
+ this.initStencilRegistry();
+ this.graph = graph || this.createGraph(themes, model);
+ this.editable = (editable != null) ? editable : !chromeless;
+ this.undoManager = this.createUndoManager();
+ this.status = '';
+
+ this.getOrCreateFilename = function()
+ {
+ return this.filename || mxResources.get('drawing', [Editor.pageCounter]) + '.xml';
+ };
+
+ this.getFilename = function()
+ {
+ return this.filename;
+ };
+
+ // Sets the status and fires a statusChanged event
+ this.setStatus = function(value, fn)
+ {
+ this.status = value;
+ this.statusFunction = fn;
+ this.fireEvent(new mxEventObject('statusChanged'));
+ };
+
+ // Returns the current status
+ this.getStatus = function()
+ {
+ return this.status;
+ };
+
+ // Updates modified state if graph changes
+ this.graphChangeListener = function(sender, eventObject)
+ {
+ var edit = (eventObject != null) ? eventObject.getProperty('edit') : null;
+
+ if (edit == null || !edit.ignoreEdit)
+ {
+ this.setModified(true);
+ }
+ };
+
+ this.graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function()
+ {
+ this.graphChangeListener.apply(this, arguments);
+ }));
+
+ // Sets persistent graph state defaults
+ this.graph.resetViewOnRootChange = false;
+ this.init();
+};
+
+/**
+ * Counts open editor tabs (must be global for cross-window access)
+ */
+Editor.pageCounter = 0;
+
+// Cross-domain window access is not allowed in FF, so if we
+// were opened from another domain then this will fail.
+(function()
+{
+ try
+ {
+ var op = window;
+
+ while (op.opener != null && typeof op.opener.Editor !== 'undefined' &&
+ !isNaN(op.opener.Editor.pageCounter) &&
+ // Workaround for possible infinite loop in FF https://drawio.atlassian.net/browse/DS-795
+ op.opener != op)
+ {
+ op = op.opener;
+ }
+
+ // Increments the counter in the first opener in the chain
+ if (op != null)
+ {
+ op.Editor.pageCounter++;
+ Editor.pageCounter = op.Editor.pageCounter;
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+})();
+
+/**
+ *
+ */
+Editor.defaultHtmlFont = '-apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", system-ui, ui-sans-serif, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"';
+
+/**
+ * Specifies if local storage should be used (eg. on the iPad which has no filesystem)
+ */
+Editor.useLocalStorage = typeof(Storage) != 'undefined' && mxClient.IS_IOS;
+
+/**
+ * Window width for simple mode to collapse panels.
+ */
+Editor.smallScreenWidth = 800;
+
+/**
+ *
+ */
+Editor.lightCheckmarkImage = '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=';
+Editor.darkCheckmarkImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAMAAACeyVWkAAAARVBMVEUAAACZmZkICAgEBASNjY2Dg4MYGBiTk5N5eXl1dXVmZmZQUFBCQkI3NzceHh4MDAykpKSJiYl+fn5sbGxaWlo/Pz8SEhK96uPlAAAAAXRSTlMAQObYZgAAAE5JREFUGNPFzTcSgDAQQ1HJGUfy/Y9K7V1qeOUfzQifCQZai1XHaz11LFysbDbzgDSSWMZiETz3+b8yNUc/MMsktxuC8XQBSncdLwz+8gCCggGXzBcozAAAAABJRU5ErkJggg==';
+Editor.darkHelpImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAP1BMVEUAAAD///////////////////////////////////////////////////////////////////////////////9Du/pqAAAAFXRSTlMAT30qCJRBboyDZyCgRzUUdF46MJlgXETgAAAAeklEQVQY022O2w4DIQhEQUURda/9/28tUO2+7CQS5sgQ4F1RapX78YUwRqQjTU8ILqQfKerTKTvACJ4nLX3krt+8aS82oI8aQC4KavRgtvEW/mDvsICgA03PSGRr79MqX1YPNIxzjyqtw8ZnnRo4t5a5undtJYRywau+ds4Cyza3E6YAAAAASUVORK5CYII=';
+Editor.lightHelpImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJub25lIiBkPSJNMCAwaDI0djI0SDB6Ii8+PHBhdGggZD0iTTExIDE4aDJ2LTJoLTJ2MnptMS0xNkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6bTAtMTRjLTIuMjEgMC00IDEuNzktNCA0aDJjMC0xLjEuOS0yIDItMnMyIC45IDIgMmMwIDItMyAxLjc1LTMgNWgyYzAtMi4yNSAzLTIuNSAzLTUgMC0yLjIxLTEuNzktNC00LTR6Ii8+PC9zdmc+';
+Editor.menuImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTMgMThoMTh2LTJIM3Yyem0wLTVoMTh2LTJIM3Yyem0wLTd2MmgxOFY2SDN6Ii8+PC9zdmc+';
+Editor.moveImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI4cHgiIGhlaWdodD0iMjhweCI+PGc+PC9nPjxnPjxnPjxnPjxwYXRoIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIuNCwyLjQpc2NhbGUoMC44KXJvdGF0ZSg0NSwxMiwxMikiIHN0cm9rZT0iIzI5YjZmMiIgZmlsbD0iIzI5YjZmMiIgZD0iTTE1LDNsMi4zLDIuM2wtMi44OSwyLjg3bDEuNDIsMS40MkwxOC43LDYuN0wyMSw5VjNIMTV6IE0zLDlsMi4zLTIuM2wyLjg3LDIuODlsMS40Mi0xLjQyTDYuNyw1LjNMOSwzSDNWOXogTTksMjEgbC0yLjMtMi4zbDIuODktMi44N2wtMS40Mi0xLjQyTDUuMywxNy4zTDMsMTV2Nkg5eiBNMjEsMTVsLTIuMywyLjNsLTIuODctMi44OWwtMS40MiwxLjQybDIuODksMi44N0wxNSwyMWg2VjE1eiIvPjwvZz48L2c+PC9nPjwvc3ZnPgo=';
+Editor.zoomInImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTUuNSAxNGgtLjc5bC0uMjgtLjI3QzE1LjQxIDEyLjU5IDE2IDExLjExIDE2IDkuNSAxNiA1LjkxIDEzLjA5IDMgOS41IDNTMyA1LjkxIDMgOS41IDUuOTEgMTYgOS41IDE2YzEuNjEgMCAzLjA5LS41OSA0LjIzLTEuNTdsLjI3LjI4di43OWw1IDQuOTlMMjAuNDkgMTlsLTQuOTktNXptLTYgMEM3LjAxIDE0IDUgMTEuOTkgNSA5LjVTNy4wMSA1IDkuNSA1IDE0IDcuMDEgMTQgOS41IDExLjk5IDE0IDkuNSAxNHptMi41LTRoLTJ2Mkg5di0ySDdWOWgyVjdoMXYyaDJ2MXoiLz48L3N2Zz4=';
+Editor.zoomOutImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTUuNSAxNGgtLjc5bC0uMjgtLjI3QzE1LjQxIDEyLjU5IDE2IDExLjExIDE2IDkuNSAxNiA1LjkxIDEzLjA5IDMgOS41IDNTMyA1LjkxIDMgOS41IDUuOTEgMTYgOS41IDE2YzEuNjEgMCAzLjA5LS41OSA0LjIzLTEuNTdsLjI3LjI4di43OWw1IDQuOTlMMjAuNDkgMTlsLTQuOTktNXptLTYgMEM3LjAxIDE0IDUgMTEuOTkgNSA5LjVTNy4wMSA1IDkuNSA1IDE0IDcuMDEgMTQgOS41IDExLjk5IDE0IDkuNSAxNHpNNyA5aDV2MUg3eiIvPjwvc3ZnPg==';
+Editor.fullscreenImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMyA1djRoMlY1aDRWM0g1Yy0xLjEgMC0yIC45LTIgMnptMiAxMEgzdjRjMCAxLjEuOSAyIDIgMmg0di0ySDV2LTR6bTE0IDRoLTR2Mmg0YzEuMSAwIDItLjkgMi0ydi00aC0ydjR6bTAtMTZoLTR2Mmg0djRoMlY1YzAtMS4xLS45LTItMi0yeiIvPjwvc3ZnPg==';
+Editor.fullscreenExitImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTUgMTZoM3YzaDJ2LTVINXYyem0zLThINXYyaDVWNUg4djN6bTYgMTFoMnYtM2gzdi0yaC01djV6bTItMTFWNWgtMnY1aDVWOGgtM3oiLz48L3N2Zz4=';
+Editor.zoomFitImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTUgMTVIM3Y0YzAgMS4xLjkgMiAyIDJoNHYtMkg1di00ek01IDVoNFYzSDVjLTEuMSAwLTIgLjktMiAydjRoMlY1em03IDNjLTIuMjEgMC00IDEuNzktNCA0czEuNzkgNCA0IDQgNC0xLjc5IDQtNC0xLjc5LTQtNC00em0wIDZjLTEuMSAwLTItLjktMi0ycy45LTIgMi0yIDIgLjkgMiAyLS45IDItMiAyem03LTExaC00djJoNHY0aDJWNWMwLTEuMS0uOS0yLTItMnptMCAxNmgtNHYyaDRjMS4xIDAgMi0uOSAyLTJ2LTRoLTJ2NHoiLz48L3N2Zz4=';
+Editor.layersImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTExLjk5IDE4LjU0bC03LjM3LTUuNzNMMyAxNC4wN2w5IDcgOS03LTEuNjMtMS4yN3pNMTIgMTZsNy4zNi01LjczTDIxIDlsLTktNy05IDcgMS42MyAxLjI3TDEyIDE2em0wLTExLjQ3TDE3Ljc0IDkgMTIgMTMuNDcgNi4yNiA5IDEyIDQuNTN6Ii8+PC9zdmc+';
+Editor.previousImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE1LjQxIDcuNDFMMTQgNmwtNiA2IDYgNiAxLjQxLTEuNDFMMTAuODMgMTJsNC41OC00LjU5eiIvPjwvc3ZnPg==';
+Editor.nextImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02LTYtNnoiLz48L3N2Zz4=';
+Editor.editImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE0LjA2IDkuMDJsLjkyLjkyTDUuOTIgMTlINXYtLjkybDkuMDYtOS4wNk0xNy42NiAzYy0uMjUgMC0uNTEuMS0uNy4yOWwtMS44MyAxLjgzIDMuNzUgMy43NSAxLjgzLTEuODNjLjM5LS4zOS4zOS0xLjAyIDAtMS40MWwtMi4zNC0yLjM0Yy0uMi0uMi0uNDUtLjI5LS43MS0uMjl6bS0zLjYgMy4xOUwzIDE3LjI1VjIxaDMuNzVMMTcuODEgOS45NGwtMy43NS0zLjc1eiIvPjwvc3ZnPg==';
+Editor.duplicateImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE2IDFINGMtMS4xIDAtMiAuOS0yIDJ2MTRoMlYzaDEyVjF6bTMgNEg4Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDExYzEuMSAwIDItLjkgMi0yVjdjMC0xLjEtLjktMi0yLTJ6bTAgMTZIOFY3aDExdjE0eiIvPjwvc3ZnPg==';
+Editor.addImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE5IDEzaC02djZoLTJ2LTZINXYtMmg2VjVoMnY2aDZ2MnoiLz48L3N2Zz4=';
+Editor.crossImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE5IDYuNDFMMTcuNTkgNSAxMiAxMC41OSA2LjQxIDUgNSA2LjQxIDEwLjU5IDEyIDUgMTcuNTkgNi40MSAxOSAxMiAxMy40MSAxNy41OSAxOSAxOSAxNy41OSAxMy40MSAxMiAxOSA2LjQxeiIvPjwvc3ZnPg==';
+Editor.verticalDotsImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEyIDhjMS4xIDAgMi0uOSAyLTJzLS45LTItMi0yLTIgLjktMiAyIC45IDIgMiAyem0wIDJjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yem0wIDZjLTEuMSAwLTIgLjktMiAycy45IDIgMiAyIDItLjkgMi0yLS45LTItMi0yeiIvPjwvc3ZnPg==';
+Editor.trashImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE2IDl2MTBIOFY5aDhtLTEuNS02aC01bC0xIDFINXYyaDE0VjRoLTMuNWwtMS0xek0xOCA3SDZ2MTJjMCAxLjEuOSAyIDIgMmg4YzEuMSAwIDItLjkgMi0yVjd6Ii8+PC9zdmc+';
+Editor.hiddenImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6bTAgMGgyNHYyNEgwVjB6bTAgMGgyNHYyNEgwVjB6bTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEyIDZjMy43OSAwIDcuMTcgMi4xMyA4LjgyIDUuNS0uNTkgMS4yMi0xLjQyIDIuMjctMi40MSAzLjEybDEuNDEgMS40MWMxLjM5LTEuMjMgMi40OS0yLjc3IDMuMTgtNC41M0MyMS4yNyA3LjExIDE3IDQgMTIgNGMtMS4yNyAwLTIuNDkuMi0zLjY0LjU3bDEuNjUgMS42NUMxMC42NiA2LjA5IDExLjMyIDYgMTIgNnptLTEuMDcgMS4xNEwxMyA5LjIxYy41Ny4yNSAxLjAzLjcxIDEuMjggMS4yOGwyLjA3IDIuMDdjLjA4LS4zNC4xNC0uNy4xNC0xLjA3QzE2LjUgOS4wMSAxNC40OCA3IDEyIDdjLS4zNyAwLS43Mi4wNS0xLjA3LjE0ek0yLjAxIDMuODdsMi42OCAyLjY4QzMuMDYgNy44MyAxLjc3IDkuNTMgMSAxMS41IDIuNzMgMTUuODkgNyAxOSAxMiAxOWMxLjUyIDAgMi45OC0uMjkgNC4zMi0uODJsMy40MiAzLjQyIDEuNDEtMS40MUwzLjQyIDIuNDUgMi4wMSAzLjg3em03LjUgNy41bDIuNjEgMi42MWMtLjA0LjAxLS4wOC4wMi0uMTIuMDItMS4zOCAwLTIuNS0xLjEyLTIuNS0yLjUgMC0uMDUuMDEtLjA4LjAxLS4xM3ptLTMuNC0zLjRsMS43NSAxLjc1Yy0uMjMuNTUtLjM2IDEuMTUtLjM2IDEuNzggMCAyLjQ4IDIuMDIgNC41IDQuNSA0LjUuNjMgMCAxLjIzLS4xMyAxLjc3LS4zNmwuOTguOThjLS44OC4yNC0xLjguMzgtMi43NS4zOC0zLjc5IDAtNy4xNy0yLjEzLTguODItNS41LjctMS40MyAxLjcyLTIuNjEgMi45My0zLjUzeiIvPjwvc3ZnPg==';
+Editor.visibleImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEyIDZjMy43OSAwIDcuMTcgMi4xMyA4LjgyIDUuNUMxOS4xNyAxNC44NyAxNS43OSAxNyAxMiAxN3MtNy4xNy0yLjEzLTguODItNS41QzQuODMgOC4xMyA4LjIxIDYgMTIgNm0wLTJDNyA0IDIuNzMgNy4xMSAxIDExLjUgMi43MyAxNS44OSA3IDE5IDEyIDE5czkuMjctMy4xMSAxMS03LjVDMjEuMjcgNy4xMSAxNyA0IDEyIDR6bTAgNWMxLjM4IDAgMi41IDEuMTIgMi41IDIuNVMxMy4zOCAxNCAxMiAxNHMtMi41LTEuMTItMi41LTIuNVMxMC42MiA5IDEyIDltMC0yYy0yLjQ4IDAtNC41IDIuMDItNC41IDQuNVM5LjUyIDE2IDEyIDE2czQuNS0yLjAyIDQuNS00LjVTMTQuNDggNyAxMiA3eiIvPjwvc3ZnPg==';
+Editor.lockedImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PGcgZmlsbD0ibm9uZSI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6Ii8+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBvcGFjaXR5PSIuODciLz48L2c+PHBhdGggZD0iTTE4IDhoLTFWNmMwLTIuNzYtMi4yNC01LTUtNVM3IDMuMjQgNyA2djJINmMtMS4xIDAtMiAuOS0yIDJ2MTBjMCAxLjEuOSAyIDIgMmgxMmMxLjEgMCAyLS45IDItMlYxMGMwLTEuMS0uOS0yLTItMnpNOSA2YzAtMS42NiAxLjM0LTMgMy0zczMgMS4zNCAzIDN2Mkg5VjZ6bTkgMTRINlYxMGgxMnYxMHptLTYtM2MxLjEgMCAyLS45IDItMnMtLjktMi0yLTItMiAuOS0yIDIgLjkgMiAyIDJ6Ii8+PC9zdmc+';
+Editor.unlockedImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE4IDhoLTFWNmMwLTIuNzYtMi4yNC01LTUtNVM3IDMuMjQgNyA2aDJjMC0xLjY2IDEuMzQtMyAzLTNzMyAxLjM0IDMgM3YySDZjLTEuMSAwLTIgLjktMiAydjEwYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWMTBjMC0xLjEtLjktMi0yLTJ6bTAgMTJINlYxMGgxMnYxMHptLTYtM2MxLjEgMCAyLS45IDItMnMtLjktMi0yLTItMiAuOS0yIDIgLjkgMiAyIDJ6Ii8+PC9zdmc+';
+Editor.printImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE5IDhoLTFWM0g2djVINWMtMS42NiAwLTMgMS4zNC0zIDN2Nmg0djRoMTJ2LTRoNHYtNmMwLTEuNjYtMS4zNC0zLTMtM3pNOCA1aDh2M0g4VjV6bTggMTJ2Mkg4di00aDh2MnptMi0ydi0ySDZ2Mkg0di00YzAtLjU1LjQ1LTEgMS0xaDE0Yy41NSAwIDEgLjQ1IDEgMXY0aC0yeiIvPjxjaXJjbGUgY3g9IjE4IiBjeT0iMTEuNSIgcj0iMSIvPjwvc3ZnPg==';
+Editor.refreshImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE3LjY1IDYuMzVDMTYuMiA0LjkgMTQuMjEgNCAxMiA0Yy00LjQyIDAtNy45OSAzLjU4LTcuOTkgOHMzLjU3IDggNy45OSA4YzMuNzMgMCA2Ljg0LTIuNTUgNy43My02aC0yLjA4Yy0uODIgMi4zMy0zLjA0IDQtNS42NSA0LTMuMzEgMC02LTIuNjktNi02czIuNjktNiA2LTZjMS42NiAwIDMuMTQuNjkgNC4yMiAxLjc4TDEzIDExaDdWNGwtMi4zNSAyLjM1eiIvPjwvc3ZnPg==';
+Editor.backImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIiBvcGFjaXR5PSIuODciLz48cGF0aCBkPSJNMTcuNTEgMy44N0wxNS43MyAyLjEgNS44NCAxMmw5LjkgOS45IDEuNzctMS43N0w5LjM4IDEybDguMTMtOC4xM3oiLz48L3N2Zz4=';
+Editor.closeImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIiBvcGFjaXR5PSIuODciLz48cGF0aCBkPSJNMTIgMkM2LjQ3IDIgMiA2LjQ3IDIgMTJzNC40NyAxMCAxMCAxMCAxMC00LjQ3IDEwLTEwUzE3LjUzIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6bTMuNTktMTNMMTIgMTAuNTkgOC40MSA3IDcgOC40MSAxMC41OSAxMiA3IDE1LjU5IDguNDEgMTcgMTIgMTMuNDEgMTUuNTkgMTcgMTcgMTUuNTkgMTMuNDEgMTIgMTcgOC40MXoiLz48L3N2Zz4='
+Editor.closeBlackImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjZweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjZweCI+PGVsbGlwc2UgY3g9IjEyIiBjeT0iMTIiIHJ4PSI5IiByeT0iOSIgc3Ryb2tlPSJub25lIiBmaWxsPSIjMDAwIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE0LjU5IDhMMTIgMTAuNTkgOS40MSA4IDggOS40MSAxMC41OSAxMiA4IDE0LjU5IDkuNDEgMTYgMTIgMTMuNDEgMTQuNTkgMTYgMTYgMTQuNTkgMTMuNDEgMTIgMTYgOS40MSAxNC41OSA4ek0xMiAyQzYuNDcgMiAyIDYuNDcgMiAxMnM0LjQ3IDEwIDEwIDEwIDEwLTQuNDcgMTAtMTBTMTcuNTMgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDgtMy41OSA4LTggOHoiLz48L3N2Zz4=';
+Editor.minusImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iNDgiIHdpZHRoPSI0OCI+PHBhdGggZD0iTTEwIDI1LjV2LTNoMjh2M1oiLz48L3N2Zz4=';
+Editor.plusImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgMTNoLTZ2NmgtMnYtNkg1di0yaDZWNWgydjZoNnYyeiIvPjwvc3ZnPg==';
+Editor.addBoxImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0iTTExIDE3aDJ2LTRoNHYtMmgtNFY3aC0ydjRIN3YyaDRabS02IDRxLS44MjUgMC0xLjQxMy0uNTg3UTMgMTkuODI1IDMgMTlWNXEwLS44MjUuNTg3LTEuNDEzUTQuMTc1IDMgNSAzaDE0cS44MjUgMCAxLjQxMy41ODdRMjEgNC4xNzUgMjEgNXYxNHEwIC44MjUtLjU4NyAxLjQxM1ExOS44MjUgMjEgMTkgMjFabTAtMmgxNFY1SDV2MTRaTTUgNXYxNFY1WiIvPjwvc3ZnPg==';
+Editor.shapesImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0iTTE1IDE1Wm0tNyAyLjk1cS4yNS4wMjUuNDg4LjAzOFE4LjcyNSAxOCA5IDE4dC41MTItLjAxMnEuMjM4LS4wMTMuNDg4LS4wMzhWMjBoMTBWMTBoLTIuMDVxLjAyNS0uMjUuMDM4LS40ODhRMTggOS4yNzUgMTggOXQtLjAxMi0uNTEyUTE3Ljk3NSA4LjI1IDE3Ljk1IDhIMjBxLjgyNSAwIDEuNDEzLjU4N1EyMiA5LjE3NSAyMiAxMHYxMHEwIC44MjUtLjU4NyAxLjQxM1EyMC44MjUgMjIgMjAgMjJIMTBxLS44MjUgMC0xLjQxMi0uNTg3UTggMjAuODI1IDggMjBaTTkgMTZxLTIuOTI1IDAtNC45NjMtMi4wMzhRMiAxMS45MjUgMiA5dDIuMDM3LTQuOTYzUTYuMDc1IDIgOSAycTIuOTI1IDAgNC45NjMgMi4wMzdRMTYgNi4wNzUgMTYgOXEwIDIuOTI1LTIuMDM3IDQuOTYyUTExLjkyNSAxNiA5IDE2Wm0wLTJxMi4wNzUgMCAzLjUzOC0xLjQ2M1ExNCAxMS4wNzUgMTQgOXQtMS40NjItMy41MzdRMTEuMDc1IDQgOSA0IDYuOTI1IDQgNS40NjMgNS40NjMgNCA2LjkyNSA0IDl0MS40NjMgMy41MzdRNi45MjUgMTQgOSAxNFptMC01WiIvPjwvc3ZnPg==';
+Editor.formatImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0iTTExIDIycS0uODI1IDAtMS40MTItLjU4N1E5IDIwLjgyNSA5IDIwdi00SDZxLS44MjUgMC0xLjQxMi0uNTg4UTQgMTQuODI1IDQgMTRWN3EwLTEuNjUgMS4xNzUtMi44MjVRNi4zNSAzIDggM2gxMnYxMXEwIC44MjUtLjU4NyAxLjQxMlExOC44MjUgMTYgMTggMTZoLTN2NHEwIC44MjUtLjU4NyAxLjQxM1ExMy44MjUgMjIgMTMgMjJaTTYgMTBoMTJWNWgtMXY0aC0yVjVoLTF2MmgtMlY1SDhxLS44MjUgMC0xLjQxMi41ODhRNiA2LjE3NSA2IDdabTAgNGgxMnYtMkg2djJabTAtMnYyWiIvPjwvc3ZnPg==';
+Editor.freehandImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iIzAwMDAwMCI+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIyNCIgd2lkdGg9IjI0Ii8+PHBhdGggZD0iTTQuNSw4YzEuMDQsMCwyLjM0LTEuNSw0LjI1LTEuNWMxLjUyLDAsMi43NSwxLjIzLDIuNzUsMi43NWMwLDIuMDQtMS45OSwzLjE1LTMuOTEsNC4yMkM1LjQyLDE0LjY3LDQsMTUuNTcsNCwxNyBjMCwxLjEsMC45LDIsMiwydjJjLTIuMjEsMC00LTEuNzktNC00YzAtMi43MSwyLjU2LTQuMTQsNC42Mi01LjI4YzEuNDItMC43OSwyLjg4LTEuNiwyLjg4LTIuNDdjMC0wLjQxLTAuMzQtMC43NS0wLjc1LTAuNzUgQzcuNSw4LjUsNi4yNSwxMCw0LjUsMTBDMy4xMiwxMCwyLDguODgsMiw3LjVDMiw1LjQ1LDQuMTcsMi44Myw1LDJsMS40MSwxLjQxQzUuNDEsNC40Miw0LDYuNDMsNCw3LjVDNCw3Ljc4LDQuMjIsOCw0LjUsOHogTTgsMjEgbDMuNzUsMGw4LjA2LTguMDZsLTMuNzUtMy43NUw4LDE3LjI1TDgsMjF6IE0xMCwxOC4wOGw2LjA2LTYuMDZsMC45MiwwLjkyTDEwLjkyLDE5TDEwLDE5TDEwLDE4LjA4eiBNMjAuMzcsNi4yOSBjLTAuMzktMC4zOS0xLjAyLTAuMzktMS40MSwwbC0xLjgzLDEuODNsMy43NSwzLjc1bDEuODMtMS44M2MwLjM5LTAuMzksMC4zOS0xLjAyLDAtMS40MUwyMC4zNyw2LjI5eiIvPjwvc3ZnPg==';
+Editor.undoImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIuNSA4Yy0yLjY1IDAtNS4wNS45OS02LjkgMi42TDIgN3Y5aDlsLTMuNjItMy42MmMxLjM5LTEuMTYgMy4xNi0xLjg4IDUuMTItMS44OCAzLjU0IDAgNi41NSAyLjMxIDcuNiA1LjVsMi4zNy0uNzhDMjEuMDggMTEuMDMgMTcuMTUgOCAxMi41IDh6Ii8+PC9zdmc+';
+Editor.redoImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTguNCAxMC42QzE2LjU1IDguOTkgMTQuMTUgOCAxMS41IDhjLTQuNjUgMC04LjU4IDMuMDMtOS45NiA3LjIyTDMuOSAxNmMxLjA1LTMuMTkgNC4wNS01LjUgNy42LTUuNSAxLjk1IDAgMy43My43MiA1LjEyIDEuODhMMTMgMTZoOVY3bC0zLjYgMy42eiIvPjwvc3ZnPg==';
+Editor.outlineImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0ibTE1IDIxLTYtMi4xLTQuNjUgMS44cS0uNS4yLS45MjUtLjExM1EzIDIwLjI3NSAzIDE5Ljc1di0xNHEwLS4zMjUuMTg4LS41NzUuMTg3LS4yNS41MTItLjM3NUw5IDNsNiAyLjEgNC42NS0xLjhxLjUtLjIuOTI1LjExMi40MjUuMzEzLjQyNS44Mzh2MTRxMCAuMzI1LS4xODguNTc1LS4xODcuMjUtLjUxMi4zNzVabS0xLTIuNDVWNi44NWwtNC0xLjR2MTEuN1ptMiAwIDMtMVY1LjdsLTMgMS4xNVpNNSAxOC4zbDMtMS4xNVY1LjQ1bC0zIDFaTTE2IDYuODV2MTEuN1ptLTgtMS40djExLjdaIi8+PC9zdmc+';
+Editor.saveImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE5IDEydjdINXYtN0gzdjdjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMnYtN2gtMnptLTYgLjY3bDIuNTktMi41OEwxNyAxMS41bC01IDUtNS01IDEuNDEtMS40MUwxMSAxMi42N1YzaDJ2OS42N3oiLz48L3N2Zz4=';
+Editor.compareImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iNDgiIHdpZHRoPSI0OCI+PHBhdGggZD0ibTE1Ljg1IDQwLTIuMS0yLjEgNi4wNS02LjA1SDR2LTNoMTUuOGwtNi4wNS02LjA1IDIuMS0yLjEgOS42NSA5LjY1Wm0xNi4zLTEyLjctOS42NS05LjY1TDMyLjE1IDhsMi4xIDIuMS02LjA1IDYuMDVINDR2M0gyOC4ybDYuMDUgNi4wNVoiLz48L3N2Zz4=';
+Editor.expandMoreImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0ibTEyIDE1LjM3NS02LTYgMS40LTEuNCA0LjYgNC42IDQuNi00LjYgMS40IDEuNFoiLz48L3N2Zz4=';
+Editor.expandLessImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0ibTcuNCAxNS4zNzUtMS40LTEuNCA2LTYgNiA2LTEuNCAxLjQtNC42LTQuNloiLz48L3N2Zz4=';
+Editor.gearImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0ibTkuMjUgMjItLjQtMy4ycS0uMzI1LS4xMjUtLjYxMi0uMy0uMjg4LS4xNzUtLjU2My0uMzc1TDQuNyAxOS4zNzVsLTIuNzUtNC43NSAyLjU3NS0xLjk1UTQuNSAxMi41IDQuNSAxMi4zMzd2LS42NzVxMC0uMTYyLjAyNS0uMzM3TDEuOTUgOS4zNzVsMi43NS00Ljc1IDIuOTc1IDEuMjVxLjI3NS0uMi41NzUtLjM3NS4zLS4xNzUuNi0uM2wuNC0zLjJoNS41bC40IDMuMnEuMzI1LjEyNS42MTMuMy4yODcuMTc1LjU2Mi4zNzVsMi45NzUtMS4yNSAyLjc1IDQuNzUtMi41NzUgMS45NXEuMDI1LjE3NS4wMjUuMzM3di42NzVxMCAuMTYzLS4wNS4zMzhsMi41NzUgMS45NS0yLjc1IDQuNzUtMi45NS0xLjI1cS0uMjc1LjItLjU3NS4zNzUtLjMuMTc1LS42LjNsLS40IDMuMlptMi44LTYuNXExLjQ1IDAgMi40NzUtMS4wMjVRMTUuNTUgMTMuNDUgMTUuNTUgMTJxMC0xLjQ1LTEuMDI1LTIuNDc1UTEzLjUgOC41IDEyLjA1IDguNXEtMS40NzUgMC0yLjQ4OCAxLjAyNVE4LjU1IDEwLjU1IDguNTUgMTJxMCAxLjQ1IDEuMDEyIDIuNDc1UTEwLjU3NSAxNS41IDEyLjA1IDE1LjVabTAtMnEtLjYyNSAwLTEuMDYyLS40MzgtLjQzOC0uNDM3LS40MzgtMS4wNjJ0LjQzOC0xLjA2MnEuNDM3LS40MzggMS4wNjItLjQzOHQxLjA2My40MzhxLjQzNy40MzcuNDM3IDEuMDYydC0uNDM3IDEuMDYycS0uNDM4LjQzOC0xLjA2My40MzhaTTEyIDEyWm0tMSA4aDEuOTc1bC4zNS0yLjY1cS43NzUtLjIgMS40MzgtLjU4OC42NjItLjM4NyAxLjIxMi0uOTM3bDIuNDc1IDEuMDI1Ljk3NS0xLjctMi4xNS0xLjYyNXEuMTI1LS4zNS4xNzUtLjczOC4wNS0uMzg3LjA1LS43ODd0LS4wNS0uNzg4cS0uMDUtLjM4Ny0uMTc1LS43MzdsMi4xNS0xLjYyNS0uOTc1LTEuNy0yLjQ3NSAxLjA1cS0uNTUtLjU3NS0xLjIxMi0uOTYzLS42NjMtLjM4Ny0xLjQzOC0uNTg3TDEzIDRoLTEuOTc1bC0uMzUgMi42NXEtLjc3NS4yLTEuNDM3LjU4Ny0uNjYzLjM4OC0xLjIxMy45MzhMNS41NSA3LjE1bC0uOTc1IDEuNyAyLjE1IDEuNnEtLjEyNS4zNzUtLjE3NS43NS0uMDUuMzc1LS4wNS44IDAgLjQuMDUuNzc1dC4xNzUuNzVsLTIuMTUgMS42MjUuOTc1IDEuNyAyLjQ3NS0xLjA1cS41NS41NzUgMS4yMTMuOTYyLjY2Mi4zODggMS40MzcuNTg4WiIvPjwvc3ZnPg==';
+Editor.extensionImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0iTTguOCAyMUg1cS0uODI1IDAtMS40MTMtLjU4N1EzIDE5LjgyNSAzIDE5di0zLjhxMS4yIDAgMi4xLS43NjIuOS0uNzYzLjktMS45MzggMC0xLjE3NS0uOS0xLjkzOFE0LjIgOS44IDMgOS44VjZxMC0uODI1LjU4Ny0xLjQxMlE0LjE3NSA0IDUgNGg0cTAtMS4wNS43MjUtMS43NzVRMTAuNDUgMS41IDExLjUgMS41cTEuMDUgMCAxLjc3NS43MjVRMTQgMi45NSAxNCA0aDRxLjgyNSAwIDEuNDEzLjU4OFEyMCA1LjE3NSAyMCA2djRxMS4wNSAwIDEuNzc1LjcyNS43MjUuNzI1LjcyNSAxLjc3NSAwIDEuMDUtLjcyNSAxLjc3NVEyMS4wNSAxNSAyMCAxNXY0cTAgLjgyNS0uNTg3IDEuNDEzUTE4LjgyNSAyMSAxOCAyMWgtMy44cTAtMS4yNS0uNzg3LTIuMTI1UTEyLjYyNSAxOCAxMS41IDE4dC0xLjkxMi44NzVROC44IDE5Ljc1IDguOCAyMVpNNSAxOWgyLjEyNXEuNi0xLjY1IDEuOTI1LTIuMzI1UTEwLjM3NSAxNiAxMS41IDE2cTEuMTI1IDAgMi40NS42NzUgMS4zMjUuNjc1IDEuOTI1IDIuMzI1SDE4di02aDJxLjIgMCAuMzUtLjE1LjE1LS4xNS4xNS0uMzUgMC0uMi0uMTUtLjM1UTIwLjIgMTIgMjAgMTJoLTJWNmgtNlY0cTAtLjItLjE1LS4zNS0uMTUtLjE1LS4zNS0uMTUtLjIgMC0uMzUuMTVRMTEgMy44IDExIDR2Mkg1djIuMnExLjM1LjUgMi4xNzUgMS42NzVROCAxMS4wNSA4IDEyLjVxMCAxLjQyNS0uODI1IDIuNlQ1IDE2LjhabTcuNzUtNy43NVoiLz48L3N2Zz4=';
+Editor.colorDropperImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iNDgiIHdpZHRoPSI0OCI+PHBhdGggZD0iTTYgNDJ2LTguNGwxOC44NS0xOC44NS0zLjYtMy42TDIzLjMgOS4xbDQuNiA0LjZMMzUgNi42cS41NS0uNTUgMS4xNzUtLjU1dDEuMTc1LjU1bDQuMDUgNC4wNXEuNTUuNTUuNTUgMS4xNzVUNDEuNCAxM2wtNy4xIDcuMSA0LjYgNC42LTIuMDUgMi4wNS0zLjYtMy42TDE0LjQgNDJabTMtM2g0LjM1TDMxLjEgMjEuMjVsLTQuMzUtNC4zNUw5IDM0LjY1Wm0yMy4xNS0yMSA2LjItNi4yLTIuMTUtMi4xNS02LjIgNi4yWm0wIDBMMzAgMTUuODUgMzIuMTUgMThaIi8+PC9zdmc+';
+Editor.helpImage = Editor.lightHelpImage;
+Editor.checkmarkImage = Editor.lightCheckmarkImage;
+
+/**
+ * All fill styles supported by rough.js.
+ */
+Editor.roughFillStyles = [{val: 'auto', dispName: 'Auto'}, {val: 'hachure', dispName: 'Hachure'},
+ {val: 'solid', dispName: 'Solid'}, {val: 'zigzag', dispName: 'ZigZag'},
+ {val: 'cross-hatch', dispName: 'Cross Hatch'}, {val: 'dashed', dispName: 'Dashed'},
+ {val: 'zigzag-line', dispName: 'ZigZag Line'}];
+
+/**
+ * Fill styles for normal mode.
+ */
+Editor.fillStyles = [{val: 'auto', dispName: 'Auto'}, {val: 'hatch', dispName: 'Hatch'},
+ {val: 'solid', dispName: 'Solid'}, {val: 'dots', dispName: 'Dots'},
+ {val: 'cross-hatch', dispName: 'Cross Hatch'}, {val: 'dashed', dispName: 'Dashed'},
+ {val: 'zigzag-line', dispName: 'ZigZag Line'}];
+
+/**
+ * Graph themes for the format panel.
+ */
+Editor.themes = null;
+
+/**
+ * Specifies the image URL to be used for the transparent background.
+ */
+Editor.ctrlKey = (mxClient.IS_MAC) ? 'Cmd' : 'Ctrl';
+
+/**
+ * Specifies the image URL to be used for the transparent background.
+ */
+Editor.hintOffset = 20;
+
+/**
+ * Delay in ms to show shape picker on hover over blue arrows.
+ */
+Editor.shapePickerHoverDelay = 300;
+
+/**
+ * Specifies the image URL to be used for the transparent background.
+ */
+Editor.fitWindowBorders = null;
+
+/**
+ * Specifies if the diagram should be saved automatically if possible. Default
+ * is true.
+ */
+Editor.popupsAllowed = window.urlParams != null? urlParams['noDevice'] != '1' : true;
+
+/**
+ * Specifies if the html and whiteSpace styles should be removed on inserted cells.
+ */
+Editor.simpleLabels = false;
+
+/**
+ * Specifies if the native clipboard is enabled. Blocked in iframes for possible sandbox attribute.
+ * LATER: Check if actually blocked.
+ */
+Editor.enableNativeCipboard = window == window.top && !mxClient.IS_FF && navigator.clipboard != null;
+
+/**
+ * Dynamic change of dark mode for minimal and sketch theme.
+ */
+Editor.sketchMode = false;
+
+/**
+ * Dynamic change of dark mode for minimal and sketch theme.
+ */
+Editor.darkMode = false;
+
+/**
+ * Dynamic change of dark mode for minimal and sketch theme.
+ */
+//Editor.currentTheme = uiTheme;
+
+/**
+ * Dynamic change of dark mode for minimal and sketch theme.
+ */
+Editor.darkColor = '#18141D';
+
+/**
+ * Dynamic change of dark mode for minimal and sketch theme.
+ */
+Editor.lightColor = '#f0f0f0';
+
+/**
+ * Returns the current state of the dark mode.
+ */
+Editor.isDarkMode = function(value)
+{
+ return Editor.darkMode;
+};
+
+/**
+ * Returns true if the given URL is a PNG data URL.
+ */
+Editor.isPngDataUrl = function(url)
+{
+ return url != null && url.substring(0, 15) == 'data:image/png;'
+};
+
+/**
+ * Returns true if the given binary data is a PNG file.
+ */
+Editor.isPngData = function(data)
+{
+ return data.length > 8 && data.charCodeAt(0) == 137 && data.charCodeAt(1) == 80 &&
+ data.charCodeAt(2) == 78 && data.charCodeAt(3) == 71 && data.charCodeAt(4) == 13 &&
+ data.charCodeAt(5) == 10 && data.charCodeAt(6) == 26 && data.charCodeAt(7) == 10;
+};
+
+/**
+ * Converts HTML to plain text.
+ */
+Editor.convertHtmlToText = function(label)
+{
+ if (label != null)
+ {
+ var temp = document.createElement('div');
+ temp.innerHTML = Graph.sanitizeHtml(label);
+
+ return mxUtils.extractTextWithWhitespace(temp.childNodes)
+ }
+ else
+ {
+ return null;
+ }
+};
+
+/**
+ * Extracts the XML from the compressed or non-compressed text chunk.
+ */
+Editor.extractGraphModelFromPng = function(data)
+{
+ var result = null;
+
+ try
+ {
+ var base64 = data.substring(data.indexOf(',') + 1);
+
+ // Workaround for invalid character error in Safari
+ var binary = (window.atob && !mxClient.IS_SF) ? atob(base64) : Base64.decode(base64, true);
+
+ EditorUi.parsePng(binary, mxUtils.bind(this, function(pos, type, length)
+ {
+ var value = binary.substring(pos + 8, pos + 8 + length);
+
+ if (type == 'zTXt')
+ {
+ var idx = value.indexOf(String.fromCharCode(0));
+
+ if (value.substring(0, idx) == 'mxGraphModel')
+ {
+ // Workaround for Java URL Encoder using + for spaces, which isn't compatible with JS
+ var xmlData = pako.inflateRaw(Graph.stringToArrayBuffer(
+ value.substring(idx + 2)), {to: 'string'}).replace(/\+/g,' ');
+
+ if (xmlData != null && xmlData.length > 0)
+ {
+ result = xmlData;
+ }
+ }
+ }
+ // Uncompressed section is normally not used
+ else if (type == 'tEXt')
+ {
+ var vals = value.split(String.fromCharCode(0));
+
+ if (vals.length > 1 && (vals[0] == 'mxGraphModel' ||
+ vals[0] == 'mxfile'))
+ {
+ result = vals[1];
+ }
+ }
+
+ if (result != null || type == 'IDAT')
+ {
+ // Stops processing the file as our text chunks
+ // are always placed before the data section
+ return true;
+ }
+ }));
+ }
+ catch (e)
+ {
+ // ignores decoding errors
+ }
+
+ if (result != null && result.charAt(0) == '%')
+ {
+ result = decodeURIComponent(result);
+ }
+
+ // Workaround for double encoded content
+ if (result != null && result.charAt(0) == '%')
+ {
+ result = decodeURIComponent(result);
+ }
+
+ return result;
+};
+
+/**
+ * Soundex algorithm for strings.
+ * See https://www.codedrome.com/the-soundex-algorithm-in-javascript/
+ */
+Editor.soundex = function(name)
+{
+ if (name == null || name.length == 0)
+ {
+ return '';
+ }
+ else
+ {
+ var s = [];
+ var si = 1;
+ var c;
+
+ // Changed: s maps to 0 not 2 to ignore plurals
+ // ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ var mappings = '01230120022455012603010202';
+
+ s[0] = name[0].toUpperCase();
+
+ for(var i = 1, l = name.length; i < l; i++)
+ {
+ c = (name[i].toUpperCase()).charCodeAt(0) - 65;
+
+ if(c >= 0 && c <= 25)
+ {
+ if(mappings[c] != '0')
+ {
+ if(mappings[c] != s[si-1])
+ {
+ s[si] = mappings[c];
+ si++;
+ }
+
+ if(si > 3)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ if(si <= 3)
+ {
+ while(si <= 3)
+ {
+ s[si] = '0';
+ si++;
+ }
+ }
+
+ return s.join('');
+ }
+};
+
+/**
+ * Selects the given part of the input element.
+ */
+Editor.selectFilename = function(input)
+{
+ var end = input.value.lastIndexOf('.');
+
+ if (end > 0)
+ {
+ var ext = input.value.substring(end + 1);
+
+ if (ext != 'drawio')
+ {
+ if (mxUtils.indexOf(['png', 'svg', 'html', 'xml', 'pdf'], ext) >= 0)
+ {
+ var temp = input.value.lastIndexOf('.drawio.', end);
+
+ if (temp > 0)
+ {
+ end = temp;
+ }
+ }
+ }
+ }
+
+ end = (end > 0) ? end : input.value.length;
+ Editor.selectSubstring(input, 0, end);
+};
+
+/**
+ * Selects the given part of the input element.
+ */
+Editor.selectSubstring = function(input, startPos, endPos)
+{
+ input.focus();
+
+ if (typeof input.selectionStart != 'undefined')
+ {
+ input.selectionStart = startPos;
+ input.selectionEnd = endPos;
+ }
+ else if (document.selection && document.selection.createRange)
+ {
+ // IE branch
+ input.select();
+ var range = document.selection.createRange();
+ range.collapse(true);
+ range.moveEnd('character', endPos);
+ range.moveStart('character', startPos);
+ range.select();
+ }
+};
+
+/**
+ * Editor inherits from mxEventSource
+ */
+mxUtils.extend(Editor, mxEventSource);
+
+/**
+ * Stores initial state of mxClient.NO_FO.
+ */
+Editor.prototype.originalNoForeignObject = mxClient.NO_FO;
+
+/**
+ * Specifies the image URL to be used for the transparent background.
+ */
+Editor.prototype.transparentImage = (mxClient.IS_SVG) ? 'data:image/gif;base64,R0lGODlhMAAwAIAAAP///wAAACH5BAEAAAAALAAAAAAwADAAAAIxhI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8egpAAA7' :
+ IMAGE_PATH + '/transparent.gif';
+
+/**
+ * Specifies if the canvas should be extended in all directions. Default is true.
+ */
+Editor.prototype.extendCanvas = true;
+
+/**
+ * Specifies if the app should run in chromeless mode. Default is false.
+ * This default is only used if the contructor argument is null.
+ */
+Editor.prototype.chromeless = false;
+
+/**
+ * Specifies the order of OK/Cancel buttons in dialogs. Default is true.
+ * Cancel first is used on Macs, Windows/Confluence uses cancel last.
+ */
+Editor.prototype.cancelFirst = true;
+
+/**
+ * Specifies if the editor is enabled. Default is true.
+ */
+Editor.prototype.enabled = true;
+
+/**
+ * Contains the name which was used for the last save. Default value is null.
+ */
+Editor.prototype.filename = null;
+
+/**
+ * Contains the current modified state of the diagram. This is false for
+ * new diagrams and after the diagram was saved.
+ */
+Editor.prototype.modified = false;
+
+/**
+ * Specifies if the diagram should be saved automatically if possible. Default
+ * is true.
+ */
+Editor.prototype.autosave = true;
+
+/**
+ * Specifies the top spacing for the initial page view. Default is 0.
+ */
+Editor.prototype.initialTopSpacing = 0;
+
+/**
+ * Specifies the app name. Default is document.title.
+ */
+Editor.prototype.appName = document.title;
+
+/**
+ *
+ */
+Editor.prototype.editBlankUrl = window.location.protocol + '//' + window.location.host + '/';
+
+/**
+ * Default value for the graph container overflow style.
+ */
+Editor.prototype.defaultGraphOverflow = 'hidden';
+
+/**
+ * Initializes the environment.
+ */
+Editor.prototype.init = function() { };
+
+/**
+ * Sets the XML node for the current diagram.
+ */
+Editor.prototype.isChromelessView = function()
+{
+ return this.chromeless;
+};
+
+/**
+ * Sets the XML node for the current diagram.
+ */
+Editor.prototype.setAutosave = function(value)
+{
+ this.autosave = value;
+ this.fireEvent(new mxEventObject('autosaveChanged'));
+};
+
+/**
+ *
+ */
+Editor.prototype.getEditBlankUrl = function(params)
+{
+ return this.editBlankUrl + params;
+}
+
+/**
+ *
+ */
+Editor.prototype.editAsNew = function(xml, title)
+{
+ var p = (title != null) ? '?title=' + encodeURIComponent(title) : '';
+
+ if (urlParams['ui'] != null)
+ {
+ p += ((p.length > 0) ? '&' : '?') + 'ui=' + urlParams['ui'];
+ }
+
+ if (typeof window.postMessage !== 'undefined' &&
+ (document.documentMode == null ||
+ document.documentMode >= 10))
+ {
+ var wnd = null;
+
+ var l = mxUtils.bind(this, function(evt)
+ {
+ if (evt.data == 'ready' && evt.source == wnd)
+ {
+ mxEvent.removeListener(window, 'message', l);
+ wnd.postMessage(xml, '*');
+ }
+ });
+
+ mxEvent.addListener(window, 'message', l);
+ wnd = this.graph.openLink(this.getEditBlankUrl(
+ p + ((p.length > 0) ? '&' : '?') +
+ 'client=1'), null, true);
+ }
+ else
+ {
+ this.graph.openLink(this.getEditBlankUrl(p) +
+ '#R' + encodeURIComponent(xml));
+ }
+};
+
+/**
+ * Sets the XML node for the current diagram.
+ */
+Editor.prototype.createGraph = function(themes, model)
+{
+ var graph = new Graph(null, model, null, null, themes);
+ graph.transparentBackground = false;
+
+ // Disables CSS transforms in Safari in chromeless mode
+ var graphIsCssTransformsSupported = graph.isCssTransformsSupported;
+ var self = this;
+
+ graph.isCssTransformsSupported = function()
+ {
+ return graphIsCssTransformsSupported.apply(this, arguments) &&
+ (!self.chromeless || !mxClient.IS_SF);
+ };
+
+ // Opens all links in a new window while editing
+ if (!this.chromeless)
+ {
+ graph.isBlankLink = function(href)
+ {
+ return !this.isExternalProtocol(href);
+ };
+ }
+
+ return graph;
+};
+
+/**
+ * Sets the XML node for the current diagram.
+ */
+Editor.prototype.resetGraph = function()
+{
+ this.graph.gridEnabled = this.graph.defaultGridEnabled && (!this.isChromelessView() || urlParams['grid'] == '1');
+ this.graph.graphHandler.guidesEnabled = true;
+ this.graph.setTooltips(true);
+ this.graph.setConnectable(true);
+ this.graph.foldingEnabled = true;
+ this.graph.scrollbars = this.graph.defaultScrollbars;
+ this.graph.pageVisible = this.graph.defaultPageVisible;
+ this.graph.pageBreaksVisible = this.graph.pageVisible;
+ this.graph.preferPageSize = this.graph.pageBreaksVisible;
+ this.graph.background = null;
+ this.graph.pageScale = mxGraph.prototype.pageScale;
+ this.graph.pageFormat = mxGraph.prototype.pageFormat;
+ this.graph.currentScale = 1;
+ this.graph.currentTranslate.x = 0;
+ this.graph.currentTranslate.y = 0;
+ this.updateGraphComponents();
+ this.graph.view.setScale(1);
+};
+
+/**
+ * Sets the XML node for the current diagram.
+ */
+Editor.prototype.readGraphState = function(node)
+{
+ var grid = node.getAttribute('grid');
+
+ if (grid == null || grid == '')
+ {
+ grid = this.graph.defaultGridEnabled ? '1' : '0';
+ }
+
+ this.graph.gridEnabled = grid != '0' && (!this.isChromelessView() || urlParams['grid'] == '1');
+ this.graph.gridSize = parseFloat(node.getAttribute('gridSize')) || mxGraph.prototype.gridSize;
+ this.graph.graphHandler.guidesEnabled = node.getAttribute('guides') != '0';
+ this.graph.setTooltips(node.getAttribute('tooltips') != '0');
+ this.graph.setConnectable(node.getAttribute('connect') != '0');
+ this.graph.connectionArrowsEnabled = node.getAttribute('arrows') != '0';
+ this.graph.foldingEnabled = node.getAttribute('fold') != '0';
+
+ if (this.isChromelessView() && this.graph.foldingEnabled)
+ {
+ this.graph.foldingEnabled = urlParams['nav'] == '1';
+ this.graph.cellRenderer.forceControlClickHandler = this.graph.foldingEnabled;
+ }
+
+ var ps = parseFloat(node.getAttribute('pageScale'));
+
+ if (!isNaN(ps) && ps > 0)
+ {
+ this.graph.pageScale = ps;
+ }
+ else
+ {
+ this.graph.pageScale = mxGraph.prototype.pageScale;
+ }
+
+ if (!this.graph.isLightboxView() && !this.graph.isViewer())
+ {
+ var pv = node.getAttribute('page');
+
+ if (pv != null)
+ {
+ this.graph.pageVisible = (pv != '0');
+ }
+ else
+ {
+ this.graph.pageVisible = this.graph.defaultPageVisible;
+ }
+ }
+ else
+ {
+ this.graph.pageVisible = false;
+ }
+
+ this.graph.pageBreaksVisible = this.graph.pageVisible;
+ this.graph.preferPageSize = this.graph.pageBreaksVisible;
+
+ var pw = parseFloat(node.getAttribute('pageWidth'));
+ var ph = parseFloat(node.getAttribute('pageHeight'));
+
+ if (!isNaN(pw) && !isNaN(ph))
+ {
+ this.graph.pageFormat = new mxRectangle(0, 0, pw, ph);
+ }
+
+ // Loads the persistent state settings
+ var bg = node.getAttribute('background');
+
+ if (bg != null && bg.length > 0)
+ {
+ this.graph.background = bg;
+ }
+ else
+ {
+ this.graph.background = null;
+ }
+};
+
+/**
+ * Sets the XML node for the current diagram.
+ */
+Editor.prototype.setGraphXml = function(node)
+{
+ if (node != null)
+ {
+ var dec = new mxCodec(node.ownerDocument);
+
+ if (node.nodeName == 'mxGraphModel')
+ {
+ this.graph.model.beginUpdate();
+
+ try
+ {
+ this.graph.model.clear();
+ this.graph.view.scale = 1;
+ this.readGraphState(node);
+ this.updateGraphComponents();
+ dec.decode(node, this.graph.getModel());
+ }
+ finally
+ {
+ this.graph.model.endUpdate();
+ }
+
+ this.fireEvent(new mxEventObject('resetGraphView'));
+ }
+ else if (node.nodeName == 'root')
+ {
+ this.resetGraph();
+
+ // Workaround for invalid XML output in Firefox 20 due to bug in mxUtils.getXml
+ var wrapper = dec.document.createElement('mxGraphModel');
+ wrapper.appendChild(node);
+
+ dec.decode(wrapper, this.graph.getModel());
+ this.updateGraphComponents();
+ this.fireEvent(new mxEventObject('resetGraphView'));
+ }
+ else
+ {
+ throw {
+ message: mxResources.get('cannotOpenFile'),
+ node: node,
+ toString: function() { return this.message; }
+ };
+ }
+ }
+ else
+ {
+ this.resetGraph();
+ this.graph.model.clear();
+ this.fireEvent(new mxEventObject('resetGraphView'));
+ }
+};
+
+/**
+ * Returns the XML node that represents the current diagram.
+ */
+Editor.prototype.getGraphXml = function(ignoreSelection)
+{
+ ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
+ var node = null;
+
+ if (ignoreSelection)
+ {
+ var enc = new mxCodec(mxUtils.createXmlDocument());
+ node = enc.encode(this.graph.getModel());
+ }
+ else
+ {
+ node = this.graph.encodeCells(mxUtils.sortCells(this.graph.model.getTopmostCells(
+ this.graph.getSelectionCells())));
+ }
+
+ if (this.graph.view.translate.x != 0 || this.graph.view.translate.y != 0)
+ {
+ node.setAttribute('dx', Math.round(this.graph.view.translate.x * 100) / 100);
+ node.setAttribute('dy', Math.round(this.graph.view.translate.y * 100) / 100);
+ }
+
+ node.setAttribute('grid', (this.graph.isGridEnabled()) ? '1' : '0');
+ node.setAttribute('gridSize', this.graph.gridSize);
+ node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
+ node.setAttribute('tooltips', (this.graph.tooltipHandler.isEnabled()) ? '1' : '0');
+ node.setAttribute('connect', (this.graph.connectionHandler.isEnabled()) ? '1' : '0');
+ node.setAttribute('arrows', (this.graph.connectionArrowsEnabled) ? '1' : '0');
+ node.setAttribute('fold', (this.graph.foldingEnabled) ? '1' : '0');
+ node.setAttribute('page', (this.graph.pageVisible) ? '1' : '0');
+ node.setAttribute('pageScale', this.graph.pageScale);
+ node.setAttribute('pageWidth', this.graph.pageFormat.width);
+ node.setAttribute('pageHeight', this.graph.pageFormat.height);
+
+ if (this.graph.background != null)
+ {
+ node.setAttribute('background', this.graph.background);
+ }
+
+ return node;
+};
+
+/**
+ * Keeps the graph container in sync with the persistent graph state
+ */
+Editor.prototype.updateGraphComponents = function()
+{
+ var graph = this.graph;
+
+ if (graph.container != null)
+ {
+ graph.view.validateBackground();
+ graph.container.style.overflow = (graph.scrollbars) ? 'auto' : this.defaultGraphOverflow;
+
+ this.fireEvent(new mxEventObject('updateGraphComponents'));
+ }
+};
+
+/**
+ * Sets the modified flag.
+ */
+Editor.prototype.setModified = function(value)
+{
+ this.modified = value;
+};
+
+/**
+ * Sets the filename.
+ */
+Editor.prototype.setFilename = function(value)
+{
+ this.filename = value;
+};
+
+/**
+ * Creates and returns a new undo manager.
+ */
+Editor.prototype.createUndoManager = function()
+{
+ var graph = this.graph;
+ var undoMgr = new mxUndoManager();
+
+ this.undoListener = function(sender, evt)
+ {
+ undoMgr.undoableEditHappened(evt.getProperty('edit'));
+ };
+
+ // Installs the command history
+ var listener = mxUtils.bind(this, function(sender, evt)
+ {
+ this.undoListener.apply(this, arguments);
+ });
+
+ graph.getModel().addListener(mxEvent.UNDO, listener);
+ graph.getView().addListener(mxEvent.UNDO, listener);
+
+ // Keeps the selection in sync with the history
+ var undoHandler = function(sender, evt)
+ {
+ var cand = graph.getSelectionCellsForChanges(evt.getProperty('edit').changes, function(change)
+ {
+ // Only selects changes to the cell hierarchy
+ return !(change instanceof mxChildChange);
+ });
+
+ if (cand.length > 0)
+ {
+ var model = graph.getModel();
+ var cells = [];
+
+ for (var i = 0; i < cand.length; i++)
+ {
+ if (graph.view.getState(cand[i]) != null)
+ {
+ cells.push(cand[i]);
+ }
+ }
+
+ graph.setSelectionCells(cells);
+ }
+ };
+
+ undoMgr.addListener(mxEvent.UNDO, undoHandler);
+ undoMgr.addListener(mxEvent.REDO, undoHandler);
+
+ return undoMgr;
+};
+
+/**
+ * Adds basic stencil set (no namespace).
+ */
+Editor.prototype.initStencilRegistry = function() { };
+
+/**
+ * Creates and returns a new undo manager.
+ */
+Editor.prototype.destroy = function()
+{
+ if (this.graph != null)
+ {
+ this.graph.destroy();
+ this.graph = null;
+ }
+};
+
+/**
+ * Class for asynchronously opening a new window and loading a file at the same
+ * time. This acts as a bridge between the open dialog and the new editor.
+ */
+OpenFile = function(done)
+{
+ this.producer = null;
+ this.consumer = null;
+ this.done = done;
+ this.args = null;
+};
+
+/**
+ * Registers the editor from the new window.
+ */
+OpenFile.prototype.setConsumer = function(value)
+{
+ this.consumer = value;
+ this.execute();
+};
+
+/**
+ * Sets the data from the loaded file.
+ */
+OpenFile.prototype.setData = function()
+{
+ this.args = arguments;
+ this.execute();
+};
+
+/**
+ * Displays an error message.
+ */
+OpenFile.prototype.error = function(msg)
+{
+ this.cancel(true);
+ mxUtils.alert(msg);
+};
+
+/**
+ * Consumes the data.
+ */
+OpenFile.prototype.execute = function()
+{
+ if (this.consumer != null && this.args != null)
+ {
+ this.cancel(false);
+ this.consumer.apply(this, this.args);
+ }
+};
+
+/**
+ * Cancels the operation.
+ */
+OpenFile.prototype.cancel = function(cancel)
+{
+ if (this.done != null)
+ {
+ this.done((cancel != null) ? cancel : true);
+ }
+};
+
+/**
+ * Basic dialogs that are available in the viewer (print dialog).
+ */
+function Dialog(editorUi, elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, ignoreBgClick)
+{
+ this.editorUi = editorUi;
+ var dx = transparent? 57 : 0;
+ var w0 = w;
+ var h0 = h;
+ var padding = transparent? 0 : 64; //No padding needed for transparent dialogs
+
+ var ds = (!Editor.inlineFullscreen && editorUi.embedViewport != null) ?
+ mxUtils.clone(editorUi.embedViewport) : this.getDocumentSize();
+
+ // Workaround for print dialog offset in viewer lightbox
+ if (editorUi.embedViewport == null && window.innerHeight != null)
+ {
+ ds.height = window.innerHeight;
+ }
+
+ var dh = ds.height;
+ var left = Math.max(1, Math.round((ds.width - w - padding) / 2));
+ var top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));
+
+ // Keeps window size inside available space
+ elt.style.maxHeight = '100%';
+
+ w = (document.body != null) ? Math.min(w, document.body.scrollWidth - padding) : w;
+ h = Math.min(h, dh - padding);
+
+ // Increments zIndex to put subdialogs and background over existing dialogs and background
+ if (editorUi.dialogs.length > 0)
+ {
+ this.zIndex += editorUi.dialogs.length * 2;
+ }
+
+ if (this.bg == null)
+ {
+ this.bg = editorUi.createDiv('geBackground');
+ this.bg.style.position = 'absolute';
+ this.bg.style.height = dh + 'px';
+ this.bg.style.right = '0px';
+ this.bg.style.zIndex = this.zIndex - 2;
+
+ mxUtils.setOpacity(this.bg, this.bgOpacity);
+ }
+
+ var origin = mxUtils.getDocumentScrollOrigin(document);
+ this.bg.style.left = origin.x + 'px';
+ this.bg.style.top = origin.y + 'px';
+ left += origin.x;
+ top += origin.y;
+
+ if (!Editor.inlineFullscreen && editorUi.embedViewport != null)
+ {
+ this.bg.style.height = this.getDocumentSize().height + 'px';
+ top += editorUi.embedViewport.y;
+ left += editorUi.embedViewport.x;
+ }
+
+ if (modal)
+ {
+ document.body.appendChild(this.bg);
+ }
+
+ var div = editorUi.createDiv(transparent? 'geTransDialog' : 'geDialog');
+ var pos = this.getPosition(left, top, w, h);
+ left = pos.x;
+ top = pos.y;
+
+ div.style.width = w + 'px';
+ div.style.height = h + 'px';
+ div.style.left = left + 'px';
+ div.style.top = top + 'px';
+ div.style.zIndex = this.zIndex;
+
+ div.appendChild(elt);
+ document.body.appendChild(div);
+
+ // Adds vertical scrollbars if needed
+ if (!noScroll && elt.clientHeight > div.clientHeight - padding)
+ {
+ elt.style.overflowY = 'auto';
+ }
+
+ //Prevent horizontal scrollbar
+ elt.style.overflowX = 'hidden';
+
+ if (closable)
+ {
+ var img = document.createElement('img');
+
+ img.setAttribute('src', Dialog.prototype.closeImage);
+ img.setAttribute('title', mxResources.get('close'));
+ img.className = 'geDialogClose';
+ img.style.top = (top + 14) + 'px';
+ img.style.left = (left + w + 38 - dx) + 'px';
+ img.style.zIndex = this.zIndex;
+
+ mxEvent.addListener(img, 'click', mxUtils.bind(this, function()
+ {
+ editorUi.hideDialog(true);
+ }));
+
+ document.body.appendChild(img);
+ this.dialogImg = img;
+
+ if (!ignoreBgClick)
+ {
+ var mouseDownSeen = false;
+
+ mxEvent.addGestureListeners(this.bg, mxUtils.bind(this, function(evt)
+ {
+ mouseDownSeen = true;
+ }), null, mxUtils.bind(this, function(evt)
+ {
+ if (mouseDownSeen)
+ {
+ editorUi.hideDialog(true);
+ mouseDownSeen = false;
+ }
+ }));
+ }
+ }
+
+ this.resizeListener = mxUtils.bind(this, function()
+ {
+ if (onResize != null)
+ {
+ var newWH = onResize();
+
+ if (newWH != null)
+ {
+ w0 = w = newWH.w;
+ h0 = h = newWH.h;
+ }
+ }
+
+ var ds = (!Editor.inlineFullscreen && editorUi.embedViewport != null) ?
+ mxUtils.clone(editorUi.embedViewport) : this.getDocumentSize();
+ dh = ds.height;
+ this.bg.style.height = dh + 'px';
+
+ if (!Editor.inlineFullscreen && editorUi.embedViewport != null)
+ {
+ this.bg.style.height = this.getDocumentSize().height + 'px';
+ }
+
+ left = Math.max(1, Math.round((ds.width - w - padding) / 2));
+ top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));
+ w = (document.body != null) ? Math.min(w0, document.body.scrollWidth - padding) : w0;
+ h = Math.min(h0, dh - padding);
+
+ // var dh = ds.height;
+ var left = Math.max(1, Math.round((ds.width - w - padding) / 2));
+ var top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));
+
+ var pos = this.getPosition(left, top, w, h);
+ left = pos.x;
+ top = pos.y;
+
+ var origin = mxUtils.getDocumentScrollOrigin(document);
+ left += origin.x;
+ top += origin.y;
+
+ if (!Editor.inlineFullscreen && editorUi.embedViewport != null)
+ {
+ top += editorUi.embedViewport.y;
+ left += editorUi.embedViewport.x;
+ }
+
+ div.style.left = left + 'px';
+ div.style.top = top + 'px';
+ div.style.width = w + 'px';
+ div.style.height = h + 'px';
+
+ // Adds vertical scrollbars if needed
+ if (!noScroll && elt.clientHeight > div.clientHeight - padding)
+ {
+ elt.style.overflowY = 'auto';
+ }
+
+ if (this.dialogImg != null)
+ {
+ this.dialogImg.style.top = (top + 14) + 'px';
+ this.dialogImg.style.left = (left + w + 38 - dx) + 'px';
+ }
+ });
+
+ if (editorUi.embedViewport != null)
+ {
+ editorUi.addListener('embedViewportChanged', this.resizeListener);
+ }
+ else
+ {
+ mxEvent.addListener(window, 'resize', this.resizeListener);
+ }
+
+ this.onDialogClose = onClose;
+ this.container = div;
+
+ editorUi.editor.fireEvent(new mxEventObject('showDialog'));
+};
+
+/**
+ *
+ */
+Dialog.prototype.zIndex = mxPopupMenu.prototype.zIndex - 2;
+
+/**
+ *
+ */
+Dialog.prototype.noColorImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/nocolor.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkEzRDlBMUUwODYxMTExRTFCMzA4RDdDMjJBMEMxRDM3IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkEzRDlBMUUxODYxMTExRTFCMzA4RDdDMjJBMEMxRDM3Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTNEOUExREU4NjExMTFFMUIzMDhEN0MyMkEwQzFEMzciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTNEOUExREY4NjExMTFFMUIzMDhEN0MyMkEwQzFEMzciLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5xh3fmAAAABlBMVEX////MzMw46qqDAAAAGElEQVR42mJggAJGKGAYIIGBth8KAAIMAEUQAIElnLuQAAAAAElFTkSuQmCC';
+
+/**
+ *
+ */
+Dialog.prototype.closeImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/close.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJAQMAAADaX5RTAAAABlBMVEV7mr3///+wksspAAAAAnRSTlP/AOW3MEoAAAAdSURBVAgdY9jXwCDDwNDRwHCwgeExmASygSL7GgB12QiqNHZZIwAAAABJRU5ErkJggg==';
+
+/**
+ *
+ */
+Dialog.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==';
+
+/**
+ * Removes the dialog from the DOM.
+ */
+Dialog.prototype.bgOpacity = 80;
+
+/**
+ * Removes the dialog from the DOM.
+ */
+Dialog.prototype.getDocumentSize = function()
+{
+ return mxUtils.getDocumentSize();
+};
+
+/**
+ * Removes the dialog from the DOM.
+ */
+Dialog.prototype.getPosition = function(left, top)
+{
+ return new mxPoint(left, top);
+};
+
+/**
+ * Removes the dialog from the DOM.
+ */
+Dialog.prototype.close = function(cancel, isEsc)
+{
+ if (this.onDialogClose != null)
+ {
+ if (this.onDialogClose(cancel, isEsc) == false)
+ {
+ return false;
+ }
+
+ this.onDialogClose = null;
+ }
+
+ if (this.dialogImg != null && this.dialogImg.parentNode != null)
+ {
+ this.dialogImg.parentNode.removeChild(this.dialogImg);
+ this.dialogImg = null;
+ }
+
+ if (this.bg != null && this.bg.parentNode != null)
+ {
+ this.bg.parentNode.removeChild(this.bg);
+ }
+
+ if (this.editorUi.embedViewport != null)
+ {
+ this.editorUi.removeListener(this.resizeListener);
+ }
+ else
+ {
+ mxEvent.removeListener(window, 'resize', this.resizeListener);
+ }
+
+ if (this.container.parentNode != null)
+ {
+ this.container.parentNode.removeChild(this.container);
+ }
+};
+
+/**
+ *
+ */
+var ErrorDialog = function(editorUi, title, message, buttonText, fn, retry, buttonText2, fn2, hide, buttonText3, fn3)
+{
+ hide = (hide != null) ? hide : true;
+
+ var div = document.createElement('div');
+ div.style.textAlign = 'center';
+
+ if (title != null)
+ {
+ var hd = document.createElement('div');
+ hd.style.padding = '0px';
+ hd.style.margin = '0px';
+ hd.style.fontSize = '18px';
+ hd.style.paddingBottom = '16px';
+ hd.style.marginBottom = '10px';
+ hd.style.borderBottom = '1px solid #c0c0c0';
+ hd.style.color = 'gray';
+ hd.style.whiteSpace = 'nowrap';
+ hd.style.textOverflow = 'ellipsis';
+ hd.style.overflow = 'hidden';
+ mxUtils.write(hd, title);
+ hd.setAttribute('title', title);
+ div.appendChild(hd);
+ }
+
+ var p2 = document.createElement('div');
+ p2.style.lineHeight = '1.2em';
+ p2.style.padding = '6px';
+
+ if (typeof message === 'string')
+ {
+ message = message.replace(/\n/g, '
');
+ }
+
+ p2.innerHTML = message;
+ div.appendChild(p2);
+
+ var btns = document.createElement('div');
+ btns.style.marginTop = '12px';
+ btns.style.textAlign = 'center';
+
+ if (retry != null)
+ {
+ var retryBtn = mxUtils.button(mxResources.get('tryAgain'), function()
+ {
+ editorUi.hideDialog();
+ retry();
+ });
+ retryBtn.className = 'geBtn';
+ btns.appendChild(retryBtn);
+
+ btns.style.textAlign = 'center';
+ }
+
+ if (buttonText3 != null)
+ {
+ var btn3 = mxUtils.button(buttonText3, function()
+ {
+ if (fn3 != null)
+ {
+ fn3();
+ }
+ });
+
+ btn3.className = 'geBtn';
+ btns.appendChild(btn3);
+ }
+
+ var btn = mxUtils.button(buttonText, function()
+ {
+ if (hide)
+ {
+ editorUi.hideDialog();
+ }
+
+ if (fn != null)
+ {
+ fn();
+ }
+ });
+
+ btn.className = 'geBtn';
+ btns.appendChild(btn);
+
+ if (buttonText2 != null)
+ {
+ var mainBtn = mxUtils.button(buttonText2, function()
+ {
+ if (hide)
+ {
+ editorUi.hideDialog();
+ }
+
+ if (fn2 != null)
+ {
+ fn2();
+ }
+ });
+
+ mainBtn.className = 'geBtn gePrimaryBtn';
+ btns.appendChild(mainBtn);
+ }
+
+ this.init = function()
+ {
+ btn.focus();
+ };
+
+ div.appendChild(btns);
+
+ this.container = div;
+};
+
+/**
+ * Constructs a new print dialog.
+ */
+var PrintDialog = function(editorUi, title)
+{
+ this.create(editorUi, title);
+};
+
+/**
+ * Constructs a new print dialog.
+ */
+PrintDialog.prototype.create = function(editorUi)
+{
+ var graph = editorUi.editor.graph;
+ var row, td;
+
+ var table = document.createElement('table');
+ table.style.width = '100%';
+ table.style.height = '100%';
+ var tbody = document.createElement('tbody');
+
+ row = document.createElement('tr');
+
+ var onePageCheckBox = document.createElement('input');
+ onePageCheckBox.setAttribute('type', 'checkbox');
+ td = document.createElement('td');
+ td.setAttribute('colspan', '2');
+ td.style.fontSize = '10pt';
+ td.appendChild(onePageCheckBox);
+
+ var span = document.createElement('span');
+ mxUtils.write(span, ' ' + mxResources.get('fitPage'));
+ td.appendChild(span);
+
+ mxEvent.addListener(span, 'click', function(evt)
+ {
+ onePageCheckBox.checked = !onePageCheckBox.checked;
+ pageCountCheckBox.checked = !onePageCheckBox.checked;
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(onePageCheckBox, 'change', function()
+ {
+ pageCountCheckBox.checked = !onePageCheckBox.checked;
+ });
+
+ row.appendChild(td);
+ tbody.appendChild(row);
+
+ row = row.cloneNode(false);
+
+ var pageCountCheckBox = document.createElement('input');
+ pageCountCheckBox.setAttribute('type', 'checkbox');
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ td.appendChild(pageCountCheckBox);
+
+ var span = document.createElement('span');
+ mxUtils.write(span, ' ' + mxResources.get('posterPrint') + ':');
+ td.appendChild(span);
+
+ mxEvent.addListener(span, 'click', function(evt)
+ {
+ pageCountCheckBox.checked = !pageCountCheckBox.checked;
+ onePageCheckBox.checked = !pageCountCheckBox.checked;
+ mxEvent.consume(evt);
+ });
+
+ row.appendChild(td);
+
+ var pageCountInput = document.createElement('input');
+ pageCountInput.setAttribute('value', '1');
+ pageCountInput.setAttribute('type', 'number');
+ pageCountInput.setAttribute('min', '1');
+ pageCountInput.setAttribute('size', '4');
+ pageCountInput.setAttribute('disabled', 'disabled');
+ pageCountInput.style.width = '50px';
+
+ td = document.createElement('td');
+ td.style.fontSize = '10pt';
+ td.appendChild(pageCountInput);
+ mxUtils.write(td, ' ' + mxResources.get('pages') + ' (max)');
+ row.appendChild(td);
+ tbody.appendChild(row);
+
+ mxEvent.addListener(pageCountCheckBox, 'change', function()
+ {
+ if (pageCountCheckBox.checked)
+ {
+ pageCountInput.removeAttribute('disabled');
+ }
+ else
+ {
+ pageCountInput.setAttribute('disabled', 'disabled');
+ }
+
+ onePageCheckBox.checked = !pageCountCheckBox.checked;
+ });
+
+ row = row.cloneNode(false);
+
+ td = document.createElement('td');
+ mxUtils.write(td, mxResources.get('pageScale') + ':');
+ row.appendChild(td);
+
+ td = document.createElement('td');
+ var pageScaleInput = document.createElement('input');
+ pageScaleInput.setAttribute('value', '100 %');
+ pageScaleInput.setAttribute('size', '5');
+ pageScaleInput.style.width = '50px';
+
+ td.appendChild(pageScaleInput);
+ row.appendChild(td);
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+ td = document.createElement('td');
+ td.colSpan = 2;
+ td.style.paddingTop = '20px';
+ td.setAttribute('align', 'right');
+
+ // Overall scale for print-out to account for print borders in dialogs etc
+ function preview(print)
+ {
+ var autoOrigin = onePageCheckBox.checked || pageCountCheckBox.checked;
+ var printScale = parseInt(pageScaleInput.value) / 100;
+
+ if (isNaN(printScale))
+ {
+ printScale = 1;
+ pageScaleInput.value = '100%';
+ }
+
+ // Workaround to match available paper size in actual print output
+ if (mxClient.IS_SF)
+ {
+ printScale *= 0.75;
+ }
+
+ var pf = graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+ var scale = 1 / graph.pageScale;
+
+ if (autoOrigin)
+ {
+ var pageCount = (onePageCheckBox.checked) ? 1 : parseInt(pageCountInput.value);
+
+ if (!isNaN(pageCount))
+ {
+ scale = mxUtils.getScaleForPageCount(pageCount, graph, pf);
+ }
+ }
+
+ // Negative coordinates are cropped or shifted if page visible
+ var border = 0;
+ var x0 = 0;
+ var y0 = 0;
+
+ // Applies print scale
+ pf = mxRectangle.fromRectangle(pf);
+ pf.width = Math.ceil(pf.width * printScale);
+ pf.height = Math.ceil(pf.height * printScale);
+ scale *= printScale;
+
+ // Starts at first visible page
+ if (!autoOrigin && graph.pageVisible)
+ {
+ var layout = graph.getPageLayout();
+ x0 -= layout.x * pf.width;
+ y0 -= layout.y * pf.height;
+ }
+ else
+ {
+ autoOrigin = true;
+ }
+
+ var preview = PrintDialog.createPrintPreview(graph, scale, pf, border, x0, y0, autoOrigin);
+ preview.open();
+
+ if (print)
+ {
+ PrintDialog.printPreview(preview);
+ }
+ };
+
+ var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
+ {
+ editorUi.hideDialog();
+ });
+ cancelBtn.className = 'geBtn';
+
+ if (editorUi.editor.cancelFirst)
+ {
+ td.appendChild(cancelBtn);
+ }
+
+ if (PrintDialog.previewEnabled)
+ {
+ var previewBtn = mxUtils.button(mxResources.get('preview'), function()
+ {
+ editorUi.hideDialog();
+ preview(false);
+ });
+ previewBtn.className = 'geBtn';
+ td.appendChild(previewBtn);
+ }
+
+ var printBtn = mxUtils.button(mxResources.get((!PrintDialog.previewEnabled) ? 'ok' : 'print'), function()
+ {
+ editorUi.hideDialog();
+ preview(true);
+ });
+ printBtn.className = 'geBtn gePrimaryBtn';
+ td.appendChild(printBtn);
+
+ if (!editorUi.editor.cancelFirst)
+ {
+ td.appendChild(cancelBtn);
+ }
+
+ row.appendChild(td);
+ tbody.appendChild(row);
+
+ table.appendChild(tbody);
+ this.container = table;
+};
+
+/**
+ * Constructs a new print dialog.
+ */
+PrintDialog.printPreview = function(preview)
+{
+ try
+ {
+ if (preview.wnd != null)
+ {
+ var printFn = function()
+ {
+ preview.wnd.focus();
+ preview.wnd.print();
+ preview.wnd.close();
+ };
+
+ // Workaround for rendering SVG output and
+ // make window available for printing
+ window.setTimeout(printFn, 500);
+ }
+ }
+ catch (e)
+ {
+ // ignores possible Access Denied
+ }
+};
+
+/**
+ * Constructs a new print dialog.
+ */
+PrintDialog.createPrintPreview = function(graph, scale, pf, border, x0, y0, autoOrigin)
+{
+ var preview = new mxPrintPreview(graph, scale, pf, border, x0, y0);
+ preview.title = mxResources.get('preview');
+ preview.addPageCss = !mxClient.IS_SF;
+ preview.printBackgroundImage = true;
+ preview.autoOrigin = autoOrigin;
+ var bg = graph.background;
+
+ if (bg == null || bg == '' || bg == mxConstants.NONE)
+ {
+ bg = '#ffffff';
+ }
+
+ preview.backgroundColor = bg;
+
+ var writeHead = preview.writeHead;
+
+ // Adds a border in the preview
+ preview.writeHead = function(doc)
+ {
+ writeHead.apply(this, arguments);
+
+ doc.writeln('');
+ };
+
+ return preview;
+};
+
+/**
+ * Specifies if the preview button should be enabled. Default is true.
+ */
+PrintDialog.previewEnabled = true;
+
+/**
+ * Constructs a new page setup dialog.
+ */
+var PageSetupDialog = function(editorUi)
+{
+ var graph = editorUi.editor.graph;
+ var row, td;
+
+ var table = document.createElement('table');
+ table.style.width = '100%';
+ table.style.height = '100%';
+ var tbody = document.createElement('tbody');
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.verticalAlign = 'top';
+ td.style.fontSize = '10pt';
+ mxUtils.write(td, mxResources.get('paperSize') + ':');
+
+ row.appendChild(td);
+
+ td = document.createElement('td');
+ td.style.verticalAlign = 'top';
+ td.style.fontSize = '10pt';
+
+ var accessor = PageSetupDialog.addPageFormatPanel(td, 'pagesetupdialog', graph.pageFormat);
+
+ row.appendChild(td);
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ mxUtils.write(td, mxResources.get('gridSize') + ':');
+ row.appendChild(td);
+
+ td = document.createElement('td');
+ td.style.whiteSpace = 'nowrap';
+
+ var gridSizeInput = document.createElement('input');
+ gridSizeInput.setAttribute('type', 'number');
+ gridSizeInput.setAttribute('min', '0');
+ gridSizeInput.style.width = '40px';
+ gridSizeInput.style.marginLeft = '6px';
+
+ gridSizeInput.value = graph.getGridSize();
+ td.appendChild(gridSizeInput);
+
+ mxEvent.addListener(gridSizeInput, 'change', function()
+ {
+ var value = parseInt(gridSizeInput.value);
+ gridSizeInput.value = Math.max(1, (isNaN(value)) ? graph.getGridSize() : value);
+ });
+
+ row.appendChild(td);
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+ td = document.createElement('td');
+
+ mxUtils.write(td, mxResources.get('background') + ':');
+
+ row.appendChild(td);
+ td = document.createElement('td');
+
+ var changeImageLink = document.createElement('button');
+ changeImageLink.className = 'geBtn';
+ changeImageLink.style.margin = '0px';
+ mxUtils.write(changeImageLink, mxResources.get('change') + '...');
+
+ var imgPreview = document.createElement('div');
+ imgPreview.style.display = 'inline-block';
+ imgPreview.style.verticalAlign = 'middle';
+ imgPreview.style.backgroundPosition = 'center center';
+ imgPreview.style.backgroundRepeat = 'no-repeat';
+ imgPreview.style.backgroundSize = 'contain';
+ imgPreview.style.border = '1px solid lightGray';
+ imgPreview.style.borderRadius = '4px';
+ imgPreview.style.marginRight = '14px';
+ imgPreview.style.height = '32px';
+ imgPreview.style.width = '64px';
+ imgPreview.style.cursor = 'pointer';
+ imgPreview.style.padding = '4px';
+
+ var newBackgroundImage = graph.backgroundImage;
+ var newBackgroundColor = graph.background;
+ var newShadowVisible = graph.shadowVisible;
+
+ function updateBackgroundImage()
+ {
+ var img = newBackgroundImage;
+
+ if (img != null && img.originalSrc != null)
+ {
+ img = editorUi.createImageForPageLink(img.originalSrc, null);
+ }
+
+ if (img != null && img.src != null)
+ {
+ imgPreview.style.backgroundImage = 'url(' + img.src + ')';
+ imgPreview.style.display = 'inline-block';
+ }
+ else
+ {
+ imgPreview.style.backgroundImage = '';
+ imgPreview.style.display = 'none';
+ }
+
+ imgPreview.style.backgroundColor = '';
+
+ if (newBackgroundColor != null && newBackgroundColor != mxConstants.NONE)
+ {
+ imgPreview.style.backgroundColor = newBackgroundColor;
+ imgPreview.style.display = 'inline-block';
+ }
+ };
+
+ var changeImage = function(evt)
+ {
+ editorUi.showBackgroundImageDialog(function(image, failed, color, shadowVisible)
+ {
+ if (!failed)
+ {
+ if (image != null && image.src != null && Graph.isPageLink(image.src))
+ {
+ image = {originalSrc: image.src};
+ }
+
+ newBackgroundImage = image;
+ newShadowVisible = shadowVisible;
+ }
+
+ newBackgroundColor = color;
+ updateBackgroundImage();
+ }, newBackgroundImage, newBackgroundColor, true);
+
+ mxEvent.consume(evt);
+ };
+
+ mxEvent.addListener(changeImageLink, 'click', changeImage);
+ mxEvent.addListener(imgPreview, 'click', changeImage);
+
+ updateBackgroundImage();
+ td.appendChild(imgPreview);
+ td.appendChild(changeImageLink);
+
+ row.appendChild(td);
+ tbody.appendChild(row);
+
+ row = document.createElement('tr');
+ td = document.createElement('td');
+ td.colSpan = 2;
+ td.style.paddingTop = '16px';
+ td.setAttribute('align', 'right');
+
+ var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
+ {
+ editorUi.hideDialog();
+ });
+ cancelBtn.className = 'geBtn';
+
+ if (editorUi.editor.cancelFirst)
+ {
+ td.appendChild(cancelBtn);
+ }
+
+ var applyBtn = mxUtils.button(mxResources.get('apply'), function()
+ {
+ editorUi.hideDialog();
+ var gridSize = parseInt(gridSizeInput.value);
+
+ if (!isNaN(gridSize) && graph.gridSize !== gridSize)
+ {
+ graph.setGridSize(gridSize);
+ }
+
+ var change = new ChangePageSetup(editorUi, newBackgroundColor,
+ newBackgroundImage, accessor.get());
+ change.ignoreColor = graph.background == newBackgroundColor;
+
+ var oldSrc = (graph.backgroundImage != null) ? graph.backgroundImage.src : null;
+ var newSrc = (newBackgroundImage != null) ? newBackgroundImage.src : null;
+
+ change.ignoreImage = oldSrc === newSrc;
+
+ if (newShadowVisible != null)
+ {
+ change.shadowVisible = newShadowVisible;
+ }
+
+ if (graph.pageFormat.width != change.previousFormat.width ||
+ graph.pageFormat.height != change.previousFormat.height ||
+ !change.ignoreColor || !change.ignoreImage||
+ change.shadowVisible != graph.shadowVisible)
+ {
+ graph.model.execute(change);
+ }
+ });
+ applyBtn.className = 'geBtn gePrimaryBtn';
+ td.appendChild(applyBtn);
+
+ if (!editorUi.editor.cancelFirst)
+ {
+ td.appendChild(cancelBtn);
+ }
+
+ row.appendChild(td);
+ tbody.appendChild(row);
+
+ table.appendChild(tbody);
+ this.container = table;
+};
+
+/**
+ *
+ */
+PageSetupDialog.addPageFormatPanel = function(div, namePostfix, pageFormat, pageFormatListener)
+{
+ var formatName = 'format-' + namePostfix;
+
+ var portraitCheckBox = document.createElement('input');
+ portraitCheckBox.setAttribute('name', formatName);
+ portraitCheckBox.setAttribute('type', 'radio');
+ portraitCheckBox.setAttribute('value', 'portrait');
+
+ var landscapeCheckBox = document.createElement('input');
+ landscapeCheckBox.setAttribute('name', formatName);
+ landscapeCheckBox.setAttribute('type', 'radio');
+ landscapeCheckBox.setAttribute('value', 'landscape');
+
+ var paperSizeSelect = document.createElement('select');
+ paperSizeSelect.style.marginBottom = '8px';
+ paperSizeSelect.style.borderRadius = '4px';
+ paperSizeSelect.style.borderWidth = '1px';
+ paperSizeSelect.style.borderStyle = 'solid';
+ paperSizeSelect.style.width = '206px';
+
+ var formatDiv = document.createElement('div');
+ formatDiv.style.marginLeft = '4px';
+ formatDiv.style.width = '210px';
+ formatDiv.style.height = '24px';
+
+ portraitCheckBox.style.marginRight = '6px';
+ formatDiv.appendChild(portraitCheckBox);
+
+ var portraitSpan = document.createElement('span');
+ portraitSpan.style.maxWidth = '100px';
+ mxUtils.write(portraitSpan, mxResources.get('portrait'));
+ formatDiv.appendChild(portraitSpan);
+
+ landscapeCheckBox.style.marginLeft = '10px';
+ landscapeCheckBox.style.marginRight = '6px';
+ formatDiv.appendChild(landscapeCheckBox);
+
+ var landscapeSpan = document.createElement('span');
+ landscapeSpan.style.width = '100px';
+ mxUtils.write(landscapeSpan, mxResources.get('landscape'));
+ formatDiv.appendChild(landscapeSpan)
+
+ var customDiv = document.createElement('div');
+ customDiv.style.marginLeft = '4px';
+ customDiv.style.width = '210px';
+ customDiv.style.height = '24px';
+
+ var widthInput = document.createElement('input');
+ widthInput.setAttribute('size', '7');
+ widthInput.style.textAlign = 'right';
+ customDiv.appendChild(widthInput);
+ mxUtils.write(customDiv, ' in x ');
+
+ var heightInput = document.createElement('input');
+ heightInput.setAttribute('size', '7');
+ heightInput.style.textAlign = 'right';
+ customDiv.appendChild(heightInput);
+ mxUtils.write(customDiv, ' in');
+
+ formatDiv.style.display = 'none';
+ customDiv.style.display = 'none';
+
+ var pf = new Object();
+ var formats = PageSetupDialog.getFormats();
+
+ for (var i = 0; i < formats.length; i++)
+ {
+ var f = formats[i];
+ pf[f.key] = f;
+
+ var paperSizeOption = document.createElement('option');
+ paperSizeOption.setAttribute('value', f.key);
+ mxUtils.write(paperSizeOption, f.title);
+ paperSizeSelect.appendChild(paperSizeOption);
+ }
+
+ var customSize = false;
+
+ function listener(sender, evt, force)
+ {
+ if (force || (widthInput != document.activeElement && heightInput != document.activeElement))
+ {
+ var detected = false;
+
+ for (var i = 0; i < formats.length; i++)
+ {
+ var f = formats[i];
+
+ // Special case where custom was chosen
+ if (customSize)
+ {
+ if (f.key == 'custom')
+ {
+ paperSizeSelect.value = f.key;
+ customSize = false;
+ }
+ }
+ else if (f.format != null)
+ {
+ // Fixes wrong values for previous A4 and A5 page sizes
+ if (f.key == 'a4')
+ {
+ if (pageFormat.width == 826)
+ {
+ pageFormat = mxRectangle.fromRectangle(pageFormat);
+ pageFormat.width = 827;
+ }
+ else if (pageFormat.height == 826)
+ {
+ pageFormat = mxRectangle.fromRectangle(pageFormat);
+ pageFormat.height = 827;
+ }
+ }
+ else if (f.key == 'a5')
+ {
+ if (pageFormat.width == 584)
+ {
+ pageFormat = mxRectangle.fromRectangle(pageFormat);
+ pageFormat.width = 583;
+ }
+ else if (pageFormat.height == 584)
+ {
+ pageFormat = mxRectangle.fromRectangle(pageFormat);
+ pageFormat.height = 583;
+ }
+ }
+
+ if (pageFormat.width == f.format.width && pageFormat.height == f.format.height)
+ {
+ paperSizeSelect.value = f.key;
+ portraitCheckBox.setAttribute('checked', 'checked');
+ portraitCheckBox.defaultChecked = true;
+ portraitCheckBox.checked = true;
+ landscapeCheckBox.removeAttribute('checked');
+ landscapeCheckBox.defaultChecked = false;
+ landscapeCheckBox.checked = false;
+ detected = true;
+ }
+ else if (pageFormat.width == f.format.height && pageFormat.height == f.format.width)
+ {
+ paperSizeSelect.value = f.key;
+ portraitCheckBox.removeAttribute('checked');
+ portraitCheckBox.defaultChecked = false;
+ portraitCheckBox.checked = false;
+ landscapeCheckBox.setAttribute('checked', 'checked');
+ landscapeCheckBox.defaultChecked = true;
+ landscapeCheckBox.checked = true;
+ detected = true;
+ }
+ }
+ }
+
+ // Selects custom format which is last in list
+ if (!detected)
+ {
+ widthInput.value = pageFormat.width / 100;
+ heightInput.value = pageFormat.height / 100;
+ portraitCheckBox.setAttribute('checked', 'checked');
+ paperSizeSelect.value = 'custom';
+ formatDiv.style.display = 'none';
+ customDiv.style.display = '';
+ }
+ else
+ {
+ formatDiv.style.display = '';
+ customDiv.style.display = 'none';
+ }
+ }
+ };
+
+ listener();
+
+ div.appendChild(paperSizeSelect);
+ mxUtils.br(div);
+
+ div.appendChild(formatDiv);
+ div.appendChild(customDiv);
+
+ var currentPageFormat = pageFormat;
+
+ var update = function(evt, selectChanged)
+ {
+ var f = pf[paperSizeSelect.value];
+
+ if (f.format != null)
+ {
+ widthInput.value = f.format.width / 100;
+ heightInput.value = f.format.height / 100;
+ customDiv.style.display = 'none';
+ formatDiv.style.display = '';
+ }
+ else
+ {
+ formatDiv.style.display = 'none';
+ customDiv.style.display = '';
+ }
+
+ var wi = parseFloat(widthInput.value);
+
+ if (isNaN(wi) || wi <= 0)
+ {
+ widthInput.value = pageFormat.width / 100;
+ }
+
+ var hi = parseFloat(heightInput.value);
+
+ if (isNaN(hi) || hi <= 0)
+ {
+ heightInput.value = pageFormat.height / 100;
+ }
+
+ var newPageFormat = new mxRectangle(0, 0,
+ Math.floor(parseFloat(widthInput.value) * 100),
+ Math.floor(parseFloat(heightInput.value) * 100));
+
+ if (paperSizeSelect.value != 'custom' && landscapeCheckBox.checked)
+ {
+ newPageFormat = new mxRectangle(0, 0, newPageFormat.height, newPageFormat.width);
+ }
+
+ // Initial select of custom should not update page format to avoid update of combo
+ if ((!selectChanged || !customSize) && (newPageFormat.width != currentPageFormat.width ||
+ newPageFormat.height != currentPageFormat.height))
+ {
+ currentPageFormat = newPageFormat;
+
+ // Updates page format and reloads format panel
+ if (pageFormatListener != null)
+ {
+ pageFormatListener(currentPageFormat);
+ }
+ }
+ };
+
+ mxEvent.addListener(portraitSpan, 'click', function(evt)
+ {
+ portraitCheckBox.checked = true;
+ update(evt);
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(landscapeSpan, 'click', function(evt)
+ {
+ landscapeCheckBox.checked = true;
+ update(evt);
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(widthInput, 'blur', update);
+ mxEvent.addListener(widthInput, 'click', update);
+ mxEvent.addListener(heightInput, 'blur', update);
+ mxEvent.addListener(heightInput, 'click', update);
+ mxEvent.addListener(landscapeCheckBox, 'change', update);
+ mxEvent.addListener(portraitCheckBox, 'change', update);
+ mxEvent.addListener(paperSizeSelect, 'change', function(evt)
+ {
+ // Handles special case where custom was chosen
+ customSize = paperSizeSelect.value == 'custom';
+ update(evt, true);
+ });
+
+ update();
+
+ return {set: function(value)
+ {
+ pageFormat = value;
+ listener(null, null, true);
+ },get: function()
+ {
+ return currentPageFormat;
+ }, widthInput: widthInput,
+ heightInput: heightInput};
+};
+
+/**
+ *
+ */
+PageSetupDialog.getFormats = function()
+{
+ return [{key: 'letter', title: 'US-Letter (8,5" x 11")', format: mxConstants.PAGE_FORMAT_LETTER_PORTRAIT},
+ {key: 'legal', title: 'US-Legal (8,5" x 14")', format: new mxRectangle(0, 0, 850, 1400)},
+ {key: 'tabloid', title: 'US-Tabloid (11" x 17")', format: new mxRectangle(0, 0, 1100, 1700)},
+ {key: 'executive', title: 'US-Executive (7" x 10")', format: new mxRectangle(0, 0, 700, 1000)},
+ {key: 'a0', title: 'A0 (841 mm x 1189 mm)', format: new mxRectangle(0, 0, 3300, 4681)},
+ {key: 'a1', title: 'A1 (594 mm x 841 mm)', format: new mxRectangle(0, 0, 2339, 3300)},
+ {key: 'a2', title: 'A2 (420 mm x 594 mm)', format: new mxRectangle(0, 0, 1654, 2336)},
+ {key: 'a3', title: 'A3 (297 mm x 420 mm)', format: new mxRectangle(0, 0, 1169, 1654)},
+ {key: 'a4', title: 'A4 (210 mm x 297 mm)', format: mxConstants.PAGE_FORMAT_A4_PORTRAIT},
+ {key: 'a5', title: 'A5 (148 mm x 210 mm)', format: new mxRectangle(0, 0, 583, 827)},
+ {key: 'a6', title: 'A6 (105 mm x 148 mm)', format: new mxRectangle(0, 0, 413, 583)},
+ {key: 'a7', title: 'A7 (74 mm x 105 mm)', format: new mxRectangle(0, 0, 291, 413)},
+ {key: 'b4', title: 'B4 (250 mm x 353 mm)', format: new mxRectangle(0, 0, 980, 1390)},
+ {key: 'b5', title: 'B5 (176 mm x 250 mm)', format: new mxRectangle(0, 0, 690, 980)},
+ {key: '16-9', title: '16:9 (1600 x 900)', format: new mxRectangle(0, 0, 900, 1600)},
+ {key: '16-10', title: '16:10 (1920 x 1200)', format: new mxRectangle(0, 0, 1200, 1920)},
+ {key: '4-3', title: '4:3 (1600 x 1200)', format: new mxRectangle(0, 0, 1200, 1600)},
+ {key: 'custom', title: mxResources.get('custom'), format: null}];
+};
+
+/**
+ * Constructs a new filename dialog.
+ */
+var FilenameDialog = function(editorUi, filename, buttonText, fn, label, validateFn, content, helpLink, closeOnBtn, cancelFn, hints, w, lblW)
+{
+ closeOnBtn = (closeOnBtn != null) ? closeOnBtn : true;
+ var row, td;
+
+ var table = document.createElement('table');
+ var tbody = document.createElement('tbody');
+ table.style.position = 'absolute';
+ table.style.top = '30px';
+ table.style.left = '20px';
+
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.textOverflow = 'ellipsis';
+ td.style.whiteSpace = 'nowrap';
+ td.style.textAlign = 'right';
+ td.style.maxWidth = (lblW? lblW + 15 : 100) + 'px';
+ td.style.fontSize = '10pt';
+ td.style.width = (lblW? lblW : 84) + 'px';
+ mxUtils.write(td, (label || mxResources.get('filename')) + ':');
+
+ row.appendChild(td);
+
+ var nameInput = document.createElement('input');
+ nameInput.setAttribute('value', filename || '');
+ nameInput.style.marginLeft = '4px';
+ nameInput.style.width = (w != null) ? w + 'px' : '180px';
+
+ var genericBtn = mxUtils.button(buttonText, function()
+ {
+ if (validateFn == null || validateFn(nameInput.value))
+ {
+ if (closeOnBtn)
+ {
+ editorUi.hideDialog();
+ }
+
+ fn(nameInput.value);
+ }
+ });
+ genericBtn.className = 'geBtn gePrimaryBtn';
+
+ this.init = function()
+ {
+ if (label == null && content != null)
+ {
+ return;
+ }
+
+ if (hints != null)
+ {
+ Editor.selectFilename(nameInput);
+ }
+ else
+ {
+ nameInput.focus();
+
+ if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
+ {
+ nameInput.select();
+ }
+ else
+ {
+ document.execCommand('selectAll', false, null);
+ }
+ }
+
+ // Installs drag and drop handler for links
+ if (Graph.fileSupport)
+ {
+ // Setup the dnd listeners
+ var dlg = table.parentNode;
+
+ if (dlg != null)
+ {
+ var graph = editorUi.editor.graph;
+ var dropElt = null;
+
+ mxEvent.addListener(dlg, 'dragleave', function(evt)
+ {
+ if (dropElt != null)
+ {
+ dropElt.style.backgroundColor = '';
+ dropElt = null;
+ }
+
+ evt.stopPropagation();
+ evt.preventDefault();
+ });
+
+ mxEvent.addListener(dlg, 'dragover', mxUtils.bind(this, function(evt)
+ {
+ // IE 10 does not implement pointer-events so it can't have a drop highlight
+ if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
+ {
+ dropElt = nameInput;
+ dropElt.style.backgroundColor = '#ebf2f9';
+ }
+
+ evt.stopPropagation();
+ evt.preventDefault();
+ }));
+
+ mxEvent.addListener(dlg, 'drop', mxUtils.bind(this, function(evt)
+ {
+ if (dropElt != null)
+ {
+ dropElt.style.backgroundColor = '';
+ dropElt = null;
+ }
+
+ if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0)
+ {
+ nameInput.value = decodeURIComponent(evt.dataTransfer.getData('text/uri-list'));
+ genericBtn.click();
+ }
+
+ evt.stopPropagation();
+ evt.preventDefault();
+ }));
+ }
+ }
+ };
+
+ td = document.createElement('td');
+ td.style.whiteSpace = 'nowrap';
+ td.appendChild(nameInput);
+ row.appendChild(td);
+
+ if (label != null || content == null)
+ {
+ tbody.appendChild(row);
+
+ if (hints != null)
+ {
+ td.appendChild(FilenameDialog.createTypeHint(editorUi, nameInput, hints));
+
+ if (editorUi.editor.diagramFileTypes != null)
+ {
+ row = document.createElement('tr');
+
+ td = document.createElement('td');
+ td.style.textOverflow = 'ellipsis';
+ td.style.textAlign = 'right';
+ td.style.maxWidth = '100px';
+ td.style.fontSize = '10pt';
+ td.style.width = '84px';
+ mxUtils.write(td, mxResources.get('type') + ':');
+ row.appendChild(td);
+
+ td = document.createElement('td');
+ td.style.whiteSpace = 'nowrap';
+ row.appendChild(td);
+
+ var typeSelect = FilenameDialog.createFileTypes(editorUi,
+ nameInput, editorUi.editor.diagramFileTypes);
+ typeSelect.style.marginLeft = '4px';
+ typeSelect.style.width = '198px';
+
+ td.appendChild(typeSelect);
+ nameInput.style.width = (w != null) ? (w - 40) + 'px' : '190px';
+
+ row.appendChild(td);
+ tbody.appendChild(row);
+ }
+ }
+ }
+
+ if (content != null)
+ {
+ row = document.createElement('tr');
+ td = document.createElement('td');
+ td.colSpan = 2;
+ td.appendChild(content);
+ row.appendChild(td);
+ tbody.appendChild(row);
+ }
+
+ row = document.createElement('tr');
+ td = document.createElement('td');
+ td.colSpan = 2;
+ td.style.paddingTop = (hints != null) ? '12px' : '20px';
+ td.style.whiteSpace = 'nowrap';
+ td.setAttribute('align', 'right');
+
+ var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
+ {
+ editorUi.hideDialog();
+
+ if (cancelFn != null)
+ {
+ cancelFn();
+ }
+ });
+ cancelBtn.className = 'geBtn';
+
+ if (editorUi.editor.cancelFirst)
+ {
+ td.appendChild(cancelBtn);
+ }
+
+ if (helpLink != null)
+ {
+ var helpBtn = mxUtils.button(mxResources.get('help'), function()
+ {
+ editorUi.editor.graph.openLink(helpLink);
+ });
+
+ helpBtn.className = 'geBtn';
+ td.appendChild(helpBtn);
+ }
+
+ mxEvent.addListener(nameInput, 'keypress', function(e)
+ {
+ if (e.keyCode == 13)
+ {
+ genericBtn.click();
+ }
+ });
+
+ td.appendChild(genericBtn);
+
+ if (!editorUi.editor.cancelFirst)
+ {
+ td.appendChild(cancelBtn);
+ }
+
+ row.appendChild(td);
+ tbody.appendChild(row);
+ table.appendChild(tbody);
+
+ this.container = table;
+};
+
+/**
+ *
+ */
+FilenameDialog.filenameHelpLink = null;
+
+/**
+ *
+ */
+FilenameDialog.createTypeHint = function(ui, nameInput, hints)
+{
+ var hint = document.createElement('img');
+ hint.style.backgroundPosition = 'center bottom';
+ hint.style.backgroundRepeat = 'no-repeat';
+ hint.style.margin = '2px 0 0 4px';
+ hint.style.verticalAlign = 'top';
+ hint.style.cursor = 'pointer';
+ hint.style.height = '16px';
+ hint.style.width = '16px';
+ mxUtils.setOpacity(hint, 70);
+
+ var nameChanged = function()
+ {
+ hint.setAttribute('src', Editor.helpImage);
+ hint.setAttribute('title', mxResources.get('help'));
+
+ for (var i = 0; i < hints.length; i++)
+ {
+ if (hints[i].ext.length > 0 && nameInput.value.toLowerCase().substring(
+ nameInput.value.length - hints[i].ext.length - 1) == '.' + hints[i].ext)
+ {
+ hint.setAttribute('title', mxResources.get(hints[i].title));
+ break;
+ }
+ }
+ };
+
+ mxEvent.addListener(nameInput, 'keyup', nameChanged);
+ mxEvent.addListener(nameInput, 'change', nameChanged);
+ mxEvent.addListener(hint, 'click', function(evt)
+ {
+ var title = hint.getAttribute('title');
+
+ if (hint.getAttribute('src') == Editor.helpImage)
+ {
+ ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
+ }
+ else if (title != '')
+ {
+ ui.showError(null, title, mxResources.get('help'), function()
+ {
+ ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
+ }, null, mxResources.get('ok'), null, null, null, 340, 90);
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ nameChanged();
+
+ return hint;
+};
+
+/**
+ *
+ */
+FilenameDialog.createFileTypes = function(editorUi, nameInput, types)
+{
+ var typeSelect = document.createElement('select');
+
+ for (var i = 0; i < types.length; i++)
+ {
+ var typeOption = document.createElement('option');
+ typeOption.setAttribute('value', i);
+ mxUtils.write(typeOption, mxResources.get(types[i].description) +
+ ' (.' + types[i].extension + ')');
+ typeSelect.appendChild(typeOption);
+ }
+
+ mxEvent.addListener(typeSelect, 'change', function(evt)
+ {
+ var ext = types[typeSelect.value].extension;
+ var idx2 = nameInput.value.lastIndexOf('.drawio.');
+ var idx = (idx2 > 0) ? idx2 : nameInput.value.lastIndexOf('.');
+
+ if (ext != 'drawio')
+ {
+ ext = 'drawio.' + ext;
+ }
+
+ if (idx > 0)
+ {
+ nameInput.value = nameInput.value.substring(0, idx + 1) + ext;
+ }
+ else
+ {
+ nameInput.value = nameInput.value + '.' + ext;
+ }
+
+ if ('createEvent' in document)
+ {
+ var changeEvent = document.createEvent('HTMLEvents');
+ changeEvent.initEvent('change', false, true);
+ nameInput.dispatchEvent(changeEvent);
+ }
+ else
+ {
+ nameInput.fireEvent('onchange');
+ }
+ });
+
+ var nameInputChanged = function(evt)
+ {
+ var name = nameInput.value.toLowerCase();
+ var active = 0;
+
+ // Finds current extension
+ for (var i = 0; i < types.length; i++)
+ {
+ var ext = types[i].extension;
+ var subExt = null;
+
+ if (ext != 'drawio')
+ {
+ subExt = ext;
+ ext = '.drawio.' + ext;
+ }
+
+ if (name.substring(name.length - ext.length - 1) == '.' + ext ||
+ (subExt != null && name.substring(name.length - subExt.length - 1) == '.' + subExt))
+ {
+ active = i;
+ break;
+ }
+ }
+
+ typeSelect.value = active;
+ };
+
+ mxEvent.addListener(nameInput, 'change', nameInputChanged);
+ mxEvent.addListener(nameInput, 'keyup', nameInputChanged);
+ nameInputChanged();
+
+ return typeSelect;
+};
+
+/**
+ *
+ */
+var WrapperWindow = function(editorUi, title, x, y, w, h, fn)
+{
+ var div = editorUi.createSidebarContainer();
+ fn(div);
+
+ this.window = new mxWindow(title, div, x, y, w, h, true, true);
+ this.window.destroyOnClose = false;
+ this.window.setMaximizable(false);
+ this.window.setResizable(true);
+ this.window.setClosable(true);
+ this.window.setVisible(true);
+
+ editorUi.installResizeHandler(this, true);
+
+ // Workaround for text selection starting in Safari
+ // when dragging shapes outside of window
+ if (mxClient.IS_SF)
+ {
+ this.window.div.onselectstart = mxUtils.bind(this, function(evt)
+ {
+ if (evt == null)
+ {
+ evt = window.event;
+ }
+
+ return (evt != null && editorUi.isSelectionAllowed(evt));
+ });
+ }
+};
+
+/**
+ * Static overrides
+ */
+(function()
+{
+ // Uses HTML for background pages (to support grid background image)
+ mxGraphView.prototype.validateBackgroundPage = function()
+ {
+ var graph = this.graph;
+
+ if (graph.container != null && !graph.transparentBackground)
+ {
+ if (graph.pageVisible)
+ {
+ var bounds = this.getBackgroundPageBounds();
+
+ if (this.backgroundPageShape == null)
+ {
+ // Finds first element in graph container
+ var firstChild = graph.container.firstChild;
+
+ while (firstChild != null && firstChild.nodeType != mxConstants.NODETYPE_ELEMENT)
+ {
+ firstChild = firstChild.nextSibling;
+ }
+
+ if (firstChild != null)
+ {
+ this.backgroundPageShape = this.createBackgroundPageShape(bounds);
+ this.backgroundPageShape.scale = 1;
+
+ // IE8 standards has known rendering issues inside mxWindow but not using shadow is worse.
+ this.backgroundPageShape.isShadow = true;
+ this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
+ this.backgroundPageShape.init(graph.container);
+
+ // Required for the browser to render the background page in correct order
+ firstChild.style.position = 'absolute';
+ graph.container.insertBefore(this.backgroundPageShape.node, firstChild);
+ this.backgroundPageShape.redraw();
+
+ this.backgroundPageShape.node.className = 'geBackgroundPage';
+
+ // Adds listener for double click handling on background
+ mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ graph.dblClick(evt);
+ })
+ );
+
+ // Adds basic listeners for graph event dispatching outside of the
+ // container and finishing the handling of a single gesture
+ mxEvent.addGestureListeners(this.backgroundPageShape.node,
+ mxUtils.bind(this, function(evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+ }),
+ mxUtils.bind(this, function(evt)
+ {
+ // Hides the tooltip if mouse is outside container
+ if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover())
+ {
+ graph.tooltipHandler.hide();
+ }
+
+ if (graph.isMouseDown && !mxEvent.isConsumed(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+ }
+ }),
+ mxUtils.bind(this, function(evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+ })
+ );
+ }
+ }
+ else
+ {
+ this.backgroundPageShape.scale = 1;
+ this.backgroundPageShape.bounds = bounds;
+ this.backgroundPageShape.redraw();
+ }
+ }
+ else if (this.backgroundPageShape != null)
+ {
+ this.backgroundPageShape.destroy();
+ this.backgroundPageShape = null;
+ }
+
+ this.validateBackgroundStyles();
+ }
+ };
+
+ // Updates the CSS of the background to draw the grid
+ mxGraphView.prototype.validateBackgroundStyles = function(factor, cx, cy)
+ {
+ var graph = this.graph;
+ factor = (factor != null) ? factor : 1;
+ var color = (graph.background == null || graph.background == mxConstants.NONE) ?
+ graph.defaultPageBackgroundColor : graph.background;
+ var gridColor = (color != null && this.gridColor != color.toLowerCase()) ? this.gridColor : '#ffffff';
+ var image = 'none';
+ var position = '';
+
+ if (graph.isGridEnabled() || graph.gridVisible)
+ {
+ var phase = 10;
+
+ if (mxClient.IS_SVG)
+ {
+ // Generates the SVG required for drawing the dynamic grid
+ image = unescape(encodeURIComponent(this.createSvgGrid(gridColor, factor)));
+ image = (window.btoa) ? btoa(image) : Base64.encode(image, true);
+ image = 'url(' + 'data:image/svg+xml;base64,' + image + ')'
+ phase = graph.gridSize * this.scale * this.gridSteps * factor;
+ }
+ else
+ {
+ // Fallback to grid wallpaper with fixed size
+ image = 'url(' + this.gridImage + ')';
+ }
+
+ var x0 = 0;
+ var y0 = 0;
+
+ var dx = (cx != null) ? cx - this.translate.x * this.scale : 0;
+ var dy = (cy != null) ? cy - this.translate.y * this.scale : 0;
+
+ var p = graph.gridSize * this.scale * this.gridSteps;
+ var ddx = dx % p;
+ var ddy = dy % p;
+
+ if (graph.view.backgroundPageShape != null)
+ {
+ var bds = this.getBackgroundPageBounds();
+
+ x0 = 1 + bds.x;
+ y0 = 1 + bds.y;
+ }
+
+ // Computes the offset to maintain origin for grid
+ position = -Math.round(phase - mxUtils.mod(this.translate.x * this.scale - x0 + dx, phase) + ddx * factor) + 'px ' +
+ -Math.round(phase - mxUtils.mod(this.translate.y * this.scale - y0 + dy, phase) + ddy * factor) + 'px';
+ }
+
+ var canvas = graph.view.canvas;
+
+ if (canvas.ownerSVGElement != null)
+ {
+ canvas = canvas.ownerSVGElement;
+ }
+
+ var useDiagramBackground = !Editor.isDarkMode() && graph.enableDiagramBackground;
+
+ if (graph.view.backgroundPageShape != null)
+ {
+ graph.view.backgroundPageShape.node.style.backgroundPosition = position;
+ graph.view.backgroundPageShape.node.style.backgroundImage = image;
+ graph.view.backgroundPageShape.node.style.backgroundColor = color;
+ graph.view.backgroundPageShape.node.style.borderColor = graph.defaultPageBorderColor;
+ graph.container.className = 'geDiagramContainer geDiagramBackdrop';
+ canvas.style.backgroundImage = 'none';
+ canvas.style.backgroundColor = '';
+
+ if (useDiagramBackground)
+ {
+ graph.container.style.backgroundColor = graph.diagramBackgroundColor;
+ }
+ else
+ {
+ graph.container.style.backgroundColor = '';
+ }
+ }
+ else
+ {
+ graph.container.className = 'geDiagramContainer';
+ canvas.style.backgroundPosition = position;
+ canvas.style.backgroundImage = image;
+
+ if (useDiagramBackground && (graph.background == null ||
+ graph.background == mxConstants.NONE))
+ {
+ canvas.style.backgroundColor = graph.diagramBackgroundColor;
+ graph.container.style.backgroundColor = '';
+ }
+ else
+ {
+ canvas.style.backgroundColor = color;
+ }
+ }
+ };
+
+ // Returns the SVG required for painting the background grid.
+ mxGraphView.prototype.createSvgGrid = function(color, factor)
+ {
+ factor = (factor != null) ? factor : 1;
+ var tmp = this.graph.gridSize * this.scale * factor;
+
+ while (tmp < this.minGridSize)
+ {
+ tmp *= 2;
+ }
+
+ var tmp2 = this.gridSteps * tmp;
+
+ // Small grid lines
+ var d = [];
+
+ for (var i = 1; i < this.gridSteps; i++)
+ {
+ var tmp3 = i * tmp;
+ d.push('M 0 ' + tmp3 + ' L ' + tmp2 + ' ' + tmp3 + ' M ' + tmp3 + ' 0 L ' + tmp3 + ' ' + tmp2);
+ }
+
+ // KNOWN: Rounding errors for certain scales (eg. 144%, 121% in Chrome, FF and Safari). Workaround
+ // in Chrome is to use 100% for the svg size, but this results in blurred grid for large diagrams.
+ var size = tmp2;
+ var svg = '';
+
+ return svg;
+ };
+
+ // Adds panning for the grid with no page view and disabled scrollbars
+ var mxGraphPanGraph = mxGraph.prototype.panGraph;
+ mxGraph.prototype.panGraph = function(dx, dy)
+ {
+ mxGraphPanGraph.apply(this, arguments);
+
+ if (this.shiftPreview1 != null)
+ {
+ var canvas = this.view.canvas;
+
+ if (canvas.ownerSVGElement != null)
+ {
+ canvas = canvas.ownerSVGElement;
+ }
+
+ var phase = this.gridSize * this.view.scale * this.view.gridSteps;
+ var position = -Math.round(phase - mxUtils.mod(this.view.translate.x * this.view.scale + dx, phase)) + 'px ' +
+ -Math.round(phase - mxUtils.mod(this.view.translate.y * this.view.scale + dy, phase)) + 'px';
+ canvas.style.backgroundPosition = position;
+ }
+ };
+
+ // Draws page breaks only within the page
+ mxGraph.prototype.updatePageBreaks = function(visible, width, height)
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+ var fmt = this.pageFormat;
+ var ps = scale * this.pageScale;
+
+ var bounds2 = this.view.getBackgroundPageBounds();
+
+ width = bounds2.width;
+ height = bounds2.height;
+ var bounds = new mxRectangle(scale * tr.x, scale * tr.y, fmt.width * ps, fmt.height * ps);
+
+ // Does not show page breaks if the scale is too small
+ visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
+
+ var horizontalCount = (visible) ? Math.ceil(height / bounds.height) - 1 : 0;
+ var verticalCount = (visible) ? Math.ceil(width / bounds.width) - 1 : 0;
+ var right = bounds2.x + width;
+ var bottom = bounds2.y + height;
+
+ if (this.horizontalPageBreaks == null && horizontalCount > 0)
+ {
+ this.horizontalPageBreaks = [];
+ }
+
+ if (this.verticalPageBreaks == null && verticalCount > 0)
+ {
+ this.verticalPageBreaks = [];
+ }
+
+ var drawPageBreaks = mxUtils.bind(this, function(breaks)
+ {
+ if (breaks != null)
+ {
+ var count = (breaks == this.horizontalPageBreaks) ? horizontalCount : verticalCount;
+
+ for (var i = 0; i <= count; i++)
+ {
+ var pts = (breaks == this.horizontalPageBreaks) ?
+ [new mxPoint(Math.round(bounds2.x), Math.round(bounds2.y + (i + 1) * bounds.height)),
+ new mxPoint(Math.round(right), Math.round(bounds2.y + (i + 1) * bounds.height))] :
+ [new mxPoint(Math.round(bounds2.x + (i + 1) * bounds.width), Math.round(bounds2.y)),
+ new mxPoint(Math.round(bounds2.x + (i + 1) * bounds.width), Math.round(bottom))];
+
+ if (breaks[i] != null)
+ {
+ breaks[i].points = pts;
+ breaks[i].redraw();
+ }
+ else
+ {
+ var pageBreak = new mxPolyline(pts, this.pageBreakColor);
+ pageBreak.dialect = this.dialect;
+ pageBreak.isDashed = this.pageBreakDashed;
+ pageBreak.pointerEvents = false;
+ pageBreak.init(this.view.backgroundPane);
+ pageBreak.redraw();
+
+ breaks[i] = pageBreak;
+ }
+ }
+
+ for (var i = count; i < breaks.length; i++)
+ {
+ if (breaks[i] != null)
+ {
+ breaks[i].destroy();
+ }
+ }
+
+ breaks.splice(count, breaks.length - count);
+ }
+ });
+
+ drawPageBreaks(this.horizontalPageBreaks);
+ drawPageBreaks(this.verticalPageBreaks);
+ };
+
+ // Disables removing relative children and table rows and cells from parents
+ var mxGraphHandlerShouldRemoveCellsFromParent = mxGraphHandler.prototype.shouldRemoveCellsFromParent;
+ mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.graph.isTableCell(cells[i]) || this.graph.isTableRow(cells[i]))
+ {
+ return false;
+ }
+ else if (this.graph.getModel().isVertex(cells[i]))
+ {
+ var geo = this.graph.getCellGeometry(cells[i]);
+
+ if (geo != null && geo.relative)
+ {
+ return false;
+ }
+ }
+ }
+
+ return mxGraphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
+ };
+
+ // Overrides to ignore hotspot only for target terminal
+ var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
+ mxConnectionHandler.prototype.createMarker = function()
+ {
+ var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
+
+ marker.intersects = mxUtils.bind(this, function(state, evt)
+ {
+ if (this.isConnecting())
+ {
+ return true;
+ }
+
+ return mxCellMarker.prototype.intersects.apply(marker, arguments);
+ });
+
+ return marker;
+ };
+
+ // Creates background page shape
+ mxGraphView.prototype.createBackgroundPageShape = function(bounds)
+ {
+ return new mxRectangleShape(bounds, '#ffffff', this.graph.defaultPageBorderColor);
+ };
+
+ // Fits the number of background pages to the graph
+ mxGraphView.prototype.getBackgroundPageBounds = function()
+ {
+ var gb = this.getGraphBounds();
+
+ // Computes unscaled, untranslated graph bounds
+ var x = (gb.width > 0) ? gb.x / this.scale - this.translate.x : 0;
+ var y = (gb.height > 0) ? gb.y / this.scale - this.translate.y : 0;
+ var w = gb.width / this.scale;
+ var h = gb.height / this.scale;
+
+ var fmt = this.graph.pageFormat;
+ var ps = this.graph.pageScale;
+
+ var pw = fmt.width * ps;
+ var ph = fmt.height * ps;
+
+ var x0 = Math.floor(Math.min(0, x) / pw);
+ var y0 = Math.floor(Math.min(0, y) / ph);
+ var xe = Math.ceil(Math.max(1, x + w) / pw);
+ var ye = Math.ceil(Math.max(1, y + h) / ph);
+
+ var rows = xe - x0;
+ var cols = ye - y0;
+
+ var bounds = new mxRectangle(this.scale * (this.translate.x + x0 * pw), this.scale *
+ (this.translate.y + y0 * ph), this.scale * rows * pw, this.scale * cols * ph);
+
+ return bounds;
+ };
+
+ // Add panning for background page in VML
+ var graphPanGraph = mxGraph.prototype.panGraph;
+ mxGraph.prototype.panGraph = function(dx, dy)
+ {
+ graphPanGraph.apply(this, arguments);
+
+ if ((this.dialect != mxConstants.DIALECT_SVG && this.view.backgroundPageShape != null) &&
+ (!this.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.container)))
+ {
+ this.view.backgroundPageShape.node.style.marginLeft = dx + 'px';
+ this.view.backgroundPageShape.node.style.marginTop = dy + 'px';
+ }
+ };
+
+ /**
+ * Consumes click events for disabled menu items.
+ */
+ var mxPopupMenuAddItem = mxPopupMenu.prototype.addItem;
+ mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled)
+ {
+ var result = mxPopupMenuAddItem.apply(this, arguments);
+
+ if (enabled != null && !enabled)
+ {
+ mxEvent.addListener(result, 'mousedown', function(evt)
+ {
+ mxEvent.consume(evt);
+ });
+ }
+
+ return result;
+ };
+
+ /**
+ * Selects tables before cells and rows.
+ */
+ var mxGraphHandlerIsPropagateSelectionCell = mxGraphHandler.prototype.isPropagateSelectionCell;
+ mxGraphHandler.prototype.isPropagateSelectionCell = function(cell, immediate, me)
+ {
+ var result = false;
+ var parent = this.graph.model.getParent(cell)
+
+ if (immediate)
+ {
+ var geo = (this.graph.model.isEdge(cell)) ? null :
+ this.graph.getCellGeometry(cell);
+
+ result = !this.graph.model.isEdge(parent) &&
+ !this.graph.isSiblingSelected(cell) &&
+ ((geo != null && geo.relative) ||
+ !this.graph.isContainer(parent) ||
+ this.graph.isPart(cell));
+ }
+ else
+ {
+ result = mxGraphHandlerIsPropagateSelectionCell.apply(this, arguments);
+
+ if (this.graph.isTableCell(cell) || this.graph.isTableRow(cell))
+ {
+ var table = parent;
+
+ if (!this.graph.isTable(table))
+ {
+ table = this.graph.model.getParent(table);
+ }
+
+ result = !this.graph.selectionCellsHandler.isHandled(table) ||
+ (this.graph.isCellSelected(table) && this.graph.isToggleEvent(me.getEvent())) ||
+ (this.graph.isCellSelected(cell) && !this.graph.isToggleEvent(me.getEvent())) ||
+ (this.graph.isTableCell(cell) && this.graph.isCellSelected(parent));
+ }
+ }
+
+ return result;
+ };
+
+ /**
+ * Returns last selected ancestor
+ */
+ mxPopupMenuHandler.prototype.getCellForPopupEvent = function(me)
+ {
+ var cell = me.getCell();
+ var model = this.graph.getModel();
+ var parent = model.getParent(cell);
+ var state = this.graph.view.getState(parent);
+ var selected = this.graph.isCellSelected(cell);
+
+ while (state != null && (model.isVertex(parent) || model.isEdge(parent)))
+ {
+ var temp = this.graph.isCellSelected(parent);
+ selected = selected || temp;
+
+ if (temp || (!selected && (this.graph.isTableCell(cell) ||
+ this.graph.isTableRow(cell))))
+ {
+ cell = parent;
+ }
+
+ parent = model.getParent(parent);
+ }
+
+ return cell;
+ };
+
+})();
+
+
+/**
+ * Adds drawing and update of the shape number.
+ */
+mxGraphView.prototype.redrawEnumerationState = function(state)
+{
+ var enumerate = mxUtils.getValue(state.style, 'enumerate', 0) == '1';
+
+ if (enumerate && state.secondLabel == null)
+ {
+ state.secondLabel = new mxText('', new mxRectangle(),
+ mxConstants.ALIGN_LEFT, mxConstants.ALIGN_BOTTOM);
+ state.secondLabel.size = 12;
+ state.secondLabel.state = state;
+ state.secondLabel.dialect = mxConstants.DIALECT_STRICTHTML;
+
+ this.graph.cellRenderer.initializeLabel(state, state.secondLabel);
+ }
+ else if (!enumerate && state.secondLabel != null)
+ {
+ state.secondLabel.destroy();
+ state.secondLabel = null;
+ }
+
+ var shape = state.secondLabel;
+
+ if (shape != null)
+ {
+ var s = state.view.scale;
+ var value = this.createEnumerationValue(state);
+ var bounds = this.graph.model.isVertex(state.cell) ?
+ new mxRectangle(state.x + state.width - 4 * s, state.y + 4 * s, 0, 0) :
+ mxRectangle.fromPoint(state.view.getPoint(state));
+
+ if (!shape.bounds.equals(bounds) || shape.value != value || shape.scale != s)
+ {
+ shape.bounds = bounds;
+ shape.value = value;
+ shape.scale = s;
+ shape.redraw();
+ }
+ }
+};
+
+Editor.GUID_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
+Editor.GUID_LENGTH = 20;
+Editor.guid = function(a) {
+ a = null != a ? a : Editor.GUID_LENGTH;
+ for (var b = [], c = 0; c < a; c++)
+ b.push(Editor.GUID_ALPHABET.charAt(Math.floor(Math.random() * Editor.GUID_ALPHABET.length)));
+ return b.join("")
+}
\ No newline at end of file
diff --git a/oaweb/public/cherry/drawio/EditorUi.js b/oaweb/public/cherry/drawio/EditorUi.js
new file mode 100644
index 0000000..61ac763
--- /dev/null
+++ b/oaweb/public/cherry/drawio/EditorUi.js
@@ -0,0 +1,6184 @@
+/**
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Constructs a new graph editor
+ */
+EditorUi = function (editor, container, lightbox) {
+ mxEventSource.call(this);
+
+ this.destroyFunctions = [];
+ this.editor = editor || new Editor();
+ this.container = container || document.body;
+
+ var ui = this;
+ var graph = this.editor.graph;
+ graph.lightbox = lightbox;
+
+ // Overrides graph bounds to include background images
+ var graphGetGraphBounds = graph.getGraphBounds;
+
+ graph.getGraphBounds = function () {
+ var bounds = graphGetGraphBounds.apply(this, arguments);
+ var img = this.backgroundImage;
+
+ if (img != null && img.width != null && img.height != null) {
+ var t = this.view.translate;
+ var s = this.view.scale;
+
+ bounds = mxRectangle.fromRectangle(bounds);
+ bounds.add(new mxRectangle(
+ (t.x + img.x) * s, (t.y + img.y) * s,
+ img.width * s, img.height * s));
+ }
+
+ return bounds;
+ };
+
+ // Faster scrollwheel zoom is possible with CSS transforms
+ if (graph.useCssTransforms) {
+ this.lazyZoomDelay = 0;
+ }
+
+ // Pre-fetches submenu image or replaces with embedded image if supported
+ if (mxClient.IS_SVG) {
+ mxPopupMenu.prototype.submenuImage = 'data:image/gif;base64,R0lGODlhCQAJAIAAAP///zMzMyH5BAEAAAAALAAAAAAJAAkAAAIPhI8WebHsHopSOVgb26AAADs=';
+ }
+ else {
+ new Image().src = mxPopupMenu.prototype.submenuImage;
+ }
+
+ // Pre-fetches connect image
+ if (!mxClient.IS_SVG && mxConnectionHandler.prototype.connectImage != null) {
+ new Image().src = mxConnectionHandler.prototype.connectImage.src;
+ }
+
+ // Installs selection state listener
+ this.selectionStateListener = mxUtils.bind(this, function (sender, evt) {
+ this.clearSelectionState();
+ });
+
+ graph.getSelectionModel().addListener(mxEvent.CHANGE, this.selectionStateListener);
+ graph.getModel().addListener(mxEvent.CHANGE, this.selectionStateListener);
+ graph.addListener(mxEvent.EDITING_STARTED, this.selectionStateListener);
+ graph.addListener(mxEvent.EDITING_STOPPED, this.selectionStateListener);
+ graph.getView().addListener('unitChanged', this.selectionStateListener);
+
+ // Disables graph and forced panning in chromeless mode
+ if (this.editor.chromeless && !this.editor.editable) {
+ this.footerHeight = 0;
+ graph.isEnabled = function () { return false; };
+ graph.panningHandler.isForcePanningEvent = function (me) {
+ return !mxEvent.isPopupTrigger(me.getEvent());
+ };
+ }
+
+ // Creates the user interface
+ this.actions = new Actions(this);
+ this.menus = this.createMenus();
+
+ if (!graph.standalone) {
+ // Stores the current style and assigns it to new cells
+ var styles = ['rounded', 'shadow', 'glass', 'dashed', 'dashPattern', 'labelBackgroundColor',
+ 'labelBorderColor', 'comic', 'sketch', 'fillWeight', 'hachureGap', 'hachureAngle', 'jiggle',
+ 'disableMultiStroke', 'disableMultiStrokeFill', 'fillStyle', 'curveFitting',
+ 'simplification', 'sketchStyle', 'pointerEvents', 'strokeColor', 'strokeWidth'];
+ var connectStyles = ['shape', 'edgeStyle', 'curved', 'rounded', 'elbow', 'jumpStyle', 'jumpSize',
+ 'comic', 'sketch', 'fillWeight', 'hachureGap', 'hachureAngle', 'jiggle',
+ 'disableMultiStroke', 'disableMultiStrokeFill', 'fillStyle', 'curveFitting',
+ 'simplification', 'sketchStyle'];
+ // Styles to be ignored if applyAll is false
+ var ignoredEdgeStyles = ['curved', 'sourcePerimeterSpacing', 'targetPerimeterSpacing',
+ 'startArrow', 'startFill', 'startSize', 'endArrow', 'endFill', 'endSize'];
+ var vertexStyleIgnored = false;
+ var edgeStyleIgnored = false;
+
+ // Note: Everything that is not in styles is ignored (styles is augmented below)
+ this.setDefaultStyle = function (cell) {
+ try {
+ if (graph.getModel().isEdge(cell)) {
+ edgeStyleIgnored = false;
+ }
+ else {
+ vertexStyleIgnored = false;
+ }
+
+ var style = graph.getCellStyle(cell, false);
+ var values = [];
+ var keys = [];
+
+ for (var key in style) {
+ values.push(style[key]);
+ keys.push(key);
+ }
+
+ // Resets current style
+ if (graph.getModel().isEdge(cell)) {
+ graph.currentEdgeStyle = {};
+ }
+ else {
+ graph.currentVertexStyle = {}
+ }
+
+ this.fireEvent(new mxEventObject('styleChanged',
+ 'keys', keys, 'values', values,
+ 'cells', [cell], 'force', true));
+
+ // Blocks update of default style with style changes
+ // once the it was set using this function
+ if (graph.getModel().isEdge(cell)) {
+ edgeStyleIgnored = true;
+ }
+ else {
+ vertexStyleIgnored = true;
+ }
+ }
+ catch (e) {
+ this.handleError(e);
+ }
+ };
+
+ this.clearDefaultStyle = function () {
+ graph.currentEdgeStyle = mxUtils.clone(graph.defaultEdgeStyle);
+ graph.currentVertexStyle = mxUtils.clone(graph.defaultVertexStyle);
+ edgeStyleIgnored = false;
+ vertexStyleIgnored = false;
+
+ // Updates UI
+ this.fireEvent(new mxEventObject('styleChanged', 'keys', [], 'values', [], 'cells', []));
+ };
+
+ // Keys that should be ignored if the cell has a value (known: new default for all cells is html=1 so
+ // for the html key this effecticely only works for edges inserted via the connection handler)
+ var valueStyles = ['fontFamily', 'fontSource', 'fontSize', 'fontColor'];
+
+ for (var i = 0; i < valueStyles.length; i++) {
+ if (mxUtils.indexOf(styles, valueStyles[i]) < 0) {
+ styles.push(valueStyles[i]);
+ }
+ }
+
+ // Keys that always update the current edge style regardless of selection
+ var alwaysEdgeStyles = ['edgeStyle', 'startArrow', 'startFill', 'startSize', 'endArrow',
+ 'endFill', 'endSize'];
+
+ // Keys that are ignored together (if one appears all are ignored)
+ var keyGroups = [['startArrow', 'startFill', 'endArrow', 'endFill'],
+ ['startSize', 'endSize'],
+ ['sourcePerimeterSpacing', 'targetPerimeterSpacing'],
+ ['fillColor', 'gradientColor', 'gradientDirection'],
+ ['opacity'],
+ ['html']];
+
+ // Adds all keys used above to the styles array
+ for (var i = 0; i < keyGroups.length; i++) {
+ for (var j = 0; j < keyGroups[i].length; j++) {
+ styles.push(keyGroups[i][j]);
+ }
+ }
+
+ for (var i = 0; i < connectStyles.length; i++) {
+ if (mxUtils.indexOf(styles, connectStyles[i]) < 0) {
+ styles.push(connectStyles[i]);
+ }
+ }
+
+ // Implements a global current style for edges and vertices that is applied to new cells
+ var insertHandler = function (cells, asText, model, vertexStyle, edgeStyle, applyAll, recurse) {
+ vertexStyle = (vertexStyle != null) ? vertexStyle : graph.currentVertexStyle;
+ edgeStyle = (edgeStyle != null) ? edgeStyle : graph.currentEdgeStyle;
+ applyAll = (applyAll != null) ? applyAll : true;
+
+ model = (model != null) ? model : graph.getModel();
+
+ if (recurse) {
+ var temp = [];
+
+ for (var i = 0; i < cells.length; i++) {
+ temp = temp.concat(model.getDescendants(cells[i]));
+ }
+
+ cells = temp;
+ }
+
+ model.beginUpdate();
+ try {
+ for (var i = 0; i < cells.length; i++) {
+ var cell = cells[i];
+ var isText = asText;
+ var appliedStyles;
+
+ // Applies basic text styles for cells with text class
+ if (cell.style != null && !isText) {
+ pairs = cell.style.split(';');
+ isText = isText || mxUtils.indexOf(pairs, 'text') >= 0;
+ }
+
+ if (isText) {
+ // Applies only basic text styles
+ appliedStyles = ['fontSize', 'fontFamily', 'fontColor'];
+ }
+ else {
+ // Removes styles defined in the cell style from the styles to be applied
+ var cellStyle = model.getStyle(cell);
+ var tokens = (cellStyle != null) ? cellStyle.split(';') : [];
+ appliedStyles = styles.slice();
+
+ for (var j = 0; j < tokens.length; j++) {
+ var tmp = tokens[j];
+ var pos = tmp.indexOf('=');
+
+ if (pos >= 0) {
+ var key = tmp.substring(0, pos);
+ var index = mxUtils.indexOf(appliedStyles, key);
+
+ if (index >= 0) {
+ appliedStyles.splice(index, 1);
+ }
+
+ // Handles special cases where one defined style ignores other styles
+ for (var k = 0; k < keyGroups.length; k++) {
+ var group = keyGroups[k];
+
+ if (mxUtils.indexOf(group, key) >= 0) {
+ for (var l = 0; l < group.length; l++) {
+ var index2 = mxUtils.indexOf(appliedStyles, group[l]);
+
+ if (index2 >= 0) {
+ appliedStyles.splice(index2, 1);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Applies the current style to the cell
+ var edge = model.isEdge(cell);
+ var current = (edge) ? edgeStyle : vertexStyle;
+ var newStyle = model.getStyle(cell);
+
+ for (var j = 0; j < appliedStyles.length; j++) {
+ var key = appliedStyles[j];
+ var styleValue = current[key];
+
+ if (styleValue != null && key != 'edgeStyle' && (key != 'shape' || edge)) {
+ // Special case: Connect styles are not applied here but in the connection handler
+ if (!edge || applyAll || mxUtils.indexOf(ignoredEdgeStyles, key) < 0) {
+ newStyle = mxUtils.setStyle(newStyle, key, styleValue);
+ }
+ }
+ }
+
+ if (Editor.simpleLabels) {
+ newStyle = mxUtils.setStyle(mxUtils.setStyle(
+ newStyle, 'html', null), 'whiteSpace', null);
+ }
+
+ model.setStyle(cell, newStyle);
+ }
+ }
+ finally {
+ model.endUpdate();
+ }
+
+ return cells;
+ };
+
+ graph.addListener('cellsInserted', function (sender, evt) {
+ insertHandler(evt.getProperty('cells'), null, null, null, null, true, true);
+ });
+
+ graph.addListener('textInserted', function (sender, evt) {
+ insertHandler(evt.getProperty('cells'), true);
+ });
+
+ this.insertHandler = insertHandler;
+
+ this.createDivs();
+ this.createUi();
+ this.refresh();
+
+ // Disables HTML and text selection
+ var textEditing = mxUtils.bind(this, function (evt) {
+ if (evt == null) {
+ evt = window.event;
+ }
+
+ return graph.isEditing() || (evt != null && this.isSelectionAllowed(evt));
+ });
+
+ // Disables text selection while not editing and no dialog visible
+ if (this.container == document.body) {
+ this.menubarContainer.onselectstart = textEditing;
+ this.menubarContainer.onmousedown = textEditing;
+ this.toolbarContainer.onselectstart = textEditing;
+ this.toolbarContainer.onmousedown = textEditing;
+ this.diagramContainer.onselectstart = textEditing;
+ this.diagramContainer.onmousedown = textEditing;
+ this.sidebarContainer.onselectstart = textEditing;
+ this.sidebarContainer.onmousedown = textEditing;
+ this.formatContainer.onselectstart = textEditing;
+ this.formatContainer.onmousedown = textEditing;
+ this.footerContainer.onselectstart = textEditing;
+ this.footerContainer.onmousedown = textEditing;
+
+ if (this.tabContainer != null) {
+ // Mouse down is needed for drag and drop
+ this.tabContainer.onselectstart = textEditing;
+ }
+
+ // Workaround for rubberband selection on iPadOS 16
+ // Avoid on previous versions to allow label editing
+ if (mxClient.IS_IOS) {
+ function iOSversion() {
+ if (/iP(hone|od|ad)/.test(navigator.platform)) {
+ // supports iOS 2.0 and later:
+ var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
+
+ return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)];
+ }
+ }
+
+ var ver = iOSversion();
+
+ if (ver != null && ver[0] >= 16) {
+ mxUtils.setPrefixedStyle(this.menubarContainer.style, 'userSelect', 'none');
+ mxUtils.setPrefixedStyle(this.diagramContainer.style, 'userSelect', 'none');
+ mxUtils.setPrefixedStyle(this.sidebarContainer.style, 'userSelect', 'none');
+ mxUtils.setPrefixedStyle(this.formatContainer.style, 'userSelect', 'none');
+ mxUtils.setPrefixedStyle(this.footerContainer.style, 'userSelect', 'none');
+
+ if (this.tabContainer != null) {
+ mxUtils.setPrefixedStyle(this.tabContainer.style, 'userSelect', 'none');
+ }
+ }
+ }
+ }
+
+ // And uses built-in context menu while editing
+ if (!this.editor.chromeless || this.editor.editable) {
+ // Allows context menu for links in hints
+ var linkHandler = function (evt) {
+ if (evt != null) {
+ var source = mxEvent.getSource(evt);
+
+ if (source.nodeName == 'A') {
+ while (source != null) {
+ if (source.className == 'geHint') {
+ return true;
+ }
+
+ source = source.parentNode;
+ }
+ }
+ }
+
+ return textEditing(evt);
+ };
+
+ if (mxClient.IS_IE && (typeof (document.documentMode) === 'undefined' || document.documentMode < 9)) {
+ mxEvent.addListener(this.diagramContainer, 'contextmenu', linkHandler);
+ }
+ else {
+ // Allows browser context menu outside of diagram and sidebar
+ this.diagramContainer.oncontextmenu = linkHandler;
+ }
+ }
+ else {
+ graph.panningHandler.usePopupTrigger = false;
+ }
+
+ // Contains the main graph instance inside the given panel
+ graph.init(this.diagramContainer);
+
+ // Improves line wrapping for in-place editor
+ if (mxClient.IS_SVG && graph.view.getDrawPane() != null) {
+ var root = graph.view.getDrawPane().ownerSVGElement;
+
+ if (root != null) {
+ root.style.position = 'absolute';
+ }
+ }
+
+ // Creates hover icons
+ this.hoverIcons = this.createHoverIcons();
+
+ // Hides hover icons when cells are moved
+ if (graph.graphHandler != null) {
+ var graphHandlerStart = graph.graphHandler.start;
+
+ graph.graphHandler.start = function () {
+ if (ui.hoverIcons != null) {
+ ui.hoverIcons.reset();
+ }
+
+ graphHandlerStart.apply(this, arguments);
+ };
+ }
+
+ // Adds tooltip when mouse is over scrollbars to show space-drag panning option
+ mxEvent.addListener(this.diagramContainer, 'mousemove', mxUtils.bind(this, function (evt) {
+ var off = mxUtils.getOffset(this.diagramContainer);
+
+ if (mxEvent.getClientX(evt) - off.x - this.diagramContainer.clientWidth > 0 ||
+ mxEvent.getClientY(evt) - off.y - this.diagramContainer.clientHeight > 0) {
+ this.diagramContainer.setAttribute('title', mxResources.get('panTooltip'));
+ }
+ else {
+ this.diagramContainer.removeAttribute('title');
+ }
+ }));
+
+ // Overrides hovericons to disable while space key is pressed
+ var hoverIconsIsResetEvent = this.hoverIcons.isResetEvent;
+
+ this.hoverIcons.isResetEvent = function (evt, allowShift) {
+ return ui.isSpaceDown() || hoverIconsIsResetEvent.apply(this, arguments);
+ };
+
+ this.keydownHandler = mxUtils.bind(this, function (evt) {
+ if (evt.which == 16 /* Shift */) {
+ this.shiftDown = true;
+ }
+ else if (evt.which == 32 /* Space */ && !graph.isEditing()) {
+ this.spaceDown = true;
+ this.hoverIcons.reset();
+ graph.container.style.cursor = 'move';
+
+ // Disables scroll after space keystroke with scrollbars
+ if (!graph.isEditing() && mxEvent.getSource(evt) == graph.container) {
+ mxEvent.consume(evt);
+ }
+ }
+ else if (!mxEvent.isConsumed(evt) && evt.keyCode == 27 /* Escape */) {
+ this.hideDialog(null, true);
+ }
+ });
+
+ mxEvent.addListener(document, 'keydown', this.keydownHandler);
+
+ this.keyupHandler = mxUtils.bind(this, function (evt) {
+ graph.container.style.cursor = '';
+ this.spaceDown = false;
+ this.shiftDown = false;
+ });
+
+ mxEvent.addListener(document, 'keyup', this.keyupHandler);
+
+ // Forces panning for middle and right mouse buttons
+ var panningHandlerIsForcePanningEvent = graph.panningHandler.isForcePanningEvent;
+ graph.panningHandler.isForcePanningEvent = function (me) {
+ // Ctrl+left button is reported as right button in FF on Mac
+ return panningHandlerIsForcePanningEvent.apply(this, arguments) ||
+ ui.isSpaceDown() || (mxEvent.isMouseEvent(me.getEvent()) &&
+ (this.usePopupTrigger || !mxEvent.isPopupTrigger(me.getEvent())) &&
+ ((!mxEvent.isControlDown(me.getEvent()) &&
+ mxEvent.isRightMouseButton(me.getEvent())) ||
+ mxEvent.isMiddleMouseButton(me.getEvent())));
+ };
+
+ // Ctrl/Cmd+Enter applies editing value except in Safari where Ctrl+Enter creates
+ // a new line (while Enter creates a new paragraph and Shift+Enter stops)
+ var cellEditorIsStopEditingEvent = graph.cellEditor.isStopEditingEvent;
+ graph.cellEditor.isStopEditingEvent = function (evt) {
+ return cellEditorIsStopEditingEvent.apply(this, arguments) ||
+ (evt.keyCode == 13 && ((!mxClient.IS_SF && mxEvent.isControlDown(evt)) ||
+ (mxClient.IS_MAC && mxEvent.isMetaDown(evt)) ||
+ (mxClient.IS_SF && mxEvent.isShiftDown(evt))));
+ };
+
+ // Adds space+wheel for zoom
+ var graphIsZoomWheelEvent = graph.isZoomWheelEvent;
+
+ graph.isZoomWheelEvent = function () {
+ return ui.isSpaceDown() || graphIsZoomWheelEvent.apply(this, arguments);
+ };
+
+ // Switches toolbar for text editing
+ var textMode = false;
+ var fontMenu = null;
+ var sizeMenu = null;
+ var nodes = null;
+
+ var updateToolbar = mxUtils.bind(this, function () {
+ if (this.toolbar != null && textMode != graph.cellEditor.isContentEditing()) {
+ var node = this.toolbar.container.firstChild;
+ var newNodes = [];
+
+ while (node != null) {
+ var tmp = node.nextSibling;
+
+ if (mxUtils.indexOf(this.toolbar.staticElements, node) < 0) {
+ node.parentNode.removeChild(node);
+ newNodes.push(node);
+ }
+
+ node = tmp;
+ }
+
+ // Saves references to special items
+ var tmp1 = this.toolbar.fontMenu;
+ var tmp2 = this.toolbar.sizeMenu;
+
+ if (nodes == null) {
+ this.toolbar.createTextToolbar();
+ }
+ else {
+ for (var i = 0; i < nodes.length; i++) {
+ this.toolbar.container.appendChild(nodes[i]);
+ }
+
+ // Restores references to special items
+ this.toolbar.fontMenu = fontMenu;
+ this.toolbar.sizeMenu = sizeMenu;
+ }
+
+ textMode = graph.cellEditor.isContentEditing();
+ fontMenu = tmp1;
+ sizeMenu = tmp2;
+ nodes = newNodes;
+ }
+ });
+
+ // Overrides cell editor to update toolbar
+ var cellEditorStartEditing = graph.cellEditor.startEditing;
+ graph.cellEditor.startEditing = function () {
+ cellEditorStartEditing.apply(this, arguments);
+ updateToolbar();
+
+ if (graph.cellEditor.isContentEditing()) {
+ var updating = false;
+
+ var updateCssHandler = function () {
+ if (!updating) {
+ updating = true;
+
+ window.setTimeout(function () {
+ var node = graph.getSelectedEditingElement();
+
+ if (node != null) {
+ var css = mxUtils.getCurrentStyle(node);
+
+ if (css != null && ui.toolbar != null) {
+ ui.toolbar.setFontName(Graph.stripQuotes(css.fontFamily));
+ ui.toolbar.setFontSize(parseInt(css.fontSize));
+ }
+ }
+
+ updating = false;
+ }, 0);
+ }
+ };
+
+ mxEvent.addListener(graph.cellEditor.textarea, 'input', updateCssHandler)
+ mxEvent.addListener(graph.cellEditor.textarea, 'touchend', updateCssHandler);
+ mxEvent.addListener(graph.cellEditor.textarea, 'mouseup', updateCssHandler);
+ mxEvent.addListener(graph.cellEditor.textarea, 'keyup', updateCssHandler);
+ updateCssHandler();
+ }
+ };
+
+ // Updates toolbar and handles possible errors
+ var cellEditorStopEditing = graph.cellEditor.stopEditing;
+ graph.cellEditor.stopEditing = function (cell, trigger) {
+ try {
+ cellEditorStopEditing.apply(this, arguments);
+ updateToolbar();
+ }
+ catch (e) {
+ ui.handleError(e);
+ }
+ };
+
+ // Enables scrollbars and sets cursor style for the container
+ graph.container.setAttribute('tabindex', '0');
+ graph.container.style.cursor = 'default';
+
+ // Workaround for page scroll if embedded via iframe
+ if (window.self === window.top && graph.container.parentNode != null) {
+ try {
+ graph.container.focus();
+ }
+ catch (e) {
+ // ignores error in old versions of IE
+ }
+ }
+
+ // Keeps graph container focused on mouse down
+ var graphFireMouseEvent = graph.fireMouseEvent;
+ graph.fireMouseEvent = function (evtName, me, sender) {
+ try {
+ if (evtName == mxEvent.MOUSE_DOWN) {
+ this.container.focus();
+ }
+
+ graphFireMouseEvent.apply(this, arguments);
+ }
+ catch (e) {
+ ui.handleError(e);
+ }
+ };
+
+ // Adds error handling for foldCells
+ var graphFoldCells = graph.foldCells;
+ graph.foldCells = function (collapse, recurse, cells, checkFoldable, evt) {
+ try {
+ graphFoldCells.apply(this, arguments);
+ }
+ catch (e) {
+ ui.handleError(e);
+ }
+ };
+
+ // Configures automatic expand on mouseover
+ graph.popupMenuHandler.autoExpand = true;
+
+ // Installs context menu
+ if (this.menus != null) {
+ graph.popupMenuHandler.factoryMethod = mxUtils.bind(this, function (menu, cell, evt) {
+ this.menus.createPopupMenu(menu, cell, evt);
+ });
+ }
+
+ // Hides context menu
+ mxEvent.addGestureListeners(document, mxUtils.bind(this, function (evt) {
+ graph.popupMenuHandler.hideMenu();
+ }));
+
+ // Create handler for key events
+ this.keyHandler = this.createKeyHandler(editor);
+
+ // Getter for key handler
+ this.getKeyHandler = function () {
+ return keyHandler;
+ };
+
+ graph.connectionHandler.addListener(mxEvent.CONNECT, function (sender, evt) {
+ var cells = [evt.getProperty('cell')];
+
+ if (evt.getProperty('terminalInserted')) {
+ cells.push(evt.getProperty('terminal'));
+
+ window.setTimeout(function () {
+ if (ui.hoverIcons != null) {
+ ui.hoverIcons.update(graph.view.getState(cells[cells.length - 1]));
+ }
+ }, 0);
+ }
+
+ insertHandler(cells);
+ });
+
+ this.addListener('styleChanged', mxUtils.bind(this, function (sender, evt) {
+ var force = evt.getProperty('force');
+
+ // Checks if edges and/or vertices were modified
+ if (this.updateDefaultStyle || force) {
+ var cells = evt.getProperty('cells');
+ var vertex = false;
+ var edge = false;
+
+ if (cells.length > 0) {
+ for (var i = 0; i < cells.length; i++) {
+ vertex = graph.getModel().isVertex(cells[i]) || vertex;
+ edge = graph.getModel().isEdge(cells[i]) || edge;
+
+ if (edge && vertex) {
+ break;
+ }
+ }
+ }
+ else {
+ vertex = true;
+ edge = true;
+ }
+
+ vertex = vertex && !vertexStyleIgnored;
+ edge = edge && !edgeStyleIgnored;
+
+ var keys = evt.getProperty('keys');
+ var values = evt.getProperty('values');
+
+ for (var i = 0; i < keys.length; i++) {
+ var common = mxUtils.indexOf(valueStyles, keys[i]) >= 0;
+
+ // Ignores transparent stroke colors
+ if (keys[i] != 'strokeColor' || (values[i] != null && values[i] != 'none')) {
+ // Special case: Edge style and shape
+ if (mxUtils.indexOf(connectStyles, keys[i]) >= 0) {
+ if (edge || mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0) {
+ if (values[i] == null) {
+ delete graph.currentEdgeStyle[keys[i]];
+ }
+ else {
+ graph.currentEdgeStyle[keys[i]] = values[i];
+ }
+ }
+ // Uses style for vertex if defined in styles
+ else if (vertex && mxUtils.indexOf(styles, keys[i]) >= 0) {
+ if (values[i] == null) {
+ delete graph.currentVertexStyle[keys[i]];
+ }
+ else {
+ graph.currentVertexStyle[keys[i]] = values[i];
+ }
+ }
+ }
+ else if (mxUtils.indexOf(styles, keys[i]) >= 0) {
+ if (vertex || common) {
+ if (values[i] == null) {
+ delete graph.currentVertexStyle[keys[i]];
+ }
+ else {
+ graph.currentVertexStyle[keys[i]] = values[i];
+ }
+ }
+
+ if (edge || common || mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0) {
+ if (values[i] == null) {
+ delete graph.currentEdgeStyle[keys[i]];
+ }
+ else {
+ graph.currentEdgeStyle[keys[i]] = values[i];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (this.toolbar != null) {
+ this.toolbar.setFontName(graph.currentVertexStyle['fontFamily'] || Menus.prototype.defaultFont);
+ this.toolbar.setFontSize(graph.currentVertexStyle['fontSize'] || Menus.prototype.defaultFontSize);
+
+ if (this.toolbar.edgeStyleMenu != null) {
+ // Updates toolbar icon for edge style
+ var edgeStyleDiv = this.toolbar.edgeStyleMenu.getElementsByTagName('div')[0];
+
+ if (graph.currentEdgeStyle['edgeStyle'] == 'orthogonalEdgeStyle' && graph.currentEdgeStyle['curved'] == '1') {
+ edgeStyleDiv.className = 'geSprite geSprite-curved';
+ }
+ else if (graph.currentEdgeStyle['edgeStyle'] == 'straight' || graph.currentEdgeStyle['edgeStyle'] == 'none' ||
+ graph.currentEdgeStyle['edgeStyle'] == null) {
+ edgeStyleDiv.className = 'geSprite geSprite-straight';
+ }
+ else if (graph.currentEdgeStyle['edgeStyle'] == 'entityRelationEdgeStyle') {
+ edgeStyleDiv.className = 'geSprite geSprite-entity';
+ }
+ else if (graph.currentEdgeStyle['edgeStyle'] == 'elbowEdgeStyle') {
+ edgeStyleDiv.className = 'geSprite geSprite-' + ((graph.currentEdgeStyle['elbow'] == 'vertical') ?
+ 'verticalelbow' : 'horizontalelbow');
+ }
+ else if (graph.currentEdgeStyle['edgeStyle'] == 'isometricEdgeStyle') {
+ edgeStyleDiv.className = 'geSprite geSprite-' + ((graph.currentEdgeStyle['elbow'] == 'vertical') ?
+ 'verticalisometric' : 'horizontalisometric');
+ }
+ else {
+ edgeStyleDiv.className = 'geSprite geSprite-orthogonal';
+ }
+ }
+
+ if (this.toolbar.edgeShapeMenu != null) {
+ // Updates icon for edge shape
+ var edgeShapeDiv = this.toolbar.edgeShapeMenu.getElementsByTagName('div')[0];
+
+ if (graph.currentEdgeStyle['shape'] == 'link') {
+ edgeShapeDiv.className = 'geSprite geSprite-linkedge';
+ }
+ else if (graph.currentEdgeStyle['shape'] == 'flexArrow') {
+ edgeShapeDiv.className = 'geSprite geSprite-arrow';
+ }
+ else if (graph.currentEdgeStyle['shape'] == 'arrow') {
+ edgeShapeDiv.className = 'geSprite geSprite-simplearrow';
+ }
+ else {
+ edgeShapeDiv.className = 'geSprite geSprite-connection';
+ }
+ }
+ }
+ }));
+
+ // Update font size and font family labels
+ if (this.toolbar != null) {
+ var update = mxUtils.bind(this, function () {
+ var ff = graph.currentVertexStyle['fontFamily'] || 'Helvetica';
+ var fs = String(graph.currentVertexStyle['fontSize'] || '12');
+ var state = graph.getView().getState(graph.getSelectionCell());
+
+ if (state != null) {
+ ff = state.style[mxConstants.STYLE_FONTFAMILY] || ff;
+ fs = state.style[mxConstants.STYLE_FONTSIZE] || fs;
+
+ if (ff.length > 10) {
+ ff = ff.substring(0, 8) + '...';
+ }
+ }
+
+ this.toolbar.setFontName(ff);
+ this.toolbar.setFontSize(fs);
+ });
+
+ graph.getSelectionModel().addListener(mxEvent.CHANGE, update);
+ graph.getModel().addListener(mxEvent.CHANGE, update);
+ }
+
+ // Makes sure the current layer is visible when cells are added
+ graph.addListener(mxEvent.CELLS_ADDED, function (sender, evt) {
+ var cells = evt.getProperty('cells');
+ var parent = evt.getProperty('parent');
+
+ if (parent != null && graph.getModel().isLayer(parent) &&
+ !graph.isCellVisible(parent) && cells != null &&
+ cells.length > 0) {
+ graph.getModel().setVisible(parent, true);
+ }
+ });
+
+ // Global handler to hide the current menu
+ this.gestureHandler = mxUtils.bind(this, function (evt) {
+ if (this.currentMenu != null && mxEvent.getSource(evt) != this.currentMenu.div) {
+ this.hideCurrentMenu();
+ }
+ });
+
+ mxEvent.addGestureListeners(document, this.gestureHandler);
+
+ // Updates the editor UI after the window has been resized or the orientation changes
+ // Timeout is workaround for old IE versions which have a delay for DOM client sizes.
+ var resizeThread = null;
+
+ this.resizeHandler = mxUtils.bind(this, function () {
+ if (resizeThread != null) {
+ window.clearTimeout(resizeThread);
+ }
+
+ resizeThread = window.setTimeout(mxUtils.bind(this, function () {
+ resizeThread = null;
+ this.windowResized();
+ }), 100);
+ });
+
+ mxEvent.addListener(window, 'resize', this.resizeHandler);
+
+ this.orientationChangeHandler = mxUtils.bind(this, function () {
+ this.refresh();
+ });
+
+ mxEvent.addListener(window, 'orientationchange', this.orientationChangeHandler);
+
+ // Workaround for bug on iOS see
+ // http://stackoverflow.com/questions/19012135/ios-7-ipad-safari-landscape-innerheight-outerheight-layout-issue
+ if (mxClient.IS_IOS && !window.navigator.standalone && typeof Menus !== 'undefined') {
+ this.scrollHandler = mxUtils.bind(this, function () {
+ window.scrollTo(0, 0);
+ });
+
+ mxEvent.addListener(window, 'scroll', this.scrollHandler);
+ }
+
+ /**
+ * Sets the initial scrollbar locations after a file was loaded.
+ */
+ this.editor.addListener('resetGraphView', mxUtils.bind(this, function () {
+ this.resetScrollbars();
+ }));
+
+ /**
+ * Repaints the grid.
+ */
+ this.addListener('gridEnabledChanged', mxUtils.bind(this, function () {
+ graph.view.validateBackground();
+ }));
+
+ this.addListener('backgroundColorChanged', mxUtils.bind(this, function () {
+ graph.view.validateBackground();
+ }));
+
+ /**
+ * Repaints the grid.
+ */
+ graph.addListener('gridSizeChanged', mxUtils.bind(this, function () {
+ if (graph.isGridEnabled()) {
+ graph.view.validateBackground();
+ }
+ }));
+
+ // Resets UI, updates action and menu states
+ this.editor.resetGraph();
+ }
+
+ this.init();
+
+ if (!graph.standalone) {
+ this.open();
+ }
+};
+
+/**
+ * Global config that specifies if the compact UI elements should be used.
+ */
+EditorUi.compactUi = true;
+
+/**
+ * Static method for pasing PNG files.
+ */
+EditorUi.parsePng = function (f, fn, error) {
+ var pos = 0;
+
+ function fread(d, count) {
+ var start = pos;
+ pos += count;
+
+ return d.substring(start, pos);
+ };
+
+ // Reads unsigned long 32 bit big endian
+ function _freadint(d) {
+ var bytes = fread(d, 4);
+
+ return bytes.charCodeAt(3) + (bytes.charCodeAt(2) << 8) +
+ (bytes.charCodeAt(1) << 16) + (bytes.charCodeAt(0) << 24);
+ };
+
+ // Checks signature
+ if (fread(f, 8) != String.fromCharCode(137) + 'PNG' + String.fromCharCode(13, 10, 26, 10)) {
+ if (error != null) {
+ error();
+ }
+
+ return;
+ }
+
+ // Reads header chunk
+ fread(f, 4);
+
+ if (fread(f, 4) != 'IHDR') {
+ if (error != null) {
+ error();
+ }
+
+ return;
+ }
+
+ fread(f, 17);
+
+ do {
+ var n = _freadint(f);
+ var type = fread(f, 4);
+
+ if (fn != null) {
+ if (fn(pos - 8, type, n)) {
+ break;
+ }
+ }
+
+ value = fread(f, n);
+ fread(f, 4);
+
+ if (type == 'IEND') {
+ break;
+ }
+ }
+ while (n);
+};
+
+// Extends mxEventSource
+mxUtils.extend(EditorUi, mxEventSource);
+
+/**
+ * Specifies the size of the split bar.
+ */
+EditorUi.prototype.splitSize = (mxClient.IS_TOUCH || mxClient.IS_POINTER) ? 12 : 8;
+
+/**
+ * Specifies the height of the menubar. Default is 30.
+ */
+EditorUi.prototype.menubarHeight = 30;
+
+/**
+ * Specifies the width of the format panel should be enabled. Default is true.
+ */
+EditorUi.prototype.formatEnabled = true;
+
+/**
+ * Specifies the width of the format panel. Default is 240.
+ */
+EditorUi.prototype.formatWidth = 240;
+
+/**
+ * Specifies the height of the toolbar. Default is 38.
+ */
+EditorUi.prototype.toolbarHeight = 38;
+
+/**
+ * Specifies the height of the footer. Default is 28.
+ */
+EditorUi.prototype.footerHeight = 28;
+
+/**
+ * Specifies the position of the horizontal split bar. Default is 240 or 118 for
+ * screen widths <= 640px.
+ */
+EditorUi.prototype.hsplitPosition = (screen.width <= Editor.smallScreenWidth) ? 0 :
+ ((urlParams['sidebar-entries'] != 'large') ? 212 : 240);
+
+/**
+ * Specifies if animations are allowed in . Default is true.
+ */
+EditorUi.prototype.allowAnimation = true;
+
+/**
+ * Default is 2.
+ */
+EditorUi.prototype.lightboxMaxFitScale = 2;
+
+/**
+ * Default is 4.
+ */
+EditorUi.prototype.lightboxVerticalDivider = 4;
+
+/**
+ * Specifies if single click on horizontal split should collapse sidebar. Default is false.
+ */
+EditorUi.prototype.hsplitClickEnabled = false;
+
+/**
+ * Whether the default styles should be updated when styles are changed. Default is true.
+ */
+EditorUi.prototype.updateDefaultStyle = false;
+
+/**
+ * Whether the default styles should be updated when styles are changed. Default is true.
+ */
+EditorUi.prototype.spaceDown = false;
+
+/**
+ * Whether the default styles should be updated when styles are changed. Default is true.
+ */
+EditorUi.prototype.shiftDown = false;
+
+/**
+ * Installs the listeners to update the action states.
+ */
+EditorUi.prototype.init = function () {
+ var graph = this.editor.graph;
+
+ if (!graph.standalone) {
+ if (urlParams['shape-picker'] != '0') {
+ this.installShapePicker();
+ }
+
+ // Hides tooltips and connection points when scrolling
+ mxEvent.addListener(graph.container, 'scroll', mxUtils.bind(this, function () {
+ graph.tooltipHandler.hide();
+
+ if (graph.connectionHandler != null && graph.connectionHandler.constraintHandler != null) {
+ graph.connectionHandler.constraintHandler.reset();
+ }
+ }));
+
+ // Hides tooltip on escape
+ graph.addListener(mxEvent.ESCAPE, mxUtils.bind(this, function () {
+ graph.tooltipHandler.hide();
+ var rb = graph.getRubberband();
+
+ if (rb != null) {
+ rb.cancel();
+ }
+ }));
+
+ mxEvent.addListener(graph.container, 'keydown', mxUtils.bind(this, function (evt) {
+ this.onKeyDown(evt);
+ }));
+
+ mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, function (evt) {
+ this.onKeyPress(evt);
+ }));
+
+ // Updates action states
+ this.addUndoListener();
+ this.addBeforeUnloadListener();
+
+ graph.getSelectionModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function () {
+ this.updateActionStates();
+ }));
+
+ graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function () {
+ this.updateActionStates();
+ }));
+
+ // Changes action states after change of default parent
+ var graphSetDefaultParent = graph.setDefaultParent;
+ var ui = this;
+
+ this.editor.graph.setDefaultParent = function () {
+ graphSetDefaultParent.apply(this, arguments);
+ ui.updateActionStates();
+ };
+
+ // Hack to make showLinkDialog and editLink available in vertex handler
+ graph.showLinkDialog = mxUtils.bind(ui, ui.showLinkDialog);
+ graph.editLink = ui.actions.get('editLink').funct;
+
+ this.updateActionStates();
+ this.initClipboard();
+ this.initCanvas();
+
+ if (this.format != null) {
+ this.format.init();
+ }
+ }
+};
+
+/**
+ * Returns information about the current selection.
+ */
+EditorUi.prototype.clearSelectionState = function () {
+ this.selectionState = null;
+};
+
+/**
+ * Returns information about the current selection.
+ */
+EditorUi.prototype.getSelectionState = function () {
+ if (this.selectionState == null) {
+ this.selectionState = this.createSelectionState();
+ }
+
+ return this.selectionState;
+};
+
+/**
+ * Returns information about the current selection.
+ */
+EditorUi.prototype.createSelectionState = function () {
+ var graph = this.editor.graph;
+ var cells = graph.getSelectionCells();
+ var result = this.initSelectionState();
+ var initial = true;
+
+ for (var i = 0; i < cells.length; i++) {
+ var style = graph.getCurrentCellStyle(cells[i]);
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_EDITABLE, '1') != '0') {
+ this.updateSelectionStateForCell(result, cells[i], cells, initial);
+ initial = false;
+ }
+ }
+
+ this.updateSelectionStateForTableCells(result);
+
+ return result;
+};
+
+/**
+ * Returns information about the current selection.
+ */
+EditorUi.prototype.initSelectionState = function () {
+ return {
+ vertices: [], edges: [], cells: [], x: null, y: null, width: null, height: null,
+ style: {}, containsImage: false, containsLabel: false, fill: true, glass: true,
+ rounded: true, autoSize: false, image: false, shadow: true, lineJumps: true, resizable: true,
+ table: false, cell: false, row: false, movable: true, rotatable: true, stroke: true,
+ swimlane: false, unlocked: this.editor.graph.isEnabled(), connections: false
+ };
+};
+
+/**
+ * Adds information about current selected table cells range.
+ */
+EditorUi.prototype.updateSelectionStateForTableCells = function (result) {
+ if (result.cells.length > 1 && result.cell) {
+ var cells = mxUtils.sortCells(result.cells);
+ var model = this.editor.graph.model;
+ var parent = model.getParent(cells[0]);
+ var table = model.getParent(parent);
+
+ if (parent != null && table != null) {
+ var col = parent.getIndex(cells[0]);
+ var row = table.getIndex(parent);
+ var lastspan = null;
+ var colspan = 1;
+ var rowspan = 1;
+ var index = 0;
+
+ var nextRowCell = (row < table.getChildCount() - 1) ?
+ model.getChildAt(model.getChildAt(
+ table, row + 1), col) : null;
+
+ while (index < cells.length - 1) {
+ var next = cells[++index];
+
+ if (nextRowCell != null && nextRowCell == next &&
+ (lastspan == null || colspan == lastspan)) {
+ lastspan = colspan;
+ colspan = 0;
+ rowspan++;
+ parent = model.getParent(nextRowCell);
+ nextRowCell = (row + rowspan < table.getChildCount()) ?
+ model.getChildAt(model.getChildAt(
+ table, row + rowspan), col) : null;
+ }
+
+ var state = this.editor.graph.view.getState(next);
+
+ if (next == model.getChildAt(parent, col + colspan) && state != null &&
+ mxUtils.getValue(state.style, 'colspan', 1) == 1 &&
+ mxUtils.getValue(state.style, 'rowspan', 1) == 1) {
+ colspan++;
+ }
+ else {
+ break;
+ }
+ }
+
+ if (index == rowspan * colspan - 1) {
+ result.mergeCell = cells[0];
+ result.colspan = colspan;
+ result.rowspan = rowspan;
+ }
+ }
+ }
+};
+
+/**
+ * Returns information about the current selection.
+ */
+EditorUi.prototype.windowResized = function () {
+ window.setTimeout(mxUtils.bind(this, function () {
+ if (this.editor.graph != null) {
+ this.refresh();
+ }
+ }), 0);
+};
+
+/**
+ * Returns information about the current selection.
+ */
+EditorUi.prototype.createTimeout = function (timeout, fn, error) {
+ var acceptResponse = true;
+ var result = null;
+
+ var handleError = mxUtils.bind(this, function (e) {
+ if (result.clear()) {
+ acceptResponse = false;
+ e = (e != null) ? e : {
+ code: App.ERROR_TIMEOUT,
+ message: mxResources.get('timeout'),
+ retry: mxUtils.bind(this, function () {
+ this.createTimeout(timeout, fn, error);
+ })
+ };
+
+ if (error != null) {
+ error(e);
+ }
+ else {
+ this.handleError(e);
+ }
+ }
+ });
+
+ var timeoutThread = window.setTimeout(handleError,
+ (timeout != null) ? timeout : this.timeout);
+
+ var result = {
+ clear: function () {
+ window.clearTimeout(timeoutThread);
+
+ return acceptResponse;
+ },
+ isAlive: function () {
+ return acceptResponse;
+ }
+ };
+
+ if (fn != null) {
+ this.tryAndHandle(mxUtils.bind(this, function () {
+ fn(result);
+ }), handleError);
+ }
+
+ return result;
+};
+
+/**
+ * Returns information about the current selection.
+ */
+EditorUi.prototype.tryAndHandle = function (fn, error) {
+ try {
+ fn();
+ }
+ catch (e) {
+ if (error != null) {
+ error(e);
+ }
+ else {
+ this.handleError(e);
+ }
+ }
+};
+
+/**
+ * Returns information about the current selection.
+ */
+EditorUi.prototype.updateSelectionStateForCell = function (result, cell, cells, initial) {
+ var graph = this.editor.graph;
+ result.cells.push(cell);
+
+ if (graph.getModel().isVertex(cell)) {
+ result.connections = graph.model.getEdgeCount(cell) > 0;
+ result.unlocked = result.unlocked && !graph.isCellLocked(cell);
+ result.resizable = result.resizable && graph.isCellResizable(cell);
+ result.rotatable = result.rotatable && graph.isCellRotatable(cell);
+ result.movable = result.movable && graph.isCellMovable(cell) &&
+ !graph.isTableRow(cell) && !graph.isTableCell(cell);
+ result.swimlane = result.swimlane || graph.isSwimlane(cell);
+ result.table = result.table || graph.isTable(cell);
+ result.cell = result.cell || graph.isTableCell(cell);
+ result.row = result.row || graph.isTableRow(cell);
+ result.vertices.push(cell);
+ var geo = graph.getCellGeometry(cell);
+
+ if (geo != null) {
+ if (geo.width > 0) {
+ if (result.width == null) {
+ result.width = geo.width;
+ }
+ else if (result.width != geo.width) {
+ result.width = '';
+ }
+ }
+ else {
+ result.containsLabel = true;
+ }
+
+ if (geo.height > 0) {
+ if (result.height == null) {
+ result.height = geo.height;
+ }
+ else if (result.height != geo.height) {
+ result.height = '';
+ }
+ }
+ else {
+ result.containsLabel = true;
+ }
+
+ if (!geo.relative || geo.offset != null) {
+ var x = (geo.relative) ? geo.offset.x : geo.x;
+ var y = (geo.relative) ? geo.offset.y : geo.y;
+
+ if (result.x == null) {
+ result.x = x;
+ }
+ else if (result.x != x) {
+ result.x = '';
+ }
+
+ if (result.y == null) {
+ result.y = y;
+ }
+ else if (result.y != y) {
+ result.y = '';
+ }
+ }
+ }
+ }
+ else if (graph.getModel().isEdge(cell)) {
+ result.edges.push(cell);
+ result.connections = true;
+ result.resizable = false;
+ result.rotatable = false;
+ result.movable = false;
+ }
+
+ var state = graph.view.getState(cell);
+
+ if (state != null) {
+ result.autoSize = result.autoSize || graph.isAutoSizeState(state);
+ result.glass = result.glass && graph.isGlassState(state);
+ result.rounded = result.rounded && graph.isRoundedState(state);
+ result.lineJumps = result.lineJumps && graph.isLineJumpState(state);
+ result.image = result.image || graph.isImageState(state);
+ result.shadow = result.shadow && graph.isShadowState(state);
+ result.fill = result.fill && graph.isFillState(state);
+ result.gradient = result.fill && graph.isGradientState(state);
+ result.stroke = result.stroke && graph.isStrokeState(state);
+
+ var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);
+ result.containsImage = result.containsImage || shape == 'image';
+ graph.mergeStyle(state.style, result.style, initial);
+ }
+};
+
+/**
+ * Returns true if the given event should start editing. This implementation returns true.
+ */
+EditorUi.prototype.installShapePicker = function () {
+ var graph = this.editor.graph;
+ var ui = this;
+
+ // Uses this event to process mouseDown to check the selection state before it is changed
+ graph.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function (sender, evt) {
+ if (evt.getProperty('eventName') == 'mouseDown') {
+ ui.hideShapePicker();
+ }
+ }));
+
+ var hidePicker = mxUtils.bind(this, function () {
+ ui.hideShapePicker(true);
+ });
+
+ graph.addListener('wheel', hidePicker);
+ graph.addListener(mxEvent.ESCAPE, hidePicker);
+ graph.view.addListener(mxEvent.SCALE, hidePicker);
+ graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, hidePicker);
+ graph.getSelectionModel().addListener(mxEvent.CHANGE, hidePicker);
+
+ // Counts as popup menu
+ var popupMenuHandlerIsMenuShowing = graph.popupMenuHandler.isMenuShowing;
+
+ graph.popupMenuHandler.isMenuShowing = function () {
+ return popupMenuHandlerIsMenuShowing.apply(this, arguments) ||
+ ui.shapePicker != null || ui.currentMenu != null;
+ };
+
+ // Adds dbl click dialog for inserting shapes
+ var graphDblClick = graph.dblClick;
+
+ graph.dblClick = function (evt, cell) {
+ if (this.isEnabled()) {
+ if (cell == null && ui.sidebar != null && !mxEvent.isShiftDown(evt) &&
+ !graph.isCellLocked(graph.getDefaultParent())) {
+ var pt = mxUtils.convertPoint(this.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ mxEvent.consume(evt);
+
+ // Asynchronous to avoid direct insert after double tap
+ window.setTimeout(mxUtils.bind(this, function () {
+ ui.showShapePicker(pt.x, pt.y);
+ }), 30);
+ }
+ else {
+ graphDblClick.apply(this, arguments);
+ }
+ }
+ };
+
+ if (this.hoverIcons != null) {
+ this.hoverIcons.addListener('reset', hidePicker);
+ var hoverIconsDrag = this.hoverIcons.drag;
+
+ this.hoverIcons.drag = function () {
+ ui.hideShapePicker();
+ hoverIconsDrag.apply(this, arguments);
+ };
+
+ var hoverIconsExecute = this.hoverIcons.execute;
+
+ this.hoverIcons.execute = function (state, dir, me) {
+ var evt = me.getEvent();
+
+ if (!this.graph.isCloneEvent(evt) && !mxEvent.isShiftDown(evt)) {
+ this.graph.connectVertex(state.cell, dir, this.graph.defaultEdgeLength, evt, null, null, mxUtils.bind(this, function (x, y, execute) {
+ var temp = graph.getCompositeParent(state.cell);
+ var geo = graph.getCellGeometry(temp);
+ me.consume();
+
+ while (temp != null && graph.model.isVertex(temp) && geo != null && geo.relative) {
+ cell = temp;
+ temp = graph.model.getParent(cell)
+ geo = graph.getCellGeometry(temp);
+ }
+
+ // Asynchronous to avoid direct insert after double tap
+ window.setTimeout(mxUtils.bind(this, function () {
+ ui.showShapePicker(me.getGraphX(), me.getGraphY(), temp, mxUtils.bind(this, function (cell) {
+ execute(cell);
+
+ if (ui.hoverIcons != null) {
+ ui.hoverIcons.update(graph.view.getState(cell));
+ }
+ }), dir);
+ }), 30);
+ }), mxUtils.bind(this, function (result) {
+ this.graph.selectCellsForConnectVertex(result, evt, this);
+ }));
+ }
+ else {
+ hoverIconsExecute.apply(this, arguments);
+ }
+ };
+
+ var thread = null;
+
+ this.hoverIcons.addListener('focus', mxUtils.bind(this, function (sender, evt) {
+ if (thread != null) {
+ window.clearTimeout(thread);
+ }
+
+ thread = window.setTimeout(mxUtils.bind(this, function () {
+ var arrow = evt.getProperty('arrow');
+ var dir = evt.getProperty('direction');
+ var mouseEvent = evt.getProperty('event');
+
+ var rect = arrow.getBoundingClientRect();
+ var offset = mxUtils.getOffset(graph.container);
+ var x = graph.container.scrollLeft + rect.x - offset.x;
+ var y = graph.container.scrollTop + rect.y - offset.y;
+
+ var temp = graph.getCompositeParent((this.hoverIcons.currentState != null) ?
+ this.hoverIcons.currentState.cell : null);
+ var div = ui.showShapePicker(x, y, temp, mxUtils.bind(this, function (cell) {
+ if (cell != null) {
+ graph.connectVertex(temp, dir, graph.defaultEdgeLength, mouseEvent, true, false, function (x, y, execute) {
+ execute(cell);
+
+ if (ui.hoverIcons != null) {
+ ui.hoverIcons.update(graph.view.getState(cell));
+ }
+ }, function (cells) {
+ graph.selectCellsForConnectVertex(cells);
+ }, mouseEvent, this.hoverIcons);
+ }
+ }), dir, true);
+
+ this.centerShapePicker(div, rect, x, y, dir);
+ mxUtils.setOpacity(div, 30);
+
+ mxEvent.addListener(div, 'mouseenter', function () {
+ mxUtils.setOpacity(div, 100);
+ });
+
+ mxEvent.addListener(div, 'mouseleave', function () {
+ ui.hideShapePicker();
+ });
+ }), Editor.shapePickerHoverDelay);
+ }));
+
+ this.hoverIcons.addListener('blur', mxUtils.bind(this, function (sender, evt) {
+ if (thread != null) {
+ window.clearTimeout(thread);
+ }
+ }));
+ }
+};
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+EditorUi.prototype.centerShapePicker = function (div, rect, x, y, dir) {
+ if (dir == mxConstants.DIRECTION_EAST || dir == mxConstants.DIRECTION_WEST) {
+ div.style.width = '40px';
+ }
+
+ var r2 = div.getBoundingClientRect();
+
+ if (dir == mxConstants.DIRECTION_NORTH) {
+ x -= r2.width / 2 - 10;
+ y -= r2.height + 6;
+ }
+ else if (dir == mxConstants.DIRECTION_SOUTH) {
+ x -= r2.width / 2 - 10;
+ y += rect.height + 6;
+ }
+ else if (dir == mxConstants.DIRECTION_WEST) {
+ x -= r2.width + 6;
+ y -= r2.height / 2 - 10;
+ }
+ else if (dir == mxConstants.DIRECTION_EAST) {
+ x += rect.width + 6;
+ y -= r2.height / 2 - 10;
+ }
+
+ div.style.left = x + 'px';
+ div.style.top = y + 'px';
+};
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+EditorUi.prototype.showShapePicker = function (x, y, source, callback, direction, hovering,
+ getInsertLocationFn, showEdges, startEditing) {
+ showEdges = showEdges || source == null;
+
+ var div = this.createShapePicker(x, y, source, callback, direction, mxUtils.bind(this, function () {
+ this.hideShapePicker();
+ }), this.getCellsForShapePicker(source, hovering, showEdges), hovering,
+ getInsertLocationFn, showEdges, startEditing);
+
+ if (div != null) {
+ if (this.hoverIcons != null && !hovering) {
+ this.hoverIcons.reset();
+ }
+
+ var graph = this.editor.graph;
+ graph.popupMenuHandler.hideMenu();
+ graph.tooltipHandler.hideTooltip();
+ this.hideCurrentMenu();
+ this.hideShapePicker();
+
+ this.shapePickerCallback = callback;
+ this.shapePicker = div;
+ }
+
+ return div;
+};
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+EditorUi.prototype.createShapePicker = function (x, y, source, callback, direction,
+ afterClick, cells, hovering, getInsertLocationFn, showEdges, startEditing) {
+ startEditing = (startEditing != null) ? startEditing : true;
+ var graph = this.editor.graph;
+ var div = null;
+
+ getInsertLocationFn = (getInsertLocationFn != null) ? getInsertLocationFn : function (cells) {
+ var cell = cells[0];
+ var w = 0;
+ var h = 0;
+ var geo = cell.geometry;
+
+ if (geo != null) {
+ if (graph.model.isEdge(cell)) {
+ var pt = geo.getTerminalPoint(false);
+ geo = new mxRectangle(0, 0, pt.x, pt.y);
+ }
+
+ w = geo.width / 2;
+ h = geo.height / 2;
+ }
+
+ return new mxPoint(graph.snap(Math.round(x / graph.view.scale) - graph.view.translate.x - w),
+ graph.snap(Math.round(y / graph.view.scale) - graph.view.translate.y - h));
+ };
+
+ if (cells != null && cells.length > 0) {
+ var ui = this;
+ var graph = this.editor.graph;
+ div = document.createElement('div');
+ var sourceState = graph.view.getState(source);
+ var style = (source != null && (sourceState == null ||
+ !graph.isTransparentState(sourceState))) ?
+ graph.copyStyle(source) : null;
+
+ // Do not place entry under pointer for touch devices
+ var w = (cells.length < 6) ? cells.length * 35 : 140;
+ div.className = 'geToolbarContainer geSidebarContainer geShapePicker';
+ div.setAttribute('title', mxResources.get('sidebarTooltip'));
+ div.style.left = x + 'px';
+ div.style.top = y + 'px';
+ div.style.width = w + 'px';
+
+ // Disables built-in pan and zoom on touch devices
+ if (mxClient.IS_POINTER) {
+ div.style.touchAction = 'none';
+ }
+
+ if (!hovering) {
+ mxUtils.setPrefixedStyle(div.style, 'transform', 'translate(-22px,-22px)');
+ }
+
+ if (graph.background != null && graph.background != mxConstants.NONE) {
+ div.style.backgroundColor = graph.background;
+ }
+
+ graph.container.appendChild(div);
+
+ var addCell = mxUtils.bind(this, function (cell) {
+ // Wrapper needed to catch events
+ var node = document.createElement('a');
+ node.className = 'geItem';
+ node.style.cssText = 'position:relative;display:inline-block;position:relative;' +
+ 'width:30px;height:30px;cursor:pointer;overflow:hidden;padding:1px';
+ div.appendChild(node);
+
+ if (style != null && urlParams['sketch'] != '1') {
+ this.sidebar.graph.pasteStyle(style, [cell]);
+ }
+ else {
+ ui.insertHandler([cell], cell.value != '' && urlParams['sketch'] != '1', this.sidebar.graph.model);
+ }
+
+ var geo = cell.geometry;
+
+ if (graph.model.isEdge(cell)) {
+ var pt = geo.getTerminalPoint(false);
+ geo = new mxRectangle(0, 0, pt.x, pt.y);
+ }
+
+ if (geo != null) {
+ node.appendChild(this.sidebar.createVertexTemplateFromCells([cell],
+ geo.width, geo.height, '', true, false, null, false,
+ mxUtils.bind(this, function (evt) {
+ if (mxEvent.isShiftDown(evt) && (source != null ||
+ !graph.isSelectionEmpty())) {
+ var temp = graph.getEditableCells((source != null) ?
+ [source] : graph.getSelectionCells());
+ graph.updateShapes(cell, temp);
+ }
+ else {
+ var clone = graph.cloneCell(cell);
+
+ if (callback != null) {
+ callback(clone);
+ }
+ else {
+ var pt = getInsertLocationFn([clone]);
+
+ if (graph.model.isEdge(clone)) {
+ clone.geometry.translate(pt.x, pt.y);
+ }
+ else {
+ clone.geometry.x = pt.x;
+ clone.geometry.y = pt.y;
+ }
+
+ graph.model.beginUpdate();
+ try {
+ graph.addCell(clone);
+
+ if (graph.model.isVertex(clone) &&
+ graph.isAutoSizeCell(clone)) {
+ graph.updateCellSize(clone);
+ }
+ }
+ finally {
+ graph.model.endUpdate();
+ }
+
+ graph.setSelectionCell(clone);
+ graph.scrollCellToVisible(clone);
+
+ if (startEditing) {
+ graph.startEditing(clone);
+ }
+
+ if (ui.hoverIcons != null) {
+ ui.hoverIcons.update(graph.view.getState(clone));
+ }
+ }
+ }
+
+ if (afterClick != null) {
+ afterClick(evt);
+ }
+
+ mxEvent.consume(evt);
+ }), 25, 25, null, null, source));
+ }
+ });
+
+ for (var i = 0; i < (hovering ? Math.min(cells.length, 4) : cells.length); i++) {
+ addCell(cells[i]);
+ }
+
+ var b = graph.container.scrollTop + graph.container.offsetHeight;
+ var dy = div.offsetTop + div.clientHeight - b;
+
+ if (dy > 0) {
+ div.style.top = Math.max(graph.container.scrollTop + 22, y - dy) + 'px';
+ }
+
+ var r = graph.container.scrollLeft + graph.container.offsetWidth;
+ var dx = div.offsetLeft + div.clientWidth - r;
+
+ if (dx > 0) {
+ div.style.left = Math.max(graph.container.scrollLeft + 22, x - dx) + 'px';
+ }
+ }
+
+ return div;
+};
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+EditorUi.prototype.getCellsForShapePicker = function (cell, hovering, showEdges) {
+ var graph = this.editor.graph;
+
+ var createVertex = mxUtils.bind(this, function (style, w, h, value) {
+ return graph.createVertex(null, null, value || '', 0, 0, w || 120, h || 60, style, false);
+ });
+
+ var createEdge = mxUtils.bind(this, function (style, y, value) {
+ var cell = new mxCell(value || '', new mxGeometry(0, 0, graph.defaultEdgeLength + 20, 0), style);
+ cell.geometry.setTerminalPoint(new mxPoint(0, 0), true);
+ cell.geometry.setTerminalPoint(new mxPoint(cell.geometry.width, (y != null) ? y : 0), false);
+ cell.geometry.points = (y != null) ? [new mxPoint(cell.geometry.width / 2, y)] : [];
+ cell.geometry.relative = true;
+ cell.edge = true;
+
+ return cell;
+ });
+
+ // Creates a clone of the source cell and moves it to the origin
+ if (cell != null) {
+ try {
+ cell = graph.cloneCell(cell);
+
+ if (graph.model.isVertex(cell) && cell.geometry != null) {
+ cell.geometry.x = 0;
+ cell.geometry.y = 0;
+ }
+ }
+ catch (e) {
+ cell = null;
+ }
+ }
+
+ if (cell == null) {
+ cell = createVertex('text;html=1;align=center;verticalAlign=middle;resizable=0;' +
+ 'points=[];autosize=1;strokeColor=none;fillColor=none;', 40, 20, 'Text');
+
+ if (graph.model.isVertex(cell) && graph.isAutoSizeCell(cell)) {
+ // Uses offscreen graph to bypass undo history
+ var tempGraph = Graph.createOffscreenGraph(graph.getStylesheet());
+ tempGraph.updateCellSize(cell);
+ }
+ }
+
+ var cells = [cell, createVertex('whiteSpace=wrap;html=1;'),
+ createVertex('ellipse;whiteSpace=wrap;html=1;', 80, 80),
+ createVertex('rhombus;whiteSpace=wrap;html=1;', 80, 80),
+ createVertex('rounded=1;whiteSpace=wrap;html=1;'),
+ createVertex('shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;'),
+ createVertex('shape=trapezoid;perimeter=trapezoidPerimeter;whiteSpace=wrap;html=1;fixedSize=1;', 120, 60),
+ createVertex('shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;', 120, 80),
+ createVertex('shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;', 120, 80),
+ createVertex('shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;'),
+ createVertex('triangle;whiteSpace=wrap;html=1;', 60, 80),
+ createVertex('shape=document;whiteSpace=wrap;html=1;boundedLbl=1;', 120, 80),
+ createVertex('shape=tape;whiteSpace=wrap;html=1;', 120, 100),
+ createVertex('ellipse;shape=cloud;whiteSpace=wrap;html=1;', 120, 80),
+ createVertex('shape=singleArrow;whiteSpace=wrap;html=1;arrowWidth=0.4;arrowSize=0.4;', 80, 60),
+ createVertex('shape=waypoint;sketch=0;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;' +
+ 'rotatable=0;perimeter=centerPerimeter;snapToPoint=1;', 20, 20)];
+
+ if (showEdges) {
+ cells = cells.concat([
+ createEdge('edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;'),
+ createEdge('edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;startArrow=classic;endSize=8;startSize=8;'),
+ createEdge('edgeStyle=none;orthogonalLoop=1;jettySize=auto;html=1;shape=flexArrow;rounded=1;startSize=8;endSize=8;'),
+ createEdge('edgeStyle=segmentEdgeStyle;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;sourcePerimeterSpacing=0;targetPerimeterSpacing=0;',
+ this.editor.graph.defaultEdgeLength / 2)
+ ]);
+ }
+
+ return cells;
+};
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+EditorUi.prototype.isShapePickerVisible = function (cancel) {
+ return this.shapePicker != null;
+};
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+EditorUi.prototype.hideShapePicker = function (cancel) {
+ if (this.shapePicker != null) {
+ this.shapePicker.parentNode.removeChild(this.shapePicker);
+ this.shapePicker = null;
+
+ if (!cancel && this.shapePickerCallback != null) {
+ this.shapePickerCallback();
+ }
+
+ this.shapePickerCallback = null;
+ }
+};
+
+/**
+ * Whether the default styles should be updated when styles are changed. Default is true.
+ */
+EditorUi.prototype.isSpaceDown = function () {
+ return this.spaceDown;
+};
+
+/**
+ * Whether the default styles should be updated when styles are changed. Default is true.
+ */
+EditorUi.prototype.isShiftDown = function () {
+ return this.shiftDown;
+};
+
+/**
+ * Returns true if the given event should start editing. This implementation returns true.
+ */
+EditorUi.prototype.onKeyDown = function (evt) {
+ var graph = this.editor.graph;
+
+ // Alt+tab for task switcher in Windows, ctrl+tab for tab control in Chrome
+ if (evt.which == 9 && graph.isEnabled() && !mxEvent.isControlDown(evt)) {
+ if (graph.isEditing()) {
+ if (mxEvent.isAltDown(evt)) {
+ graph.stopEditing(false);
+ }
+ else {
+ try {
+ var nesting = graph.cellEditor.isContentEditing() && graph.cellEditor.isTextSelected();
+
+ if (window.getSelection && graph.cellEditor.isContentEditing() &&
+ !nesting && !mxClient.IS_IE && !mxClient.IS_IE11) {
+ var selection = window.getSelection();
+ var container = (selection.rangeCount > 0) ? selection.getRangeAt(0).commonAncestorContainer : null;
+ nesting = container != null && (container.nodeName == 'LI' || (container.parentNode != null &&
+ container.parentNode.nodeName == 'LI'));
+ }
+
+ if (nesting) {
+ // (Shift+)tab indents/outdents with text selection or inside list elements
+ document.execCommand(mxEvent.isShiftDown(evt) ? 'outdent' : 'indent', false, null);
+ }
+ // Shift+tab applies value with cursor
+ else if (mxEvent.isShiftDown(evt)) {
+ graph.stopEditing(false);
+ }
+ else {
+ // Inserts tab character
+ graph.cellEditor.insertTab(!graph.cellEditor.isContentEditing() ? 4 : null);
+ }
+ }
+ catch (e) {
+ // ignore
+ }
+ }
+ }
+ else if (mxEvent.isAltDown(evt)) {
+ graph.selectParentCell();
+ }
+ else {
+ graph.selectCell(!mxEvent.isShiftDown(evt));
+ }
+
+ mxEvent.consume(evt);
+ }
+};
+
+/**
+ * Returns true if the given event should start editing. This implementation returns true.
+ */
+EditorUi.prototype.onKeyPress = function (evt) {
+ var graph = this.editor.graph;
+
+ // KNOWN: Focus does not work if label is empty in quirks mode
+ if (this.isImmediateEditingEvent(evt) && !graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 &&
+ evt.which !== 27 && !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt)) {
+ graph.escape();
+ graph.startEditing();
+
+ // Workaround for FF where char is lost if cursor is placed before char
+ if (mxClient.IS_FF) {
+ var ce = graph.cellEditor;
+
+ if (ce.textarea != null) {
+ ce.textarea.innerHTML = String.fromCharCode(evt.which);
+
+ // Moves cursor to end of textarea
+ var range = document.createRange();
+ range.selectNodeContents(ce.textarea);
+ range.collapse(false);
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }
+ }
+ }
+};
+
+/**
+ * Returns true if the given event should start editing. This implementation returns true.
+ */
+EditorUi.prototype.isImmediateEditingEvent = function (evt) {
+ return true;
+};
+
+/**
+ * Updates the CSS for the given element to match the selection.
+ */
+EditorUi.prototype.updateCssForMarker = function (markerDiv, prefix, shape, marker, fill) {
+ markerDiv.style.display = 'inline-flex';
+ markerDiv.style.alignItems = 'center';
+ markerDiv.style.justifyContent = 'center';
+ markerDiv.innerText = '';
+
+ if (shape == 'flexArrow') {
+ markerDiv.className = (marker != null && marker != mxConstants.NONE) ?
+ 'geSprite geSprite-' + prefix + 'blocktrans' : 'geSprite geSprite-noarrow';
+ }
+ else {
+ var src = this.getImageForMarker(marker, fill);
+
+ if (src != null) {
+ var img = document.createElement('img');
+ img.className = 'geAdaptiveAsset';
+ img.setAttribute('src', src);
+ markerDiv.className = '';
+
+ if (prefix == 'end') {
+ mxUtils.setPrefixedStyle(img.style, 'transform', 'scaleX(-1)');
+ }
+
+ markerDiv.appendChild(img);
+ }
+ else {
+ markerDiv.className = 'geSprite geSprite-noarrow';
+ markerDiv.innerHTML = mxUtils.htmlEntities(mxResources.get('none'));
+ markerDiv.style.backgroundImage = 'none';
+ markerDiv.style.fontSize = '11px';
+ markerDiv.style.filter = 'none';
+ }
+ }
+};
+
+/**
+ * Returns true if the given event should start editing. This implementation returns true.
+ */
+EditorUi.prototype.getImageForMarker = function (marker, fill) {
+ var result = null;
+
+ if (marker == mxConstants.ARROW_CLASSIC) {
+ result = (fill != '1') ? Format.classicMarkerImage.src :
+ Format.classicFilledMarkerImage.src
+ }
+ else if (marker == mxConstants.ARROW_CLASSIC_THIN) {
+ result = (fill != '1') ? Format.classicThinMarkerImage.src :
+ Format.openThinFilledMarkerImage.src;
+ }
+ else if (marker == mxConstants.ARROW_OPEN) {
+ result = Format.openFilledMarkerImage.src;
+ }
+ else if (marker == mxConstants.ARROW_OPEN_THIN) {
+ result = Format.openThinFilledMarkerImage.src;
+ }
+ else if (marker == mxConstants.ARROW_BLOCK) {
+ result = (fill != '1') ? Format.blockMarkerImage.src :
+ Format.blockFilledMarkerImage.src;
+ }
+ else if (marker == mxConstants.ARROW_BLOCK_THIN) {
+ result = (fill != '1') ? Format.blockThinMarkerImage.src :
+ Format.blockThinFilledMarkerImage.src;
+ }
+ else if (marker == mxConstants.ARROW_OVAL) {
+ result = (fill != '1') ? Format.ovalMarkerImage.src :
+ Format.ovalFilledMarkerImage.src;
+ }
+ else if (marker == mxConstants.ARROW_DIAMOND) {
+ result = (fill != '1') ? Format.diamondMarkerImage.src :
+ Format.diamondFilledMarkerImage.src;
+ }
+ else if (marker == mxConstants.ARROW_DIAMOND_THIN) {
+ result = (fill != '1') ? Format.diamondThinMarkerImage.src :
+ Format.diamondThinFilledMarkerImage.src;
+ }
+ else if (marker == 'doubleBlock') {
+ result = (fill != '1') ? Format.doubleBlockMarkerImage.src :
+ Format.doubleBlockFilledMarkerImage.src;
+ }
+ else if (marker == 'box') {
+ result = Format.boxMarkerImage.src;
+ }
+ else if (marker == 'halfCircle') {
+ result = Format.halfCircleMarkerImage.src;
+ }
+ else if (marker == 'openAsync') {
+ result = Format.openAsyncFilledMarkerImage.src;
+ }
+ else if (marker == 'async') {
+ result = (fill != '1') ? Format.asyncMarkerImage.src :
+ Format.asyncFilledMarkerImage.src;
+ }
+ else if (marker == 'dash') {
+ result = Format.dashMarkerImage.src;
+ }
+ else if (marker == 'baseDash') {
+ result = Format.baseDashMarkerImage.src;
+ }
+ else if (marker == 'cross') {
+ result = Format.crossMarkerImage.src;
+ }
+ else if (marker == 'circle') {
+ result = Format.circleMarkerImage.src;
+ }
+ else if (marker == 'circlePlus') {
+ result = Format.circlePlusMarkerImage.src;
+ }
+ else if (marker == 'ERone') {
+ result = Format.EROneMarkerImage.src;
+ }
+ else if (marker == 'ERmandOne') {
+ result = Format.ERmandOneMarkerImage.src;
+ }
+ else if (marker == 'ERmany') {
+ result = Format.ERmanyMarkerImage.src;
+ }
+ else if (marker == 'ERoneToMany') {
+ result = Format.ERoneToManyMarkerImage.src;
+ }
+ else if (marker == 'ERzeroToOne') {
+ result = Format.ERzeroToOneMarkerImage.src;
+ }
+ else if (marker == 'ERzeroToMany') {
+ result = Format.ERzeroToManyMarkerImage.src;
+ }
+
+ return result;
+};
+
+/**
+ * Overridden in Menus.js
+ */
+EditorUi.prototype.createMenus = function () {
+ return null;
+};
+
+/**
+ * Hook for allowing selection and context menu for certain events.
+ */
+EditorUi.prototype.updatePasteActionStates = function () {
+ var graph = this.editor.graph;
+ var paste = this.actions.get('paste');
+ var pasteHere = this.actions.get('pasteHere');
+
+ paste.setEnabled(this.editor.graph.cellEditor.isContentEditing() ||
+ (((!mxClient.IS_FF && navigator.clipboard != null) || !mxClipboard.isEmpty()) &&
+ graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())));
+ pasteHere.setEnabled(paste.isEnabled());
+};
+
+/**
+ * Hook for allowing selection and context menu for certain events.
+ */
+EditorUi.prototype.initClipboard = function () {
+ var ui = this;
+
+ var mxClipboardCut = mxClipboard.cut;
+ mxClipboard.cut = function (graph) {
+ if (graph.cellEditor.isContentEditing()) {
+ document.execCommand('cut', false, null);
+ }
+ else {
+ mxClipboardCut.apply(this, arguments);
+ }
+
+ ui.updatePasteActionStates();
+ };
+
+ var mxClipboardCopy = mxClipboard.copy;
+ mxClipboard.copy = function (graph) {
+ var result = null;
+
+ if (graph.cellEditor.isContentEditing()) {
+ document.execCommand('copy', false, null);
+ }
+ else {
+ result = result || graph.getSelectionCells();
+ result = graph.getExportableCells(graph.model.getTopmostCells(result));
+
+ var cloneMap = new Object();
+ var lookup = graph.createCellLookup(result);
+ var clones = graph.cloneCells(result, null, cloneMap);
+
+ // Uses temporary model to force new IDs to be assigned
+ // to avoid having to carry over the mapping from object
+ // ID to cell ID to the paste operation
+ var model = new mxGraphModel();
+ var parent = model.getChildAt(model.getRoot(), 0);
+
+ for (var i = 0; i < clones.length; i++) {
+ model.add(parent, clones[i]);
+
+ // Checks for orphaned relative children and makes absolute
+ var state = graph.view.getState(result[i]);
+
+ if (state != null) {
+ var geo = graph.getCellGeometry(clones[i]);
+
+ if (geo != null && geo.relative && !model.isEdge(result[i]) &&
+ lookup[mxObjectIdentity.get(model.getParent(result[i]))] == null) {
+ geo.offset = null;
+ geo.relative = false;
+ geo.x = state.x / state.view.scale - state.view.translate.x;
+ geo.y = state.y / state.view.scale - state.view.translate.y;
+ }
+ }
+ }
+
+ graph.updateCustomLinks(graph.createCellMapping(cloneMap, lookup), clones);
+
+ mxClipboard.insertCount = 1;
+ mxClipboard.setCells(clones);
+ }
+
+ ui.updatePasteActionStates();
+
+ return result;
+ };
+
+ var mxClipboardPaste = mxClipboard.paste;
+ mxClipboard.paste = function (graph) {
+ var result = null;
+
+ if (graph.cellEditor.isContentEditing()) {
+ document.execCommand('paste', false, null);
+ }
+ else {
+ result = mxClipboardPaste.apply(this, arguments);
+ }
+
+ ui.updatePasteActionStates();
+
+ return result;
+ };
+
+ // Overrides cell editor to update paste action state
+ var cellEditorStartEditing = this.editor.graph.cellEditor.startEditing;
+
+ this.editor.graph.cellEditor.startEditing = function () {
+ cellEditorStartEditing.apply(this, arguments);
+ ui.updatePasteActionStates();
+ };
+
+ var cellEditorStopEditing = this.editor.graph.cellEditor.stopEditing;
+
+ this.editor.graph.cellEditor.stopEditing = function (cell, trigger) {
+ cellEditorStopEditing.apply(this, arguments);
+ ui.updatePasteActionStates();
+ };
+
+ this.updatePasteActionStates();
+};
+
+/**
+ * Delay between zoom steps when not using preview.
+ */
+EditorUi.prototype.lazyZoomDelay = 20;
+
+/**
+ * Delay before update of DOM when using preview.
+ */
+EditorUi.prototype.wheelZoomDelay = 500;
+
+/**
+ * Delay before update of DOM when using preview.
+ */
+EditorUi.prototype.buttonZoomDelay = 600;
+
+/**
+ * Initializes the infinite canvas.
+ */
+EditorUi.prototype.initCanvas = function () {
+ // Initial page layout view, scrollBuffer and timer-based scrolling
+ var graph = this.editor.graph;
+ graph.timerAutoScroll = true;
+
+ /**
+ * Returns the padding for pages in page view with scrollbars.
+ */
+ graph.getPagePadding = function () {
+ return new mxPoint(Math.max(0, Math.round((graph.container.offsetWidth - 34) / graph.view.scale)),
+ Math.max(0, Math.round((graph.container.offsetHeight - 34) / graph.view.scale)));
+ };
+
+ // Fits the number of background pages to the graph
+ graph.view.getBackgroundPageBounds = function () {
+ var layout = this.graph.getPageLayout();
+ var page = this.graph.getPageSize();
+
+ return new mxRectangle(this.scale * (this.translate.x + layout.x * page.width),
+ this.scale * (this.translate.y + layout.y * page.height),
+ this.scale * layout.width * page.width,
+ this.scale * layout.height * page.height);
+ };
+
+ graph.getPreferredPageSize = function (bounds, width, height) {
+ var pages = this.getPageLayout();
+ var size = this.getPageSize();
+
+ return new mxRectangle(0, 0, pages.width * size.width, pages.height * size.height);
+ };
+
+ // Scales pages/graph to fit available size
+ var resize = null;
+ var ui = this;
+
+ if (this.editor.isChromelessView()) {
+ resize = mxUtils.bind(this, function (autoscale, maxScale, cx, cy) {
+ if (graph.container != null && !graph.isViewer()) {
+ cx = (cx != null) ? cx : 0;
+ cy = (cy != null) ? cy : 0;
+
+ var bds = (graph.pageVisible) ?
+ graph.view.getBackgroundPageBounds() :
+ graph.getGraphBounds();
+ var scroll = mxUtils.hasScrollbars(graph.container);
+ var tr = graph.view.translate;
+ var s = graph.view.scale;
+
+ // Normalizes the bounds
+ var b = mxRectangle.fromRectangle(bds);
+ b.x = b.x / s - tr.x;
+ b.y = b.y / s - tr.y;
+ b.width /= s;
+ b.height /= s;
+
+ var st = graph.container.scrollTop;
+ var sl = graph.container.scrollLeft;
+ var sb = (document.documentMode >= 8) ? 20 : 14;
+
+ if (document.documentMode == 8 || document.documentMode == 9) {
+ sb += 3;
+ }
+
+ var cw = graph.container.offsetWidth - sb;
+ var ch = graph.container.offsetHeight - sb;
+
+ var ns = (autoscale) ? Math.max(0.3, Math.min(maxScale || 1, cw / b.width)) : s;
+ var dx = ((cw - ns * b.width) / 2) / ns;
+ var dy = (this.lightboxVerticalDivider == 0) ? 0 : ((ch - ns * b.height) / this.lightboxVerticalDivider) / ns;
+
+ if (scroll) {
+ dx = Math.max(dx, 0);
+ dy = Math.max(dy, 0);
+ }
+
+ if (scroll || bds.width < cw || bds.height < ch) {
+ graph.view.scaleAndTranslate(ns, Math.floor(dx - b.x), Math.floor(dy - b.y));
+ graph.container.scrollTop = st * ns / s;
+ graph.container.scrollLeft = sl * ns / s;
+ }
+ else if (cx != 0 || cy != 0) {
+ var t = graph.view.translate;
+ graph.view.setTranslate(Math.floor(t.x + cx / s), Math.floor(t.y + cy / s));
+ }
+ }
+ });
+
+ // Hack to make function available to subclassers
+ this.chromelessResize = resize;
+
+ // Hook for subclassers for override
+ this.chromelessWindowResize = mxUtils.bind(this, function () {
+ this.chromelessResize(false);
+ });
+
+ // Removable resize listener
+ var autoscaleResize = mxUtils.bind(this, function () {
+ this.chromelessWindowResize(false);
+ });
+
+ mxEvent.addListener(window, 'resize', autoscaleResize);
+
+ this.destroyFunctions.push(function () {
+ mxEvent.removeListener(window, 'resize', autoscaleResize);
+ });
+
+ this.editor.addListener('resetGraphView', mxUtils.bind(this, function () {
+ this.chromelessResize(true);
+ }));
+
+ this.actions.get('zoomIn').funct = mxUtils.bind(this, function (evt) {
+ graph.zoomIn();
+ this.chromelessResize(false);
+ });
+ this.actions.get('zoomOut').funct = mxUtils.bind(this, function (evt) {
+ graph.zoomOut();
+ this.chromelessResize(false);
+ });
+
+ // Creates toolbar for viewer - do not use CSS here
+ // as this may be used in a viewer that has no CSS
+ if (urlParams['toolbar'] != '0') {
+ var toolbarConfig = JSON.parse(decodeURIComponent(urlParams['toolbar-config'] || '{}'));
+
+ this.chromelessToolbar = document.createElement('div');
+ this.chromelessToolbar.style.position = 'fixed';
+ this.chromelessToolbar.style.overflow = 'hidden';
+ this.chromelessToolbar.style.boxSizing = 'border-box';
+ this.chromelessToolbar.style.whiteSpace = 'nowrap';
+ this.chromelessToolbar.style.padding = '10px 10px 8px 10px';
+ this.chromelessToolbar.style.left = (graph.isViewer()) ? '0' : '50%';
+
+ if (!mxClient.IS_IE && !mxClient.IS_IE11) {
+ this.chromelessToolbar.style.backgroundColor = '#000000';
+ }
+ else {
+ this.chromelessToolbar.style.backgroundColor = '#ffffff';
+ this.chromelessToolbar.style.border = '3px solid black';
+ }
+
+ mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'borderRadius', '16px');
+ mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'transition', 'opacity 600ms ease-in-out');
+
+ var updateChromelessToolbarPosition = mxUtils.bind(this, function () {
+ var css = mxUtils.getCurrentStyle(graph.container);
+
+ if (graph.isViewer()) {
+ this.chromelessToolbar.style.top = '0';
+ }
+ else {
+ this.chromelessToolbar.style.bottom = ((css != null) ? parseInt(css['margin-bottom'] || 0) : 0) +
+ ((this.tabContainer != null) ? (20 + parseInt(this.tabContainer.style.height)) : 20) + 'px';
+ }
+ });
+
+ this.editor.addListener('resetGraphView', updateChromelessToolbarPosition);
+ updateChromelessToolbarPosition();
+
+ var btnCount = 0;
+
+ var addButton = mxUtils.bind(this, function (fn, imgSrc, tip) {
+ btnCount++;
+
+ var a = document.createElement('span');
+ a.style.paddingLeft = '8px';
+ a.style.paddingRight = '8px';
+ a.style.cursor = 'pointer';
+ mxEvent.addListener(a, 'click', fn);
+
+ if (tip != null) {
+ a.setAttribute('title', tip);
+ }
+
+ var img = document.createElement('img');
+ img.setAttribute('border', '0');
+ img.setAttribute('src', imgSrc);
+ img.style.width = '36px';
+ img.style.filter = 'invert(100%)';
+
+ a.appendChild(img);
+ this.chromelessToolbar.appendChild(a);
+
+ return a;
+ });
+
+ if (toolbarConfig.backBtn != null) {
+ var backUrl = Graph.sanitizeLink(toolbarConfig.backBtn.url);
+
+ if (backUrl != null) {
+ addButton(mxUtils.bind(this, function (evt) {
+ window.location.href = backUrl;
+ mxEvent.consume(evt);
+ }), Editor.backImage, mxResources.get('back', null, 'Back'));
+ }
+ }
+
+ if (this.isPagesEnabled()) {
+ var prevButton = addButton(mxUtils.bind(this, function (evt) {
+ this.actions.get('previousPage').funct();
+ mxEvent.consume(evt);
+ }), Editor.previousImage, mxResources.get('previousPage'));
+
+ var pageInfo = document.createElement('div');
+ pageInfo.style.fontFamily = Editor.defaultHtmlFont;
+ pageInfo.style.display = 'inline-block';
+ pageInfo.style.verticalAlign = 'top';
+ pageInfo.style.fontWeight = 'bold';
+ pageInfo.style.marginTop = '8px';
+ pageInfo.style.fontSize = '14px';
+
+ if (!mxClient.IS_IE && !mxClient.IS_IE11) {
+ pageInfo.style.color = '#ffffff';
+ }
+ else {
+ pageInfo.style.color = '#000000';
+ }
+
+ this.chromelessToolbar.appendChild(pageInfo);
+
+ var nextButton = addButton(mxUtils.bind(this, function (evt) {
+ this.actions.get('nextPage').funct();
+ mxEvent.consume(evt);
+ }), Editor.nextImage, mxResources.get('nextPage'));
+
+ var updatePageInfo = mxUtils.bind(this, function () {
+ if (this.pages != null && this.pages.length > 1 && this.currentPage != null) {
+ pageInfo.innerText = '';
+ mxUtils.write(pageInfo, (mxUtils.indexOf(this.pages, this.currentPage) + 1) + ' / ' + this.pages.length);
+ }
+ });
+
+ prevButton.style.paddingLeft = '0px';
+ prevButton.style.paddingRight = '4px';
+ nextButton.style.paddingLeft = '4px';
+ nextButton.style.paddingRight = '0px';
+
+ var updatePageButtons = mxUtils.bind(this, function () {
+ if (this.pages != null && this.pages.length > 1 && this.currentPage != null) {
+ nextButton.style.display = '';
+ prevButton.style.display = '';
+ pageInfo.style.display = 'inline-block';
+ }
+ else {
+ nextButton.style.display = 'none';
+ prevButton.style.display = 'none';
+ pageInfo.style.display = 'none';
+ }
+
+ updatePageInfo();
+ });
+
+ this.editor.addListener('resetGraphView', updatePageButtons);
+ this.editor.addListener('pageSelected', updatePageInfo);
+ }
+
+ addButton(mxUtils.bind(this, function (evt) {
+ this.actions.get('zoomOut').funct();
+ mxEvent.consume(evt);
+ }), Editor.zoomOutImage, mxResources.get('zoomOut') + ' (Alt+Mousewheel)');
+
+ addButton(mxUtils.bind(this, function (evt) {
+ this.actions.get('zoomIn').funct();
+ mxEvent.consume(evt);
+ }), Editor.zoomInImage, mxResources.get('zoomIn') + ' (Alt+Mousewheel)');
+
+ addButton(mxUtils.bind(this, function (evt) {
+ if (graph.isLightboxView()) {
+ if (graph.view.scale == 1) {
+ this.lightboxFit();
+ }
+ else {
+ graph.zoomTo(1);
+ }
+
+ this.chromelessResize(false);
+ }
+ else {
+ this.chromelessResize(true);
+ }
+
+ mxEvent.consume(evt);
+ }), Editor.zoomFitImage, mxResources.get('fit'));
+
+ // Changes toolbar opacity on hover
+ var fadeThread = null;
+ var fadeThread2 = null;
+
+ var fadeOut = mxUtils.bind(this, function (delay) {
+ if (fadeThread != null) {
+ window.clearTimeout(fadeThread);
+ fadeThread = null;
+ }
+
+ if (fadeThread2 != null) {
+ window.clearTimeout(fadeThread2);
+ fadeThread2 = null;
+ }
+
+ fadeThread = window.setTimeout(mxUtils.bind(this, function () {
+ mxUtils.setOpacity(this.chromelessToolbar, 0);
+ fadeThread = null;
+
+ fadeThread2 = window.setTimeout(mxUtils.bind(this, function () {
+ this.chromelessToolbar.style.display = 'none';
+ fadeThread2 = null;
+ }), 600);
+ }), delay || 200);
+ });
+
+ var fadeIn = mxUtils.bind(this, function (opacity) {
+ if (fadeThread != null) {
+ window.clearTimeout(fadeThread);
+ fadeThread = null;
+ }
+
+ if (fadeThread2 != null) {
+ window.clearTimeout(fadeThread2);
+ fadeThread2 = null;
+ }
+
+ this.chromelessToolbar.style.display = '';
+ mxUtils.setOpacity(this.chromelessToolbar, opacity || 30);
+ });
+
+ if (urlParams['layers'] == '1') {
+ this.layersDialog = null;
+
+ var layersButton = addButton(mxUtils.bind(this, function (evt) {
+ if (this.layersDialog != null) {
+ this.layersDialog.parentNode.removeChild(this.layersDialog);
+ this.layersDialog = null;
+ }
+ else {
+ this.layersDialog = graph.createLayersDialog(null, true);
+
+ mxEvent.addListener(this.layersDialog, 'mouseleave', mxUtils.bind(this, function () {
+ this.layersDialog.parentNode.removeChild(this.layersDialog);
+ this.layersDialog = null;
+ }));
+
+ var r = layersButton.getBoundingClientRect();
+
+ mxUtils.setPrefixedStyle(this.layersDialog.style, 'borderRadius', '5px');
+ this.layersDialog.style.position = 'fixed';
+ this.layersDialog.style.fontFamily = Editor.defaultHtmlFont;
+ this.layersDialog.style.width = '160px';
+ this.layersDialog.style.padding = '4px 2px 4px 2px';
+ this.layersDialog.style.left = r.left + 'px';
+ this.layersDialog.style.bottom = parseInt(this.chromelessToolbar.style.bottom) +
+ this.chromelessToolbar.offsetHeight + 4 + 'px';
+
+ if (!mxClient.IS_IE && !mxClient.IS_IE11) {
+ this.layersDialog.style.backgroundColor = '#000000';
+ this.layersDialog.style.color = '#ffffff';
+ mxUtils.setOpacity(this.layersDialog, 80);
+ }
+ else {
+ this.layersDialog.style.backgroundColor = '#ffffff';
+ this.layersDialog.style.border = '2px solid black';
+ this.layersDialog.style.color = '#000000';
+ }
+
+ // Puts the dialog on top of the container z-index
+ var style = mxUtils.getCurrentStyle(this.editor.graph.container);
+ this.layersDialog.style.zIndex = style.zIndex;
+
+ document.body.appendChild(this.layersDialog);
+ this.editor.fireEvent(new mxEventObject('layersDialogShown'));
+ }
+
+ mxEvent.consume(evt);
+ }), Editor.layersImage, mxResources.get('layers'));
+
+ // Shows/hides layers button depending on content
+ var model = graph.getModel();
+
+ model.addListener(mxEvent.CHANGE, function () {
+ layersButton.style.display = (model.getChildCount(model.root) > 1) ? '' : 'none';
+ });
+ }
+
+ if (urlParams['openInSameWin'] != '1' || navigator.standalone) {
+ this.addChromelessToolbarItems(addButton);
+ }
+
+ if (this.editor.editButtonLink != null || this.editor.editButtonFunc != null) {
+ addButton(mxUtils.bind(this, function (evt) {
+ if (this.editor.editButtonFunc != null) {
+ this.editor.editButtonFunc();
+ }
+ else if (this.editor.editButtonLink == '_blank') {
+ this.editor.editAsNew(this.getEditBlankXml());
+ }
+ else {
+ graph.openLink(this.editor.editButtonLink, 'editWindow');
+ }
+
+ mxEvent.consume(evt);
+ }), Editor.editImage, mxResources.get('edit'));
+ }
+
+ if (this.lightboxToolbarActions != null) {
+ for (var i = 0; i < this.lightboxToolbarActions.length; i++) {
+ var lbAction = this.lightboxToolbarActions[i];
+ lbAction.elem = addButton(lbAction.fn, lbAction.icon, lbAction.tooltip);
+ }
+ }
+
+ if (toolbarConfig.refreshBtn != null) {
+ var refreshUrl = (toolbarConfig.refreshBtn.url == null) ? null :
+ Graph.sanitizeLink(toolbarConfig.refreshBtn.url);
+
+ addButton(mxUtils.bind(this, function (evt) {
+ if (refreshUrl != null) {
+ window.location.href = refreshUrl;
+ }
+ else {
+ window.location.reload();
+ }
+
+ mxEvent.consume(evt);
+ }), Editor.refreshImage, mxResources.get('refresh', null, 'Refresh'));
+ }
+
+ if (toolbarConfig.fullscreenBtn != null && window.self !== window.top) {
+ addButton(mxUtils.bind(this, function (evt) {
+ if (toolbarConfig.fullscreenBtn.url) {
+ graph.openLink(toolbarConfig.fullscreenBtn.url);
+ }
+ else {
+ graph.openLink(window.location.href);
+ }
+
+ mxEvent.consume(evt);
+ }), Editor.fullscreenImage, mxResources.get('openInNewWindow', null, 'Open in New Window'));
+ }
+
+ if (!toolbarConfig.noCloseBtn && ((toolbarConfig.closeBtn && window.self === window.top) ||
+ (graph.lightbox && (urlParams['close'] == '1' || this.container != document.body)))) {
+ addButton(mxUtils.bind(this, function (evt) {
+ if (urlParams['close'] == '1' || toolbarConfig.closeBtn) {
+ window.close();
+ }
+ else {
+ this.destroy();
+ mxEvent.consume(evt);
+ }
+ }), Editor.closeImage, mxResources.get('close') + ' (Escape)');
+ }
+
+ // Initial state invisible
+ this.chromelessToolbar.style.display = 'none';
+
+ if (!graph.isViewer()) {
+ mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'transform', 'translate(-50%,0)');
+ }
+
+ graph.container.appendChild(this.chromelessToolbar);
+
+ mxEvent.addListener(graph.container, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', mxUtils.bind(this, function (evt) {
+ if (!mxEvent.isTouchEvent(evt)) {
+ if (!mxEvent.isShiftDown(evt)) {
+ fadeIn(30);
+ }
+
+ fadeOut();
+ }
+ }));
+
+ mxEvent.addListener(this.chromelessToolbar, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', function (evt) {
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(this.chromelessToolbar, 'mouseenter', mxUtils.bind(this, function (evt) {
+ graph.tooltipHandler.resetTimer();
+ graph.tooltipHandler.hideTooltip();
+
+ if (!mxEvent.isShiftDown(evt)) {
+ fadeIn(100);
+ }
+ else {
+ fadeOut();
+ }
+ }));
+
+ mxEvent.addListener(this.chromelessToolbar, 'mousemove', mxUtils.bind(this, function (evt) {
+ if (!mxEvent.isShiftDown(evt)) {
+ fadeIn(100);
+ }
+ else {
+ fadeOut();
+ }
+
+ mxEvent.consume(evt);
+ }));
+
+ mxEvent.addListener(this.chromelessToolbar, 'mouseleave', mxUtils.bind(this, function (evt) {
+ if (!mxEvent.isTouchEvent(evt)) {
+ fadeIn(30);
+ }
+ }));
+
+ // Shows/hides toolbar for touch devices
+ var tol = graph.getTolerance();
+
+ graph.addMouseListener(
+ {
+ startX: 0,
+ startY: 0,
+ scrollLeft: 0,
+ scrollTop: 0,
+ mouseDown: function (sender, me) {
+ this.startX = me.getGraphX();
+ this.startY = me.getGraphY();
+ this.scrollLeft = graph.container.scrollLeft;
+ this.scrollTop = graph.container.scrollTop;
+ },
+ mouseMove: function (sender, me) { },
+ mouseUp: function (sender, me) {
+ if (mxEvent.isTouchEvent(me.getEvent())) {
+ if ((Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
+ Math.abs(this.scrollTop - graph.container.scrollTop) < tol) &&
+ (Math.abs(this.startX - me.getGraphX()) < tol &&
+ Math.abs(this.startY - me.getGraphY()) < tol)) {
+ if (parseFloat(ui.chromelessToolbar.style.opacity || 0) > 0) {
+ fadeOut();
+ }
+ else {
+ fadeIn(30);
+ }
+ }
+ }
+ }
+ });
+ } // end if toolbar
+
+ // Installs handling of highlight and handling links to relative links and anchors
+ if (!this.editor.editable) {
+ this.addChromelessClickHandler();
+ }
+ }
+ else if (this.editor.extendCanvas) {
+ /**
+ * Guesses autoTranslate to avoid another repaint (see below).
+ * Works if only the scale of the graph changes or if pages
+ * are visible and the visible pages do not change. Uses
+ * geometries to guess the bounding box of the graph.
+ */
+ var graphViewValidate = graph.view.validate;
+ var zero = new mxPoint();
+ var lastPage = null;
+
+ graph.view.validate = function () {
+ if (graph.container != null &&
+ mxUtils.hasScrollbars(graph.container)) {
+ // Sets initial state after page changes
+ if (ui.currentPage != null &&
+ lastPage != ui.currentPage) {
+ lastPage = ui.currentPage;
+
+ // Sets initial translate based on geometries
+ // to avoid revalidation in sizeDidChange
+ var bbox = graph.getBoundingBoxFromGeometry(
+ graph.model.getCells(), true);
+
+ // Handles blank diagrams
+ if (bbox == null) {
+ bbox = new mxRectangle(
+ graph.view.translate.x * graph.view.scale,
+ graph.view.translate.y * graph.view.scale);
+ }
+
+ var pageLayout = graph.getPageLayout(bbox, zero, 1);
+ var tr = graph.getDefaultTranslate(pageLayout);
+ this.x0 = pageLayout.x;
+ this.y0 = pageLayout.y;
+
+ if (tr.x != this.translate.x ||
+ tr.y != this.translate.y) {
+ this.invalidate();
+ this.translate.x = tr.x;
+ this.translate.y = tr.y;
+ }
+ }
+
+ var pad = graph.getPagePadding();
+ var size = graph.getPageSize();
+ var tx = pad.x - (this.x0 || 0) * size.width;
+ var ty = pad.y - (this.y0 || 0) * size.height;
+
+ if (this.translate.x != tx || this.translate.y != ty) {
+ this.invalidate();
+ this.translate.x = tx
+ this.translate.y = ty
+ }
+ }
+
+ graphViewValidate.apply(this, arguments);
+ };
+
+ if (!graph.isViewer()) {
+ var graphSizeDidChange = graph.sizeDidChange;
+
+ graph.sizeDidChange = function () {
+ if (this.container != null &&
+ mxUtils.hasScrollbars(this.container)) {
+ this.updateMinimumSize();
+
+ if (!this.autoTranslate) {
+ var pageLayout = this.getPageLayout();
+ var tr = this.getDefaultTranslate(pageLayout);
+ var tx = this.view.translate.x;
+ var ty = this.view.translate.y;
+
+ if (tr.x != tx || tr.y != ty) {
+ this.view.x0 = pageLayout.x;
+ this.view.y0 = pageLayout.y;
+ this.autoTranslate = true;
+
+ // Requires full revalidation
+ this.view.setTranslate(tr.x, tr.y);
+ this.container.scrollLeft += Math.round((tr.x - tx) * this.view.scale);
+ this.container.scrollTop += Math.round((tr.y - ty) * this.view.scale);
+ this.autoTranslate = false;
+
+ return;
+ }
+ }
+
+ graphSizeDidChange.apply(this, arguments);
+ }
+ else {
+ // Fires event but does not invoke superclass
+ this.fireEvent(new mxEventObject(mxEvent.SIZE,
+ 'bounds', this.getGraphBounds()));
+ }
+ };
+ }
+ }
+
+ // Accumulates the zoom factor while the rendering is taking place
+ // so that not the complete sequence of zoom steps must be painted
+ var bgGroup = graph.view.getBackgroundPane();
+ var mainGroup = graph.view.getDrawPane();
+ graph.cumulativeZoomFactor = 1;
+ var updateZoomTimeout = null;
+ var cursorPosition = null;
+ var scrollPosition = null;
+ var forcedZoom = null;
+ var filter = null;
+ var mult = 20;
+
+ var scheduleZoom = function (delay) {
+ if (updateZoomTimeout != null) {
+ window.clearTimeout(updateZoomTimeout);
+ }
+
+ if (delay >= 0) {
+ window.setTimeout(function () {
+ if (!graph.isMouseDown || forcedZoom) {
+ updateZoomTimeout = window.setTimeout(mxUtils.bind(this, function () {
+ if (graph.isFastZoomEnabled()) {
+ // Transforms background page
+ if (graph.view.backgroundPageShape != null && graph.view.backgroundPageShape.node != null) {
+ mxUtils.setPrefixedStyle(graph.view.backgroundPageShape.node.style, 'transform-origin', null);
+ mxUtils.setPrefixedStyle(graph.view.backgroundPageShape.node.style, 'transform', null);
+ }
+
+ // Transforms graph and background image
+ mainGroup.style.transformOrigin = '';
+ bgGroup.style.transformOrigin = '';
+
+ // Workaround for no reset of transform in Safari
+ if (mxClient.IS_SF) {
+ mainGroup.style.transform = 'scale(1)';
+ bgGroup.style.transform = 'scale(1)';
+
+ window.setTimeout(function () {
+ mainGroup.style.transform = '';
+ bgGroup.style.transform = '';
+ }, 0)
+ }
+ else {
+ mainGroup.style.transform = '';
+ bgGroup.style.transform = '';
+ }
+
+ // Shows interactive elements
+ graph.view.getDecoratorPane().style.opacity = '';
+ graph.view.getOverlayPane().style.opacity = '';
+ }
+
+ var sp = new mxPoint(graph.container.scrollLeft, graph.container.scrollTop);
+ var offset = mxUtils.getOffset(graph.container);
+ var prev = graph.view.scale;
+ var dx = 0;
+ var dy = 0;
+
+ if (cursorPosition != null) {
+ dx = graph.container.offsetWidth / 2 - cursorPosition.x + offset.x;
+ dy = graph.container.offsetHeight / 2 - cursorPosition.y + offset.y;
+ }
+
+ graph.zoom(graph.cumulativeZoomFactor, null,
+ graph.isFastZoomEnabled() ? mult : null);
+ var s = graph.view.scale;
+
+ if (s != prev) {
+ if (scrollPosition != null) {
+ dx += sp.x - scrollPosition.x;
+ dy += sp.y - scrollPosition.y;
+ }
+
+ if (resize != null) {
+ ui.chromelessResize(false, null, dx * (graph.cumulativeZoomFactor - 1),
+ dy * (graph.cumulativeZoomFactor - 1));
+ }
+
+ if (mxUtils.hasScrollbars(graph.container) && (dx != 0 || dy != 0)) {
+ graph.container.scrollLeft -= dx * (graph.cumulativeZoomFactor - 1);
+ graph.container.scrollTop -= dy * (graph.cumulativeZoomFactor - 1);
+ }
+ }
+
+ if (filter != null) {
+ mainGroup.setAttribute('filter', filter);
+ }
+
+ graph.cumulativeZoomFactor = 1;
+ updateZoomTimeout = null;
+ scrollPosition = null;
+ cursorPosition = null;
+ forcedZoom = null;
+ filter = null;
+ }), (delay != null) ? delay : ((graph.isFastZoomEnabled()) ? ui.wheelZoomDelay : ui.lazyZoomDelay));
+ }
+ }, 0);
+ }
+ };
+
+ graph.lazyZoom = function (zoomIn, ignoreCursorPosition, delay, factor) {
+ factor = (factor != null) ? factor : this.zoomFactor;
+
+ // TODO: Fix ignored cursor position if scrollbars are disabled
+ ignoreCursorPosition = ignoreCursorPosition || !graph.scrollbars;
+
+ if (ignoreCursorPosition) {
+ cursorPosition = new mxPoint(
+ graph.container.offsetLeft + graph.container.clientWidth / 2,
+ graph.container.offsetTop + graph.container.clientHeight / 2);
+ }
+
+ // Switches to 5% zoom steps below 15%
+ if (zoomIn) {
+ if (this.view.scale * this.cumulativeZoomFactor <= 0.15) {
+ this.cumulativeZoomFactor *= (this.view.scale + 0.05) / this.view.scale;
+ }
+ else {
+ this.cumulativeZoomFactor *= factor;
+ this.cumulativeZoomFactor = Math.round(this.view.scale * this.cumulativeZoomFactor * 100) / 100 / this.view.scale;
+ }
+ }
+ else {
+ if (this.view.scale * this.cumulativeZoomFactor <= 0.15) {
+ this.cumulativeZoomFactor *= (this.view.scale - 0.05) / this.view.scale;
+ }
+ else {
+ this.cumulativeZoomFactor /= factor;
+ this.cumulativeZoomFactor = Math.round(this.view.scale * this.cumulativeZoomFactor * 100) / 100 / this.view.scale;
+ }
+ }
+
+ this.cumulativeZoomFactor = Math.max(0.05, Math.min(this.view.scale * this.cumulativeZoomFactor, 160)) / this.view.scale;
+
+ if (graph.isFastZoomEnabled()) {
+ if (filter == null && mainGroup.getAttribute('filter') != '') {
+ filter = mainGroup.getAttribute('filter');
+ mainGroup.removeAttribute('filter');
+ }
+
+ scrollPosition = new mxPoint(graph.container.scrollLeft, graph.container.scrollTop);
+
+ // Applies final rounding to preview
+ var f = Math.round((Math.round(this.view.scale * this.cumulativeZoomFactor *
+ 100) / 100) * mult) / (mult * this.view.scale);
+
+ var cx = (ignoreCursorPosition || cursorPosition == null) ?
+ graph.container.scrollLeft + graph.container.clientWidth / 2 :
+ cursorPosition.x + graph.container.scrollLeft - graph.container.offsetLeft;
+ var cy = (ignoreCursorPosition || cursorPosition == null) ?
+ graph.container.scrollTop + graph.container.clientHeight / 2 :
+ cursorPosition.y + graph.container.scrollTop - graph.container.offsetTop;
+ mainGroup.style.transformOrigin = cx + 'px ' + cy + 'px';
+ mainGroup.style.transform = 'scale(' + f + ')';
+ bgGroup.style.transformOrigin = cx + 'px ' + cy + 'px';
+ bgGroup.style.transform = 'scale(' + f + ')';
+
+ if (graph.view.backgroundPageShape != null && graph.view.backgroundPageShape.node != null) {
+ var page = graph.view.backgroundPageShape.node;
+
+ mxUtils.setPrefixedStyle(page.style, 'transform-origin',
+ ((ignoreCursorPosition || cursorPosition == null) ?
+ ((graph.container.clientWidth / 2 + graph.container.scrollLeft -
+ page.offsetLeft) + 'px') : ((cursorPosition.x + graph.container.scrollLeft -
+ page.offsetLeft - graph.container.offsetLeft) + 'px')) + ' ' +
+ ((ignoreCursorPosition || cursorPosition == null) ?
+ ((graph.container.clientHeight / 2 + graph.container.scrollTop -
+ page.offsetTop) + 'px') : ((cursorPosition.y + graph.container.scrollTop -
+ page.offsetTop - graph.container.offsetTop) + 'px')));
+ mxUtils.setPrefixedStyle(page.style, 'transform', 'scale(' + f + ')');
+ }
+ else {
+ graph.view.validateBackgroundStyles(f, cx, cy);
+ }
+
+ graph.view.getDecoratorPane().style.opacity = '0';
+ graph.view.getOverlayPane().style.opacity = '0';
+
+ if (ui.hoverIcons != null) {
+ ui.hoverIcons.reset();
+ }
+
+ graph.fireEvent(new mxEventObject('zoomPreview', 'factor', f));
+ }
+
+ scheduleZoom(graph.isFastZoomEnabled() ? delay : 0);
+ };
+
+ // Holds back repaint until after mouse gestures
+ mxEvent.addGestureListeners(graph.container, function (evt) {
+ if (updateZoomTimeout != null) {
+ window.clearTimeout(updateZoomTimeout);
+ }
+ }, null, function (evt) {
+ if (graph.cumulativeZoomFactor != 1) {
+ scheduleZoom(0);
+ }
+ });
+
+ // Holds back repaint until scroll ends
+ mxEvent.addListener(graph.container, 'scroll', function (evt) {
+ if (updateZoomTimeout != null && !graph.isMouseDown && graph.cumulativeZoomFactor != 1) {
+ scheduleZoom(0);
+ }
+ });
+
+ mxEvent.addMouseWheelListener(mxUtils.bind(this, function (evt, up, force, cx, cy) {
+ graph.fireEvent(new mxEventObject('wheel'));
+
+ if (this.dialogs == null || this.dialogs.length == 0) {
+ // Scrolls with scrollbars turned off
+ if (!graph.scrollbars && !force && graph.isScrollWheelEvent(evt)) {
+ var t = graph.view.getTranslate();
+ var step = 40 / graph.view.scale;
+
+ if (!mxEvent.isShiftDown(evt)) {
+ graph.view.setTranslate(t.x, t.y + ((up) ? step : -step));
+ }
+ else {
+ graph.view.setTranslate(t.x + ((up) ? -step : step), t.y);
+ }
+ }
+ else if (force || graph.isZoomWheelEvent(evt)) {
+ var source = mxEvent.getSource(evt);
+
+ while (source != null) {
+ if (source == graph.container) {
+ graph.tooltipHandler.hideTooltip();
+ cursorPosition = (cx != null && cy != null) ? new mxPoint(cx, cy) :
+ new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ forcedZoom = force;
+ var factor = graph.zoomFactor;
+ var delay = null;
+
+ // Slower zoom for pinch gesture on trackpad with max delta to
+ // filter out mouse wheel events in Brave browser for Windows
+ if (evt.ctrlKey && evt.deltaY != null && Math.abs(evt.deltaY) < 40 &&
+ Math.round(evt.deltaY) != evt.deltaY) {
+ factor = 1 + (Math.abs(evt.deltaY) / 20) * (factor - 1);
+ }
+ // Slower zoom for pinch gesture on touch screens
+ else if (evt.movementY != null && evt.type == 'pointermove') {
+ factor = 1 + (Math.max(1, Math.abs(evt.movementY)) / 20) * (factor - 1);
+ delay = -1;
+ }
+
+ graph.lazyZoom(up, null, delay, factor);
+ mxEvent.consume(evt);
+
+ return false;
+ }
+
+ source = source.parentNode;
+ }
+ }
+ }
+ }), graph.container);
+
+ // Uses fast zoom for pinch gestures on iOS
+ graph.panningHandler.zoomGraph = function (evt) {
+ graph.cumulativeZoomFactor = evt.scale;
+ graph.lazyZoom(evt.scale > 0, true);
+ mxEvent.consume(evt);
+ };
+};
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+EditorUi.prototype.addChromelessToolbarItems = function (addButton) {
+ addButton(mxUtils.bind(this, function (evt) {
+ this.actions.get('print').funct();
+ mxEvent.consume(evt);
+ }), Editor.printImage, mxResources.get('print'));
+};
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+EditorUi.prototype.isPagesEnabled = function () {
+ return this.editor.editable || urlParams['hide-pages'] != '1';
+};
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+EditorUi.prototype.createTemporaryGraph = function (stylesheet) {
+ return Graph.createOffscreenGraph(stylesheet);
+};
+
+/**
+ *
+ */
+EditorUi.prototype.addChromelessClickHandler = function () {
+ var hl = urlParams['highlight'];
+
+ // Adds leading # for highlight color code
+ if (hl != null && hl.length > 0) {
+ hl = '#' + hl;
+ }
+
+ this.editor.graph.addClickHandler(hl);
+};
+
+/**
+ *
+ */
+EditorUi.prototype.toggleFormatPanel = function (visible) {
+ visible = (visible != null) ? visible : this.formatWidth == 0;
+
+ if (this.format != null) {
+ this.formatWidth = (visible) ? 240 : 0;
+ this.formatContainer.style.width = this.formatWidth + 'px';
+ this.refresh();
+ this.format.refresh();
+ this.fireEvent(new mxEventObject('formatWidthChanged'));
+ }
+};
+
+/**
+ *
+ */
+EditorUi.prototype.isFormatPanelVisible = function () {
+ return this.formatWidth > 0;
+};
+
+/**
+ * Adds support for placeholders in labels.
+ */
+EditorUi.prototype.lightboxFit = function (maxHeight) {
+ if (this.isDiagramEmpty()) {
+ this.editor.graph.view.setScale(1);
+ }
+ else {
+ var p = urlParams['border'];
+ var border = 60;
+
+ if (p != null) {
+ border = parseInt(p);
+ }
+
+ // LATER: Use initial graph bounds to avoid rounding errors
+ this.editor.graph.maxFitScale = this.lightboxMaxFitScale;
+ this.editor.graph.fit(border, null, null, null, null, null, maxHeight);
+ this.editor.graph.maxFitScale = null;
+ }
+};
+
+/**
+ * Translates this point by the given vector.
+ *
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+EditorUi.prototype.isDiagramEmpty = function () {
+ var model = this.editor.graph.getModel();
+
+ return model.getChildCount(model.root) == 1 && model.getChildCount(model.getChildAt(model.root, 0)) == 0;
+};
+
+/**
+ * Hook for allowing selection and context menu for certain events.
+ */
+EditorUi.prototype.isSelectionAllowed = function (evt) {
+ return mxEvent.getSource(evt).nodeName == 'SELECT' || (mxEvent.getSource(evt).nodeName == 'INPUT' &&
+ mxUtils.isAncestorNode(this.formatContainer, mxEvent.getSource(evt)));
+};
+
+/**
+ * Installs dialog if browser window is closed without saving
+ * This must be disabled during save and image export.
+ */
+EditorUi.prototype.addBeforeUnloadListener = function () {
+ // Installs dialog if browser window is closed without saving
+ // This must be disabled during save and image export
+ window.onbeforeunload = mxUtils.bind(this, function () {
+ if (!this.editor.isChromelessView()) {
+ return this.onBeforeUnload();
+ }
+ });
+};
+
+/**
+ * Sets the onbeforeunload for the application
+ */
+EditorUi.prototype.onBeforeUnload = function () {
+ if (this.editor.modified) {
+ return mxResources.get('allChangesLost');
+ }
+};
+
+/**
+ * Opens the current diagram via the window.opener if one exists.
+ */
+EditorUi.prototype.open = function () {
+ // Cross-domain window access is not allowed in FF, so if we
+ // were opened from another domain then this will fail.
+ try {
+ if (window.opener != null && window.opener.openFile != null) {
+ window.opener.openFile.setConsumer(mxUtils.bind(this, function (xml, filename) {
+ try {
+ var doc = mxUtils.parseXml(xml);
+ this.editor.setGraphXml(doc.documentElement);
+ this.editor.setModified(false);
+ this.editor.undoManager.clear();
+
+ if (filename != null) {
+ this.editor.setFilename(filename);
+ this.updateDocumentTitle();
+ }
+
+ return;
+ }
+ catch (e) {
+ mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
+ }
+ }));
+ }
+ }
+ catch (e) {
+ // ignore
+ }
+
+ // Fires as the last step if no file was loaded
+ this.editor.graph.view.validate();
+
+ // Required only in special cases where an initial file is opened
+ // and the minimumGraphSize changes and CSS must be updated.
+ this.editor.graph.sizeDidChange();
+ this.editor.fireEvent(new mxEventObject('resetGraphView'));
+};
+
+/**
+ * Shows the given popup menu.
+ */
+EditorUi.prototype.showPopupMenu = function (fn, x, y, evt) {
+ this.editor.graph.popupMenuHandler.hideMenu();
+
+ var menu = new mxPopupMenu(fn);
+ menu.div.className += ' geMenubarMenu';
+ menu.smartSeparators = true;
+ menu.showDisabled = true;
+ menu.autoExpand = true;
+
+ // Disables autoexpand and destroys menu when hidden
+ menu.hideMenu = mxUtils.bind(this, function () {
+ mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
+ menu.destroy();
+ });
+
+ menu.popup(x, y, null, evt);
+
+ // Allows hiding by clicking on document
+ this.setCurrentMenu(menu);
+};
+
+/**
+ * Sets the current menu and element.
+ */
+EditorUi.prototype.setCurrentMenu = function (menu, elt) {
+ this.currentMenuElt = elt;
+ this.currentMenu = menu;
+ this.hideShapePicker();
+};
+
+/**
+ * Resets the current menu and element.
+ */
+EditorUi.prototype.resetCurrentMenu = function () {
+ this.currentMenuElt = null;
+ this.currentMenu = null;
+};
+
+/**
+ * Hides and destroys the current menu.
+ */
+EditorUi.prototype.hideCurrentMenu = function () {
+ if (this.currentMenu != null) {
+ this.currentMenu.hideMenu();
+ this.resetCurrentMenu();
+ }
+};
+
+/**
+ * Updates the document title.
+ */
+EditorUi.prototype.updateDocumentTitle = function () {
+ var title = this.editor.getOrCreateFilename();
+
+ if (this.editor.appName != null) {
+ title += ' - ' + this.editor.appName;
+ }
+
+ document.title = title;
+};
+
+/**
+ * Updates the document title.
+ */
+EditorUi.prototype.createHoverIcons = function () {
+ return new HoverIcons(this.editor.graph);
+};
+
+/**
+ * Returns the URL for a copy of this editor with no state.
+ */
+EditorUi.prototype.redo = function () {
+ try {
+ var graph = this.editor.graph;
+
+ if (graph.isEditing()) {
+ document.execCommand('redo', false, null);
+ }
+ else {
+ this.editor.undoManager.redo();
+ }
+ }
+ catch (e) {
+ // ignore all errors
+ }
+};
+
+/**
+ * Returns the URL for a copy of this editor with no state.
+ */
+EditorUi.prototype.undo = function () {
+ try {
+ var graph = this.editor.graph;
+
+ if (graph.isEditing()) {
+ // Stops editing and executes undo on graph if native undo
+ // does not affect current editing value
+ var value = graph.cellEditor.textarea.innerHTML;
+ document.execCommand('undo', false, null);
+
+ if (value == graph.cellEditor.textarea.innerHTML) {
+ graph.stopEditing(true);
+ this.editor.undoManager.undo();
+ }
+ }
+ else {
+ this.editor.undoManager.undo();
+ }
+ }
+ catch (e) {
+ // ignore all errors
+ }
+};
+
+/**
+ * Returns the URL for a copy of this editor with no state.
+ */
+EditorUi.prototype.canRedo = function () {
+ return this.editor.graph.isEditing() || this.editor.undoManager.canRedo();
+};
+
+/**
+ * Returns the URL for a copy of this editor with no state.
+ */
+EditorUi.prototype.canUndo = function () {
+ return this.editor.graph.isEditing() || this.editor.undoManager.canUndo();
+};
+
+/**
+ *
+ */
+EditorUi.prototype.getEditBlankXml = function () {
+ return mxUtils.getXml(this.editor.getGraphXml());
+};
+
+/**
+ * Returns the URL for a copy of this editor with no state.
+ */
+EditorUi.prototype.getUrl = function (pathname) {
+ var href = (pathname != null) ? pathname : window.location.pathname;
+ var parms = (href.indexOf('?') > 0) ? 1 : 0;
+
+ // Removes template URL parameter for new blank diagram
+ for (var key in urlParams) {
+ if (parms == 0) {
+ href += '?';
+ }
+ else {
+ href += '&';
+ }
+
+ href += key + '=' + urlParams[key];
+ parms++;
+ }
+
+ return href;
+};
+
+/**
+ * Specifies if the graph has scrollbars.
+ */
+EditorUi.prototype.setScrollbars = function (value) {
+ var graph = this.editor.graph;
+ var prev = graph.container.style.overflow;
+ graph.scrollbars = value;
+ this.editor.updateGraphComponents();
+
+ if (prev != graph.container.style.overflow) {
+ graph.container.scrollTop = 0;
+ graph.container.scrollLeft = 0;
+ graph.view.scaleAndTranslate(1, 0, 0);
+ this.resetScrollbars();
+ }
+
+ this.fireEvent(new mxEventObject('scrollbarsChanged'));
+};
+
+/**
+ * Function: fitDiagramToWindow
+ *
+ * Zooms the diagram to fit into the window.
+ */
+EditorUi.prototype.fitDiagramToWindow = function () {
+ var graph = this.editor.graph;
+ var bounds = (graph.isSelectionEmpty()) ?
+ mxRectangle.fromRectangle(graph.getGraphBounds()) :
+ graph.getBoundingBox(graph.getSelectionCells())
+ var t = graph.view.translate;
+ var s = graph.view.scale;
+
+ bounds.x = bounds.x / s - t.x;
+ bounds.y = bounds.y / s - t.y;
+ bounds.width /= s;
+ bounds.height /= s;
+
+ if (graph.backgroundImage != null) {
+ bounds.add(new mxRectangle(0, 0,
+ graph.backgroundImage.width,
+ graph.backgroundImage.height));
+ }
+
+ if (bounds.width == 0 || bounds.height == 0) {
+ graph.zoomTo(1);
+ this.resetScrollbars();
+ }
+ else {
+ var b = Editor.fitWindowBorders;
+
+ if (b != null) {
+ bounds.x -= b.x;
+ bounds.y -= b.y;
+ bounds.width += b.width + b.x;
+ bounds.height += b.height + b.y;
+ }
+
+ graph.fitWindow(bounds);
+ }
+};
+
+/**
+ * Returns true if the graph has scrollbars.
+ */
+EditorUi.prototype.hasScrollbars = function () {
+ return this.editor.graph.scrollbars;
+};
+
+/**
+ * Resets the state of the scrollbars.
+ */
+EditorUi.prototype.resetScrollbars = function () {
+ var graph = this.editor.graph;
+ var c = graph.container;
+
+ if (!this.editor.extendCanvas) {
+ c.scrollTop = 0;
+ c.scrollLeft = 0;
+
+ if (!mxUtils.hasScrollbars(c)) {
+ graph.view.setTranslate(0, 0);
+ }
+ }
+ else if (!this.editor.isChromelessView()) {
+ if (mxUtils.hasScrollbars(c)) {
+ if (graph.pageVisible) {
+ var pad = graph.getPagePadding();
+ c.scrollTop = Math.floor(pad.y - this.editor.initialTopSpacing) - 1;
+ c.scrollLeft = Math.floor(Math.min(pad.x,
+ (c.scrollWidth - c.clientWidth) / 2)) - 1;
+
+ // Scrolls graph to visible area
+ var bounds = graph.getGraphBounds();
+
+ if (bounds.width > 0 && bounds.height > 0) {
+ if (bounds.x > c.scrollLeft + c.clientWidth * 0.9) {
+ c.scrollLeft = Math.min(bounds.x + bounds.width - c.clientWidth, bounds.x - 10);
+ }
+
+ if (bounds.y > c.scrollTop + c.clientHeight * 0.9) {
+ c.scrollTop = Math.min(bounds.y + bounds.height - c.clientHeight, bounds.y - 10);
+ }
+ }
+ }
+ else {
+ var bounds = graph.getGraphBounds();
+
+ if (bounds.width == 0 && bounds.height == 0) {
+ c.scrollLeft = (c.scrollWidth - c.clientWidth) / 2;
+ c.scrollTop = (c.scrollHeight - c.clientHeight) / 2;
+ }
+ else {
+ var width = Math.max(bounds.width, graph.scrollTileSize.width * graph.view.scale);
+ var height = Math.max(bounds.height, graph.scrollTileSize.height * graph.view.scale);
+
+ c.scrollLeft = Math.floor(Math.max(0, bounds.x - Math.max(0, (c.clientWidth - width) / 2)));
+ c.scrollTop = Math.floor(Math.max(0, bounds.y - Math.max(20, (c.clientHeight - height) / 4)));
+ }
+ }
+ }
+ else {
+ var b = mxRectangle.fromRectangle((graph.pageVisible) ?
+ graph.view.getBackgroundPageBounds() :
+ graph.getGraphBounds())
+ var tr = graph.view.translate;
+ var s = graph.view.scale;
+ b.x = b.x / s - tr.x;
+ b.y = b.y / s - tr.y;
+ b.width /= s;
+ b.height /= s;
+
+ var dy = (graph.pageVisible) ? 0 : Math.max(0, (c.clientHeight - b.height) / 4);
+
+ graph.view.setTranslate(Math.floor(Math.max(0,
+ (c.clientWidth - b.width) / 2) - b.x + 2),
+ Math.floor(dy - b.y + 1));
+ }
+ }
+};
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+EditorUi.prototype.setPageVisible = function (value) {
+ var graph = this.editor.graph;
+ var hasScrollbars = mxUtils.hasScrollbars(graph.container);
+ var tx = 0;
+ var ty = 0;
+
+ if (hasScrollbars) {
+ tx = graph.view.translate.x * graph.view.scale - graph.container.scrollLeft;
+ ty = graph.view.translate.y * graph.view.scale - graph.container.scrollTop;
+ }
+
+ graph.pageVisible = value;
+ graph.pageBreaksVisible = value;
+ graph.preferPageSize = value;
+ graph.view.validateBackground();
+
+ // Workaround for possible handle offset
+ if (hasScrollbars) {
+ var cells = graph.getSelectionCells();
+ graph.clearSelection();
+ graph.setSelectionCells(cells);
+ }
+
+ // Calls updatePageBreaks
+ graph.sizeDidChange();
+
+ if (hasScrollbars) {
+ graph.container.scrollLeft = graph.view.translate.x * graph.view.scale - tx;
+ graph.container.scrollTop = graph.view.translate.y * graph.view.scale - ty;
+ }
+
+ graph.defaultPageVisible = value;
+ this.fireEvent(new mxEventObject('pageViewChanged'));
+};
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+EditorUi.prototype.installResizeHandler = function (dialog, resizable, destroy) {
+ if (resizable) {
+ dialog.window.setSize = function (w, h) {
+ if (!this.minimized) {
+ var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
+ var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;
+ w = Math.min(w, iw - this.getX());
+ h = Math.min(h, ih - this.getY());
+ }
+
+ mxWindow.prototype.setSize.apply(this, arguments);
+ };
+ }
+
+ dialog.window.setLocation = function (x, y) {
+ var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
+ var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;
+
+ var w = parseInt(this.div.style.width);
+ var h = parseInt(this.div.style.height);
+
+ x = Math.max(0, Math.min(x, iw - w));
+ y = Math.max(0, Math.min(y, ih - h));
+
+ if (this.getX() != x || this.getY() != y) {
+ mxWindow.prototype.setLocation.apply(this, arguments);
+ }
+
+ if (resizable && !this.minimized) {
+ this.setSize(w, h);
+ }
+ };
+
+ var resizeListener = mxUtils.bind(this, function () {
+ var x = dialog.window.getX();
+ var y = dialog.window.getY();
+
+ dialog.window.setLocation(x, y);
+ });
+
+ mxEvent.addListener(window, 'resize', resizeListener);
+
+ dialog.destroy = function () {
+ mxEvent.removeListener(window, 'resize', resizeListener);
+ dialog.window.destroy();
+
+ if (destroy != null) {
+ destroy();
+ }
+ }
+};
+
+/**
+ * Class: ChangeGridColor
+ *
+ * Undoable change to grid color.
+ */
+function ChangeGridColor(ui, color) {
+ this.ui = ui;
+ this.color = color;
+};
+
+/**
+ * Executes selection of a new page.
+ */
+ChangeGridColor.prototype.execute = function () {
+ var temp = this.ui.editor.graph.view.gridColor;
+ this.ui.setGridColor(this.color);
+ this.color = temp;
+};
+
+// Registers codec for ChangePageSetup
+(function () {
+ var codec = new mxObjectCodec(new ChangeGridColor(), ['ui']);
+
+ mxCodecRegistry.register(codec);
+})();
+
+/**
+ * Change types
+ */
+function ChangePageSetup(ui, color, image, format, pageScale) {
+ this.ui = ui;
+ this.color = color;
+ this.previousColor = color;
+ this.image = image;
+ this.previousImage = image;
+ this.format = format;
+ this.previousFormat = format;
+ this.pageScale = pageScale;
+ this.previousPageScale = pageScale;
+
+ // Needed since null are valid values for color and image
+ this.ignoreColor = false;
+ this.ignoreImage = false;
+}
+
+/**
+ * Implementation of the undoable page rename.
+ */
+ChangePageSetup.prototype.execute = function () {
+ var graph = this.ui.editor.graph;
+
+ if (!this.ignoreColor) {
+ this.color = this.previousColor;
+ var tmp = graph.background;
+ this.ui.setBackgroundColor(this.previousColor);
+ this.previousColor = tmp;
+ }
+
+ if (!this.ignoreImage) {
+ this.image = this.previousImage;
+ var tmp = graph.backgroundImage;
+ var img = this.previousImage;
+
+ if (img != null && Graph.isPageLink(img.src)) {
+ img = this.ui.createImageForPageLink(img.src, this.ui.currentPage);
+ }
+
+ this.ui.setBackgroundImage(img);
+ this.previousImage = tmp;
+ }
+
+ if (this.previousFormat != null) {
+ this.format = this.previousFormat;
+ var tmp = graph.pageFormat;
+
+ if (this.previousFormat.width != tmp.width ||
+ this.previousFormat.height != tmp.height) {
+ this.ui.setPageFormat(this.previousFormat);
+ this.previousFormat = tmp;
+ }
+ }
+
+ if (this.foldingEnabled != null && this.foldingEnabled != this.ui.editor.graph.foldingEnabled) {
+ this.ui.setFoldingEnabled(this.foldingEnabled);
+ this.foldingEnabled = !this.foldingEnabled;
+ }
+
+ if (this.previousPageScale != null) {
+ var currentPageScale = this.ui.editor.graph.pageScale;
+
+ if (this.previousPageScale != currentPageScale) {
+ this.ui.setPageScale(this.previousPageScale);
+ this.previousPageScale = currentPageScale;
+ }
+ }
+};
+
+// Registers codec for ChangePageSetup
+(function () {
+ var codec = new mxObjectCodec(new ChangePageSetup(), ['ui', 'previousColor', 'previousImage', 'previousFormat', 'previousPageScale']);
+
+ codec.afterDecode = function (dec, node, obj) {
+ obj.previousColor = obj.color;
+ obj.previousImage = obj.image;
+ obj.previousFormat = obj.format;
+ obj.previousPageScale = obj.pageScale;
+
+ if (obj.foldingEnabled != null) {
+ obj.foldingEnabled = !obj.foldingEnabled;
+ }
+
+ return obj;
+ };
+
+ mxCodecRegistry.register(codec);
+})();
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+EditorUi.prototype.setBackgroundColor = function (value) {
+ this.editor.graph.background = value;
+ this.editor.graph.view.validateBackground();
+
+ this.fireEvent(new mxEventObject('backgroundColorChanged'));
+};
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+EditorUi.prototype.setFoldingEnabled = function (value) {
+ this.editor.graph.foldingEnabled = value;
+ this.editor.graph.view.revalidate();
+
+ this.fireEvent(new mxEventObject('foldingEnabledChanged'));
+};
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+EditorUi.prototype.setPageFormat = function (value, ignorePageVisible) {
+ ignorePageVisible = (ignorePageVisible != null) ? ignorePageVisible : urlParams['sketch'] == '1';
+ this.editor.graph.pageFormat = value;
+
+ if (!ignorePageVisible) {
+ if (!this.editor.graph.pageVisible) {
+ this.actions.get('pageView').funct();
+ }
+ else {
+ this.editor.graph.view.validateBackground();
+ this.editor.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject('pageFormatChanged'));
+};
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+EditorUi.prototype.setPageScale = function (value) {
+ this.editor.graph.pageScale = value;
+
+ if (!this.editor.graph.pageVisible) {
+ this.actions.get('pageView').funct();
+ }
+ else {
+ this.editor.graph.view.validateBackground();
+ this.editor.graph.sizeDidChange();
+ }
+
+ this.fireEvent(new mxEventObject('pageScaleChanged'));
+};
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+EditorUi.prototype.setGridColor = function (value) {
+ this.editor.graph.view.gridColor = value;
+ this.editor.graph.view.validateBackground();
+ this.fireEvent(new mxEventObject('gridColorChanged'));
+};
+
+/**
+ * Updates the states of the given undo/redo items.
+ */
+EditorUi.prototype.addUndoListener = function () {
+ var undoMgr = this.editor.undoManager;
+
+ var undoListener = mxUtils.bind(this, function () {
+ this.updateActionStates();
+ });
+
+ undoMgr.addListener(mxEvent.ADD, undoListener);
+ undoMgr.addListener(mxEvent.UNDO, undoListener);
+ undoMgr.addListener(mxEvent.REDO, undoListener);
+ undoMgr.addListener(mxEvent.CLEAR, undoListener);
+
+ // Overrides cell editor to update action states
+ var cellEditorStartEditing = this.editor.graph.cellEditor.startEditing;
+
+ this.editor.graph.cellEditor.startEditing = function () {
+ cellEditorStartEditing.apply(this, arguments);
+ undoListener();
+ };
+
+ var cellEditorStopEditing = this.editor.graph.cellEditor.stopEditing;
+
+ this.editor.graph.cellEditor.stopEditing = function (cell, trigger) {
+ cellEditorStopEditing.apply(this, arguments);
+ undoListener();
+ };
+
+ // Updates the button states once
+ undoListener();
+};
+
+/**
+* Updates the states of the given toolbar items based on the selection.
+*/
+EditorUi.prototype.updateActionStates = function () {
+ var graph = this.editor.graph;
+ var ss = this.getSelectionState();
+ var unlocked = graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent());
+ var editable = !this.editor.chromeless || this.editor.editable;
+
+ // Updates action states
+ var actions = ['cut', 'copy', 'bold', 'italic', 'underline', 'delete', 'duplicate',
+ 'editStyle', 'editTooltip', 'editLink', 'backgroundColor', 'borderColor',
+ 'edit', 'toFront', 'toBack', 'solid', 'dashed', 'pasteSize',
+ 'dotted', 'fillColor', 'gradientColor', 'shadow', 'fontColor',
+ 'formattedText', 'rounded', 'toggleRounded', 'strokeColor',
+ 'sharp', 'snapToGrid'];
+
+ for (var i = 0; i < actions.length; i++) {
+ this.actions.get(actions[i]).setEnabled(ss.cells.length > 0);
+ }
+
+ this.actions.get('grid').setEnabled(editable);
+ this.actions.get('undo').setEnabled(this.canUndo() && editable);
+ this.actions.get('redo').setEnabled(this.canRedo() && editable);
+ this.actions.get('pasteSize').setEnabled(this.copiedSize != null && ss.vertices.length > 0);
+ this.actions.get('pasteData').setEnabled(this.copiedValue != null && ss.cells.length > 0);
+ this.actions.get('setAsDefaultStyle').setEnabled(graph.getSelectionCount() == 1);
+ this.actions.get('lockUnlock').setEnabled(!graph.isSelectionEmpty());
+ this.actions.get('bringForward').setEnabled(ss.cells.length == 1);
+ this.actions.get('sendBackward').setEnabled(ss.cells.length == 1);
+ this.actions.get('rotation').setEnabled(ss.vertices.length == 1);
+ this.actions.get('wordWrap').setEnabled(ss.vertices.length == 1);
+ this.actions.get('autosize').setEnabled(ss.vertices.length > 0);
+ this.actions.get('copySize').setEnabled(ss.vertices.length == 1);
+ this.actions.get('clearWaypoints').setEnabled(ss.connections);
+ this.actions.get('curved').setEnabled(ss.edges.length > 0);
+ this.actions.get('turn').setEnabled(ss.cells.length > 0);
+ this.actions.get('group').setEnabled(!ss.row && !ss.cell &&
+ (ss.cells.length > 1 || (ss.vertices.length == 1 &&
+ graph.model.getChildCount(ss.cells[0]) == 0 &&
+ !graph.isContainer(ss.vertices[0]))));
+ this.actions.get('ungroup').setEnabled(!ss.row && !ss.cell && !ss.table &&
+ ss.vertices.length > 0 && (graph.isContainer(ss.vertices[0]) ||
+ graph.getModel().getChildCount(ss.vertices[0]) > 0));
+ this.actions.get('removeFromGroup').setEnabled(ss.cells.length == 1 &&
+ graph.getModel().isVertex(graph.getModel().getParent(ss.cells[0])));
+ this.actions.get('collapsible').setEnabled(ss.vertices.length == 1 &&
+ (graph.model.getChildCount(ss.vertices[0]) > 0 ||
+ graph.isContainer(ss.vertices[0])));
+ this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null);
+ this.actions.get('home').setEnabled(graph.view.currentRoot != null);
+ this.actions.get('enterGroup').setEnabled(ss.cells.length == 1 &&
+ graph.isValidRoot(ss.cells[0]));
+ this.actions.get('copyData').setEnabled(ss.cells.length == 1);
+ this.actions.get('editLink').setEnabled(ss.cells.length == 1);
+ this.actions.get('editStyle').setEnabled(ss.cells.length == 1);
+ this.actions.get('editTooltip').setEnabled(ss.cells.length == 1);
+ this.actions.get('openLink').setEnabled(ss.cells.length == 1 &&
+ graph.getLinkForCell(ss.cells[0]) != null);
+ this.actions.get('guides').setEnabled(graph.isEnabled());
+ this.actions.get('selectVertices').setEnabled(unlocked);
+ this.actions.get('selectEdges').setEnabled(unlocked);
+ this.actions.get('selectAll').setEnabled(unlocked);
+ this.actions.get('selectNone').setEnabled(unlocked);
+
+ var foldable = ss.vertices.length == 1 &&
+ graph.isCellFoldable(ss.vertices[0]);
+ this.actions.get('expand').setEnabled(foldable);
+ this.actions.get('collapse').setEnabled(foldable);
+
+ // Updates menu states
+ this.menus.get('navigation').setEnabled(ss.cells.length > 0 ||
+ graph.view.currentRoot != null);
+ this.menus.get('layout').setEnabled(unlocked);
+ this.menus.get('insert').setEnabled(unlocked);
+ this.menus.get('direction').setEnabled(ss.unlocked &&
+ ss.vertices.length == 1);
+ this.menus.get('distribute').setEnabled(ss.unlocked &&
+ ss.vertices.length > 1);
+ this.menus.get('align').setEnabled(ss.unlocked &&
+ ss.cells.length > 0);
+
+ this.updatePasteActionStates();
+};
+
+EditorUi.prototype.zeroOffset = new mxPoint(0, 0);
+
+EditorUi.prototype.getDiagramContainerOffset = function () {
+ return this.zeroOffset;
+};
+
+/**
+ * Refreshes the viewport.
+ */
+EditorUi.prototype.refresh = function (sizeDidChange) {
+ sizeDidChange = (sizeDidChange != null) ? sizeDidChange : true;
+
+ var w = this.container.clientWidth;
+ var h = this.container.clientHeight;
+
+ if (this.container == document.body) {
+ w = document.body.clientWidth || document.documentElement.clientWidth;
+ h = document.documentElement.clientHeight;
+ }
+
+ // Workaround for bug on iOS see
+ // http://stackoverflow.com/questions/19012135/ios-7-ipad-safari-landscape-innerheight-outerheight-layout-issue
+ // FIXME: Fix if footer visible
+ var off = 0;
+
+ if (mxClient.IS_IOS && !window.navigator.standalone && typeof Menus !== 'undefined') {
+ if (window.innerHeight != document.documentElement.clientHeight) {
+ off = document.documentElement.clientHeight - window.innerHeight;
+ window.scrollTo(0, 0);
+ }
+ }
+
+ var effHsplitPosition = Math.max(0, Math.min(
+ this.hsplitPosition, w - this.splitSize - 40));
+ var tmp = 0;
+
+ if (this.menubar != null) {
+ this.menubarContainer.style.height = this.menubarHeight + 'px';
+ tmp += this.menubarHeight;
+ }
+
+ if (this.toolbar != null) {
+ this.toolbarContainer.style.top = this.menubarHeight + 'px';
+ this.toolbarContainer.style.height = this.toolbarHeight + 'px';
+ tmp += this.toolbarHeight;
+ }
+
+ if (tmp > 0) {
+ tmp += 1;
+ }
+
+ var fw = (this.format != null) ? this.formatWidth : 0;
+ this.sidebarContainer.style.top = tmp + 'px';
+ this.sidebarContainer.style.width = effHsplitPosition + 'px';
+ this.formatContainer.style.top = tmp + 'px';
+ this.formatContainer.style.width = fw + 'px';
+ this.formatContainer.style.display = (this.format != null) ? '' : 'none';
+
+ var diagContOffset = this.getDiagramContainerOffset();
+ var contLeft = (this.hsplit.parentNode != null) ? (effHsplitPosition) : 0;
+ this.footerContainer.style.height = this.footerHeight + 'px';
+ this.hsplit.style.top = this.sidebarContainer.style.top;
+ this.hsplit.style.left = effHsplitPosition + 'px';
+ this.footerContainer.style.display = (this.footerHeight == 0) ? 'none' : '';
+
+ if (this.tabContainer != null) {
+ this.tabContainer.style.left = contLeft + 'px';
+ this.hsplit.style.bottom = this.tabContainer.offsetHeight + 'px';
+ }
+ else {
+ this.hsplit.style.bottom = (this.footerHeight + off) + 'px';
+ }
+
+ if (this.footerHeight > 0) {
+ this.footerContainer.style.bottom = off + 'px';
+ }
+
+ var th = 0;
+
+ if (this.tabContainer != null) {
+ this.tabContainer.style.bottom = (this.footerHeight + off) + 'px';
+ this.tabContainer.style.right = fw + 'px';
+ th = this.tabContainer.clientHeight;
+ this.checkTabScrollerOverflow();
+ }
+
+ this.sidebarContainer.style.bottom = (this.footerHeight + off) + 'px';
+ this.formatContainer.style.bottom = (this.footerHeight + off) + 'px';
+
+ this.diagramContainer.style.left = (contLeft + diagContOffset.x) + 'px';
+ this.diagramContainer.style.top = (tmp + diagContOffset.y) + 'px';
+ this.diagramContainer.style.right = fw + 'px';
+ this.diagramContainer.style.bottom = (this.footerHeight + off + th) + 'px';
+
+ if (sizeDidChange) {
+ this.editor.graph.sizeDidChange();
+ }
+};
+
+/**
+ * Creates the required containers.
+ */
+EditorUi.prototype.createTabContainer = function () {
+ return null;
+};
+
+/**
+ * Creates the required containers.
+ */
+EditorUi.prototype.createDivs = function () {
+ this.menubarContainer = this.createDiv('geMenubarContainer');
+ this.toolbarContainer = this.createDiv('geToolbarContainer');
+ this.sidebarContainer = this.createDiv('geSidebarContainer');
+ this.formatContainer = this.createDiv('geSidebarContainer geFormatContainer');
+ this.diagramContainer = this.createDiv('geDiagramContainer');
+ this.footerContainer = this.createDiv('geFooterContainer');
+ this.hsplit = this.createDiv('geHsplit');
+
+ // Sets static style for containers
+ this.menubarContainer.style.top = '0px';
+ this.menubarContainer.style.left = '0px';
+ this.menubarContainer.style.right = '0px';
+ this.toolbarContainer.style.left = '0px';
+ this.toolbarContainer.style.right = '0px';
+ this.sidebarContainer.style.left = '0px';
+ this.sidebarContainer.style.zIndex = '1';
+ this.formatContainer.style.right = '0px';
+ this.formatContainer.style.zIndex = '1';
+ this.diagramContainer.style.right = ((this.format != null) ? this.formatWidth : 0) + 'px';
+ this.footerContainer.style.left = '0px';
+ this.footerContainer.style.right = '0px';
+ this.footerContainer.style.bottom = '0px';
+ this.footerContainer.style.zIndex = mxPopupMenu.prototype.zIndex - 3;
+ this.hsplit.style.width = this.splitSize + 'px';
+ this.hsplit.style.zIndex = '1';
+
+ if (!this.editor.chromeless) {
+ this.tabContainer = this.createTabContainer();
+ }
+ else {
+ this.diagramContainer.style.border = 'none';
+ }
+};
+
+/**
+ * Hook for sidebar footer container. This implementation returns null.
+ */
+EditorUi.prototype.createSidebarContainer = function () {
+ var div = document.createElement('div');
+ div.className = 'geSidebarContainer';
+
+ return div;
+};
+
+/**
+ * Creates the required containers.
+ */
+EditorUi.prototype.createUi = function () {
+ // Creates menubar
+ this.menubar = (this.editor.chromeless) ? null : this.menus.createMenubar(this.createDiv('geMenubar'));
+
+ if (this.menubar != null) {
+ this.menubarContainer.appendChild(this.menubar.container);
+ }
+
+ // Adds status bar in menubar
+ if (this.menubar != null) {
+ this.statusContainer = this.createStatusContainer();
+
+ // Connects the status bar to the editor status
+ this.editor.addListener('statusChanged', mxUtils.bind(this, function () {
+ this.setStatusText(this.editor.getStatus());
+ }));
+
+ this.setStatusText(this.editor.getStatus());
+ this.menubar.container.appendChild(this.statusContainer);
+
+ // Inserts into DOM
+ this.container.appendChild(this.menubarContainer);
+ }
+
+ // Creates the sidebar
+ this.sidebar = (this.editor.chromeless) ? null : this.createSidebar(this.sidebarContainer);
+
+ if (this.sidebar != null) {
+ this.container.appendChild(this.sidebarContainer);
+ }
+
+ // Creates the format sidebar
+ this.format = (this.editor.chromeless || !this.formatEnabled) ? null : this.createFormat(this.formatContainer);
+
+ if (this.format != null) {
+ this.container.appendChild(this.formatContainer);
+ }
+
+ // Creates the footer
+ var footer = (this.editor.chromeless) ? null : this.createFooter();
+
+ if (footer != null) {
+ this.footerContainer.appendChild(footer);
+ this.container.appendChild(this.footerContainer);
+ }
+
+ this.container.appendChild(this.diagramContainer);
+
+ if (this.container != null && this.tabContainer != null) {
+ this.container.appendChild(this.tabContainer);
+ }
+
+ // Creates toolbar
+ this.toolbar = (this.editor.chromeless) ? null : this.createToolbar(this.createDiv('geToolbar'));
+
+ if (this.toolbar != null) {
+ this.toolbarContainer.appendChild(this.toolbar.container);
+ this.container.appendChild(this.toolbarContainer);
+ }
+
+ // HSplit
+ if (this.sidebar != null) {
+ this.container.appendChild(this.hsplit);
+
+ this.addSplitHandler(this.hsplit, true, 0, mxUtils.bind(this, function (value) {
+ this.hsplitPosition = value;
+ this.refresh();
+ }));
+ }
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createStatusContainer = function () {
+ var container = document.createElement('a');
+ container.className = 'geItem geStatus';
+
+ // Handles data-action attribute
+ mxEvent.addListener(container, 'click', mxUtils.bind(this, function (evt) {
+ var elt = mxEvent.getSource(evt);
+
+ if (elt.nodeName != 'A') {
+ var name = elt.getAttribute('data-action');
+
+ // Make generic
+ if (name == 'statusFunction' && this.editor.statusFunction != null) {
+ this.editor.statusFunction();
+ }
+ else if (name != null) {
+ var action = this.actions.get(name);
+
+ if (action != null) {
+ action.funct();
+ }
+ }
+ else {
+ var title = elt.getAttribute('data-title');
+ var msg = elt.getAttribute('data-message');
+
+ if (title != null && msg != null) {
+ this.showError(title, msg);
+ }
+ else {
+ var link = elt.getAttribute('data-link');
+
+ if (link != null) {
+ this.editor.graph.openLink(link);
+ }
+ }
+ }
+
+ mxEvent.consume(evt);
+ }
+ }));
+
+ return container;
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.setStatusText = function (value) {
+ this.statusContainer.innerHTML = Graph.sanitizeHtml(value);
+
+ // Wraps simple status messages in a div for styling
+ if (this.statusContainer.getElementsByTagName('div').length == 0 &&
+ value != null && value.length > 0) {
+ this.statusContainer.innerText = '';
+ var div = this.createStatusDiv(value);
+ this.statusContainer.appendChild(div);
+ }
+
+ // Handles data-effect attribute
+ var spans = this.statusContainer.querySelectorAll('[data-effect="fade"]');
+
+ if (spans != null) {
+ for (var i = 0; i < spans.length; i++) {
+ (function (temp) {
+ mxUtils.setOpacity(temp, 0);
+ mxUtils.setPrefixedStyle(temp.style, 'transform', 'scaleX(0)');
+ mxUtils.setPrefixedStyle(temp.style, 'transition', 'all 0.2s ease');
+
+ window.setTimeout(mxUtils.bind(this, function () {
+ mxUtils.setOpacity(temp, 100);
+ mxUtils.setPrefixedStyle(temp.style, 'transform', 'scaleX(1)');
+ mxUtils.setPrefixedStyle(temp.style, 'transition', 'all 1s ease');
+
+ window.setTimeout(mxUtils.bind(this, function () {
+ mxUtils.setPrefixedStyle(temp.style, 'transform', 'scaleX(0)');
+ mxUtils.setOpacity(temp, 0);
+
+ window.setTimeout(mxUtils.bind(this, function () {
+ if (temp.parentNode != null) {
+ temp.parentNode.removeChild(temp);
+ }
+ }), 1000);
+ }), Editor.updateStatusInterval / 2);
+ }), 0);
+ })(spans[i]);
+ }
+ }
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createStatusDiv = function (value) {
+ var div = document.createElement('div');
+ div.style.textOverflow = 'ellipsis';
+ div.style.display = 'inline-block';
+ div.style.whiteSpace = 'nowrap';
+ div.style.overflow = 'hidden';
+ div.style.minWidth = '0';
+
+ div.setAttribute('title', value);
+ div.innerHTML = Graph.sanitizeHtml(value);
+
+ return div;
+};
+
+/**
+ * Creates a new toolbar for the given container.
+ */
+EditorUi.prototype.createToolbar = function (container) {
+ return new Toolbar(this, container);
+};
+
+/**
+ * Creates a new sidebar for the given container.
+ */
+EditorUi.prototype.createSidebar = function (container) {
+ return new Sidebar(this, container);
+};
+
+/**
+ * Creates a new sidebar for the given container.
+ */
+EditorUi.prototype.createFormat = function (container) {
+ return new Format(this, container);
+};
+
+/**
+ * Creates and returns a new footer.
+ */
+EditorUi.prototype.createFooter = function () {
+ return this.createDiv('geFooter');
+};
+
+/**
+ * Creates the actual toolbar for the toolbar container.
+ */
+EditorUi.prototype.createDiv = function (classname) {
+ var elt = document.createElement('div');
+ elt.className = classname;
+
+ return elt;
+};
+
+/**
+ * Updates the states of the given undo/redo items.
+ */
+EditorUi.prototype.addSplitHandler = function (elt, horizontal, dx, onChange) {
+ var start = null;
+ var initial = null;
+ var ignoreClick = true;
+ var last = null;
+
+ // Disables built-in pan and zoom in IE10 and later
+ if (mxClient.IS_POINTER) {
+ elt.style.touchAction = 'none';
+ }
+
+ var getValue = mxUtils.bind(this, function () {
+ var result = parseInt(((horizontal) ? elt.style.left : elt.style.bottom));
+
+ // Takes into account hidden footer
+ if (!horizontal) {
+ result = result + dx - this.footerHeight;
+ }
+
+ return result;
+ });
+
+ function moveHandler(evt) {
+ if (start != null) {
+ var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ onChange(Math.max(0, initial + ((horizontal) ? (pt.x - start.x) : (start.y - pt.y)) - dx));
+ mxEvent.consume(evt);
+
+ if (initial != getValue()) {
+ ignoreClick = true;
+ last = null;
+ }
+ }
+ };
+
+ function dropHandler(evt) {
+ moveHandler(evt);
+ initial = null;
+ start = null;
+ };
+
+ mxEvent.addGestureListeners(elt, function (evt) {
+ start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ initial = getValue();
+ ignoreClick = false;
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(elt, 'click', mxUtils.bind(this, function (evt) {
+ if (!ignoreClick && this.hsplitClickEnabled) {
+ var next = (last != null) ? last - dx : 0;
+ last = getValue();
+ onChange(next);
+ mxEvent.consume(evt);
+ }
+ }));
+
+ mxEvent.addGestureListeners(document, null, moveHandler, dropHandler);
+
+ this.destroyFunctions.push(function () {
+ mxEvent.removeGestureListeners(document, null, moveHandler, dropHandler);
+ });
+};
+
+/**
+ * Translates this point by the given vector.
+ *
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+EditorUi.prototype.prompt = function (title, defaultValue, fn) {
+ var dlg = new FilenameDialog(this, defaultValue, mxResources.get('apply'), function (newValue) {
+ fn(parseFloat(newValue));
+ }, title);
+
+ this.showDialog(dlg.container, 300, 80, true, true);
+ dlg.init();
+};
+
+/**
+ * Translates this point by the given vector.
+ *
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+EditorUi.prototype.handleError = function (resp, title, fn, invokeFnOnClose, notFoundMessage) {
+ var e = (resp != null && resp.error != null) ? resp.error : resp;
+
+ if (e != null || title != null) {
+ var msg = mxUtils.htmlEntities(mxResources.get('unknownError'));
+ var btn = mxResources.get('ok');
+ title = (title != null) ? title : mxResources.get('error');
+
+ if (e != null && e.message != null) {
+ msg = mxUtils.htmlEntities(e.message);
+ }
+
+ this.showError(title, msg, btn, fn, null, null, null, null, null,
+ null, null, null, (invokeFnOnClose) ? fn : null);
+ }
+ else if (fn != null) {
+ fn();
+ }
+};
+
+/**
+ * Translates this point by the given vector.
+ *
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+EditorUi.prototype.showError = function (title, msg, btn, fn, retry, btn2, fn2, btn3, fn3, w, h, hide, onClose) {
+ var dlg = new ErrorDialog(this, title, msg, btn || mxResources.get('ok'),
+ fn, retry, btn2, fn2, hide, btn3, fn3);
+ var lines = Math.ceil((msg != null) ? msg.length / 50 : 1);
+ this.showDialog(dlg.container, w || 340, h || (100 + lines * 20), true, false, onClose);
+ dlg.init();
+};
+
+/**
+ * Displays a print dialog.
+ */
+EditorUi.prototype.showDialog = function (elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, ignoreBgClick) {
+ this.editor.graph.tooltipHandler.resetTimer();
+ this.editor.graph.tooltipHandler.hideTooltip();
+
+ if (this.dialogs == null) {
+ this.dialogs = [];
+ }
+
+ this.dialog = new Dialog(this, elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, ignoreBgClick);
+ this.dialogs.push(this.dialog);
+};
+
+/**
+ * Displays a print dialog.
+ */
+EditorUi.prototype.hideDialog = function (cancel, isEsc, matchContainer) {
+ if (this.dialogs != null && this.dialogs.length > 0) {
+ if (matchContainer != null && matchContainer != this.dialog.container.firstChild) {
+ return;
+ }
+
+ var dlg = this.dialogs.pop();
+
+ if (dlg.close(cancel, isEsc) == false) {
+ //add the dialog back if dialog closing is cancelled
+ this.dialogs.push(dlg);
+ return;
+ }
+
+ this.dialog = (this.dialogs.length > 0) ? this.dialogs[this.dialogs.length - 1] : null;
+ this.editor.fireEvent(new mxEventObject('hideDialog'));
+
+ if (this.dialog == null && this.editor.graph.container != null &&
+ this.editor.graph.container.style.visibility != 'hidden') {
+ window.setTimeout(mxUtils.bind(this, function () {
+ if (this.editor != null && (this.dialogs == null || this.dialogs.length == 0)) {
+ if (this.editor.graph.isEditing() && this.editor.graph.cellEditor.textarea != null) {
+ this.editor.graph.cellEditor.textarea.focus();
+ }
+ else {
+ mxUtils.clearSelection();
+ this.editor.graph.container.focus();
+ }
+ }
+ }), 0);
+ }
+ }
+};
+
+/**
+ * Handles ctrl+enter keystroke to clone cells.
+ */
+EditorUi.prototype.ctrlEnter = function () {
+ var graph = this.editor.graph;
+
+ if (graph.isEnabled()) {
+ try {
+ var cells = graph.getSelectionCells();
+ var lookup = new mxDictionary();
+ var newCells = [];
+
+ for (var i = 0; i < cells.length; i++) {
+ // Clones table rows instead of cells
+ var cell = (graph.isTableCell(cells[i])) ? graph.model.getParent(cells[i]) : cells[i];
+
+ if (cell != null && !lookup.get(cell)) {
+ lookup.put(cell, true);
+ newCells.push(cell);
+ }
+ }
+
+ graph.setSelectionCells(graph.duplicateCells(newCells, false));
+ }
+ catch (e) {
+ this.handleError(e);
+ }
+ }
+};
+
+/**
+ * Display a color dialog.
+ */
+EditorUi.prototype.pickColor = function (color, apply) {
+ var graph = this.editor.graph;
+ var selState = graph.cellEditor.saveSelection();
+ var h = 230 + ((Math.ceil(ColorDialog.prototype.presetColors.length / 12) +
+ Math.ceil(ColorDialog.prototype.defaultColors.length / 12)) * 17);
+
+ var dlg = new ColorDialog(this, mxUtils.rgba2hex(color) || 'none', function (color) {
+ graph.cellEditor.restoreSelection(selState);
+ apply(color);
+ }, function () {
+ graph.cellEditor.restoreSelection(selState);
+ });
+
+ this.showDialog(dlg.container, 230, h, true, false);
+ dlg.init();
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+EditorUi.prototype.openFile = function () {
+ // Closes dialog after open
+ window.openFile = new OpenFile(mxUtils.bind(this, function (cancel) {
+ this.hideDialog(cancel);
+ }));
+
+ // Removes openFile if dialog is closed
+ this.showDialog(new OpenDialog(this).container, (Editor.useLocalStorage) ? 640 : 320,
+ (Editor.useLocalStorage) ? 480 : 220, true, true, function () {
+ window.openFile = null;
+ });
+};
+
+/**
+ * Extracs the graph model from the given HTML data from a data transfer event.
+ */
+EditorUi.prototype.extractGraphModelFromHtml = function (data) {
+ var result = null;
+
+ try {
+ var idx = data.indexOf('<mxGraphModel ');
+
+ if (idx >= 0) {
+ var idx2 = data.lastIndexOf('</mxGraphModel>');
+
+ if (idx2 > idx) {
+ result = data.substring(idx, idx2 + 21).replace(/>/g, '>').
+ replace(/</g, '<').replace(/\\"/g, '"').replace(/\n/g, '');
+ }
+ }
+ }
+ catch (e) {
+ // ignore
+ }
+
+ return result;
+};
+
+/**
+ * Opens the given files in the editor.
+ */
+EditorUi.prototype.readGraphModelFromClipboard = function (fn) {
+ this.readGraphModelFromClipboardWithType(mxUtils.bind(this, function (xml) {
+ if (xml != null) {
+ fn(xml);
+ }
+ else {
+ this.readGraphModelFromClipboardWithType(mxUtils.bind(this, function (xml) {
+ if (xml != null) {
+ var tmp = decodeURIComponent(xml);
+
+ if (this.isCompatibleString(tmp)) {
+ xml = tmp;
+ }
+ }
+
+ fn(xml);
+ }), 'text');
+ }
+ }), 'html');
+};
+
+/**
+ * Opens the given files in the editor.
+ */
+EditorUi.prototype.readGraphModelFromClipboardWithType = function (fn, type) {
+ navigator.clipboard.read().then(mxUtils.bind(this, function (data) {
+ if (data != null && data.length > 0 && type == 'html' &&
+ mxUtils.indexOf(data[0].types, 'text/html') >= 0) {
+ data[0].getType('text/html').then(mxUtils.bind(this, function (blob) {
+ blob.text().then(mxUtils.bind(this, function (value) {
+ try {
+ var elt = this.parseHtmlData(value);
+ var asHtml = elt.getAttribute('data-type') != 'text/plain';
+
+ // KNOWN: Paste from IE11 to other browsers on Windows
+ // seems to paste the contents of index.html
+ var xml = (asHtml) ? elt.innerHTML :
+ mxUtils.trim((elt.innerText == null) ?
+ mxUtils.getTextContent(elt) : elt.innerText);
+
+ // Workaround for junk after XML in VM
+ try {
+ var idx = xml.lastIndexOf('%3E');
+
+ if (idx >= 0 && idx < xml.length - 3) {
+ xml = xml.substring(0, idx + 3);
+ }
+ }
+ catch (e) {
+ // ignore
+ }
+
+ // Checks for embedded XML content
+ try {
+ var spans = elt.getElementsByTagName('span');
+ var tmp = (spans != null && spans.length > 0) ?
+ mxUtils.trim(decodeURIComponent(spans[0].textContent)) :
+ decodeURIComponent(xml);
+
+ if (this.isCompatibleString(tmp)) {
+ xml = tmp;
+ }
+ }
+ catch (e) {
+ // ignore
+ }
+ }
+ catch (e) {
+ // ignore
+ }
+
+ fn(this.isCompatibleString(xml) ? xml : null);
+ }))['catch'](function (data) {
+ fn(null);
+ });
+ }))['catch'](function (data) {
+ fn(null);
+ });
+ }
+ else if (data != null && data.length > 0 && type == 'text' &&
+ mxUtils.indexOf(data[0].types, 'text/plain') >= 0) {
+ data[0].getType('text/plain').then(function (blob) {
+ blob.text().then(function (value) {
+ fn(value);
+ })['catch'](function () {
+ fn(null);
+ });
+ })['catch'](function () {
+ fn(null);
+ });
+ }
+ else {
+ fn(null);
+ }
+ }))['catch'](function (data) {
+ fn(null);
+ });
+};
+
+/**
+ * Parses the given HTML data and returns a DIV.
+ */
+EditorUi.prototype.parseHtmlData = function (data) {
+ var elt = null;
+
+ if (data != null && data.length > 0) {
+ var hasMeta = data.substring(0, 6) == '' : '') +
+ Graph.sanitizeHtml(data);
+ asHtml = true;
+
+ // Workaround for innerText not ignoring style elements in Chrome
+ var styles = elt.getElementsByTagName('style');
+
+ if (styles != null) {
+ while (styles.length > 0) {
+ styles[0].parentNode.removeChild(styles[0]);
+ }
+ }
+
+ // Special case of link pasting from Chrome
+ if (elt.firstChild != null && elt.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT &&
+ elt.firstChild.nextSibling != null && elt.firstChild.nextSibling.nodeType == mxConstants.NODETYPE_ELEMENT &&
+ elt.firstChild.nodeName == 'META' && elt.firstChild.nextSibling.nodeName == 'A' &&
+ elt.firstChild.nextSibling.nextSibling == null) {
+ var temp = (elt.firstChild.nextSibling.innerText == null) ?
+ mxUtils.getTextContent(elt.firstChild.nextSibling) :
+ elt.firstChild.nextSibling.innerText;
+
+ if (temp == elt.firstChild.nextSibling.getAttribute('href')) {
+ mxUtils.setTextContent(elt, temp);
+ asHtml = false;
+ }
+ }
+
+ // Extracts single image source address with meta tag in markup
+ var img = (hasMeta && elt.firstChild != null) ? elt.firstChild.nextSibling : elt.firstChild;
+
+ if (img != null && img.nextSibling == null &&
+ img.nodeType == mxConstants.NODETYPE_ELEMENT &&
+ img.nodeName == 'IMG') {
+ var temp = img.getAttribute('src');
+
+ if (temp != null) {
+ if (Editor.isPngDataUrl(temp)) {
+ var xml = Editor.extractGraphModelFromPng(temp);
+
+ if (xml != null && xml.length > 0) {
+ temp = xml;
+ }
+ }
+
+ mxUtils.setTextContent(elt, temp);
+ asHtml = false;
+ }
+ }
+ else {
+ // Extracts embedded XML or image source address from single PNG image
+ var images = elt.getElementsByTagName('img');
+
+ if (images.length == 1) {
+ var img = images[0];
+ var temp = img.getAttribute('src');
+
+ if (temp != null && img.parentNode == elt && elt.children.length == 1) {
+ if (Editor.isPngDataUrl(temp)) {
+ var xml = Editor.extractGraphModelFromPng(temp);
+
+ if (xml != null && xml.length > 0) {
+ temp = xml;
+ }
+ }
+
+ mxUtils.setTextContent(elt, temp);
+ asHtml = false;
+ }
+ }
+ }
+
+ if (asHtml) {
+ Graph.removePasteFormatting(elt);
+ }
+ }
+
+ if (!asHtml) {
+ elt.setAttribute('data-type', 'text/plain');
+ }
+
+ return elt;
+};
+
+/**
+ * Opens the given files in the editor.
+ */
+EditorUi.prototype.extractGraphModelFromEvent = function (evt) {
+ var result = null;
+ var data = null;
+
+ if (evt != null) {
+ var provider = (evt.dataTransfer != null) ?
+ evt.dataTransfer : evt.clipboardData;
+
+ if (provider != null) {
+ if (document.documentMode == 10 || document.documentMode == 11) {
+ data = provider.getData('Text');
+ }
+ else {
+ data = (mxUtils.indexOf(provider.types, 'text/html') >= 0) ?
+ provider.getData('text/html') : null;
+
+ if (mxUtils.indexOf(provider.types, 'text/plain') >= 0 &&
+ (data == null || data.length == 0)) {
+ data = provider.getData('text/plain');
+ }
+ }
+
+ if (data != null) {
+ data = Graph.zapGremlins(mxUtils.trim(data));
+
+ // Tries parsing as HTML document with embedded XML
+ var xml = this.extractGraphModelFromHtml(data);
+
+ if (xml != null) {
+ data = xml;
+ }
+ }
+ }
+ }
+
+ if (data != null && this.isCompatibleString(data)) {
+ result = data;
+ }
+
+ return result;
+};
+
+/**
+ * Hook for subclassers to return true if event data is a supported format.
+ * This implementation always returns false.
+ */
+EditorUi.prototype.isCompatibleString = function (data) {
+ return false;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+EditorUi.prototype.saveFile = function (forceDialog) {
+ if (!forceDialog && this.editor.filename != null) {
+ this.save(this.editor.getOrCreateFilename());
+ }
+ else {
+ var dlg = new FilenameDialog(this, this.editor.getOrCreateFilename(), mxResources.get('save'), mxUtils.bind(this, function (name) {
+ this.save(name);
+ }), null, mxUtils.bind(this, function (name) {
+ if (name != null && name.length > 0) {
+ return true;
+ }
+
+ mxUtils.confirm(mxResources.get('invalidName'));
+
+ return false;
+ }));
+ this.showDialog(dlg.container, 300, 100, true, true);
+ dlg.init();
+ }
+};
+
+/**
+ * Saves the current graph under the given filename.
+ */
+EditorUi.prototype.save = function (name) {
+ if (name != null) {
+ if (this.editor.graph.isEditing()) {
+ this.editor.graph.stopEditing();
+ }
+
+ var xml = mxUtils.getXml(this.editor.getGraphXml());
+
+ try {
+ if (Editor.useLocalStorage) {
+ if (localStorage.getItem(name) != null &&
+ !mxUtils.confirm(mxResources.get('replaceIt', [name]))) {
+ return;
+ }
+
+ localStorage.setItem(name, xml);
+ this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('saved')) + ' ' + new Date());
+ }
+ else {
+ if (xml.length < MAX_REQUEST_SIZE) {
+ new mxXmlRequest(SAVE_URL, 'filename=' + encodeURIComponent(name) +
+ '&xml=' + encodeURIComponent(xml)).simulate(document, '_blank');
+ }
+ else {
+ mxUtils.alert(mxResources.get('drawingTooLarge'));
+ mxUtils.popup(xml);
+
+ return;
+ }
+ }
+
+ this.editor.setModified(false);
+ this.editor.setFilename(name);
+ this.updateDocumentTitle();
+ }
+ catch (e) {
+ this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('errorSavingFile')));
+ }
+ }
+};
+
+/**
+ * Executes the given array of graph layouts using executeLayout and
+ * calls done after the last layout has finished.
+ */
+EditorUi.prototype.executeLayouts = function (layouts, post) {
+ this.executeLayout(mxUtils.bind(this, function () {
+ var layout = new mxCompositeLayout(this.editor.graph, layouts);
+ var cells = this.editor.graph.getSelectionCells();
+
+ layout.execute(this.editor.graph.getDefaultParent(),
+ cells.length == 0 ? null : cells);
+ }), true, post);
+};
+
+/**
+ * Executes the given layout.
+ */
+EditorUi.prototype.executeLayout = function (exec, animate, post) {
+ var graph = this.editor.graph;
+ graph.getModel().beginUpdate();
+ try {
+ exec();
+ }
+ catch (e) {
+ throw e;
+ }
+ finally {
+ // Animates the changes in the graph model
+ if (this.allowAnimation && animate && graph.isEnabled()) {
+ // New API for animating graph layout results asynchronously
+ var morph = new mxMorphing(graph);
+ morph.addListener(mxEvent.DONE, mxUtils.bind(this, function () {
+ graph.getModel().endUpdate();
+
+ if (post != null) {
+ post();
+ }
+ }));
+
+ morph.startAnimation();
+ }
+ else {
+ graph.getModel().endUpdate();
+
+ if (post != null) {
+ post();
+ }
+ }
+ }
+};
+
+/**
+ * Hides the current menu.
+ */
+EditorUi.prototype.showImageDialog = function (title, value, fn, ignoreExisting) {
+ var cellEditor = this.editor.graph.cellEditor;
+ var selState = cellEditor.saveSelection();
+ var newValue = mxUtils.prompt(title, value);
+ cellEditor.restoreSelection(selState);
+
+ if (newValue != null && newValue.length > 0) {
+ var img = new Image();
+
+ img.onload = function () {
+ fn(newValue, img.width, img.height);
+ };
+ img.onerror = function () {
+ fn(null);
+ mxUtils.alert(mxResources.get('fileNotFound'));
+ };
+
+ img.src = newValue;
+ }
+ else {
+ fn(null);
+ }
+};
+
+/**
+ * Hides the current menu.
+ */
+EditorUi.prototype.showLinkDialog = function (value, btnLabel, fn) {
+ var dlg = new LinkDialog(this, value, btnLabel, fn);
+ this.showDialog(dlg.container, 420, 90, true, true);
+ dlg.init();
+};
+
+/**
+ * Hides the current menu.
+ */
+EditorUi.prototype.showDataDialog = function (cell) {
+ if (cell != null && typeof window.EditDataDialog !== 'undefined') {
+ var dlg = new EditDataDialog(this, cell);
+ this.showDialog(dlg.container, 480, 420, true, false, null, false);
+ dlg.init();
+ }
+};
+
+/**
+ * Hides the current menu.
+ */
+EditorUi.prototype.showBackgroundImageDialog = function (apply, img) {
+ apply = (apply != null) ? apply : mxUtils.bind(this, function (image) {
+ var change = new ChangePageSetup(this, null, image);
+ change.ignoreColor = true;
+
+ this.editor.graph.model.execute(change);
+ });
+
+ var newValue = mxUtils.prompt(mxResources.get('backgroundImage'), (img != null) ? img.src : '');
+
+ if (newValue != null && newValue.length > 0) {
+ var img = new Image();
+
+ img.onload = function () {
+ apply(new mxImage(newValue, img.width, img.height), false);
+ };
+ img.onerror = function () {
+ apply(null, true);
+ mxUtils.alert(mxResources.get('fileNotFound'));
+ };
+
+ img.src = newValue;
+ }
+ else {
+ apply(null);
+ }
+};
+
+/**
+ * Loads the stylesheet for this graph.
+ */
+EditorUi.prototype.setBackgroundImage = function (image) {
+ this.editor.graph.setBackgroundImage(image);
+ this.editor.graph.view.validateBackgroundImage();
+
+ this.fireEvent(new mxEventObject('backgroundImageChanged'));
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+EditorUi.prototype.confirm = function (msg, okFn, cancelFn) {
+ if (mxUtils.confirm(msg)) {
+ if (okFn != null) {
+ okFn();
+ }
+ }
+ else if (cancelFn != null) {
+ cancelFn();
+ }
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+EditorUi.prototype.createOutline = function (wnd) {
+ var outline = new mxOutline(this.editor.graph);
+
+ mxEvent.addListener(window, 'resize', function () {
+ outline.update(false);
+ });
+
+ return outline;
+};
+
+// Alt+Shift+Keycode mapping to action
+EditorUi.prototype.altShiftActions = {
+ 65: 'connectionArrows', // Alt+Shift+A
+ 82: 'clearWaypoints', // Alt+Shift+R
+ 76: 'editLink', // Alt+Shift+L
+ 79: 'connectionPoints', // Alt+Shift+O
+ 81: 'editConnectionPoints', // Alt+Shift+Q
+ 84: 'editTooltip', // Alt+Shift+T
+ 86: 'pasteSize', // Alt+Shift+V
+ 70: 'copySize', // Alt+Shift+F
+ 66: 'copyData', // Alt+Shift+B
+ 69: 'pasteData' // Alt+Shift+E
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+EditorUi.prototype.createKeyHandler = function (editor) {
+ var editorUi = this;
+ var graph = this.editor.graph;
+ var keyHandler = new mxKeyHandler(graph);
+
+ var isEventIgnored = keyHandler.isEventIgnored;
+ keyHandler.isEventIgnored = function (evt) {
+ // Handles undo/redo/ctrl+./,/u via action and allows ctrl+b/i
+ // only if editing value is HTML (except for FF and Safari)
+ // 66, 73 are keycodes for editing actions like bold, italic
+ return !(mxEvent.isShiftDown(evt) && evt.keyCode == 9) &&
+ ((!this.isControlDown(evt) || mxEvent.isShiftDown(evt) ||
+ (evt.keyCode != 90 && evt.keyCode != 89 && evt.keyCode != 188 &&
+ evt.keyCode != 190 && evt.keyCode != 85)) && ((evt.keyCode != 66 && evt.keyCode != 73) ||
+ !this.isControlDown(evt) || (this.graph.cellEditor.isContentEditing() &&
+ !mxClient.IS_FF && !mxClient.IS_SF)) &&
+ ((evt.keyCode != 109 && evt.keyCode != 107) ||
+ (!this.isControlDown(evt) && !mxEvent.isShiftDown(evt)) ||
+ (!this.graph.cellEditor.isContentEditing() &&
+ !mxClient.IS_FF && !mxClient.IS_SF)) &&
+ isEventIgnored.apply(this, arguments));
+ };
+
+ // Ignores graph enabled state but not chromeless state
+ keyHandler.isEnabledForEvent = function (evt) {
+ return (!mxEvent.isConsumed(evt) && this.isGraphEvent(evt) && this.isEnabled() &&
+ (editorUi.dialogs == null || editorUi.dialogs.length == 0));
+ };
+
+ // Routes command-key to control-key on Mac
+ keyHandler.isControlDown = function (evt) {
+ return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey);
+ };
+
+ var thread = null;
+
+ // Helper function to move cells with the cursor keys
+ function nudge(keyCode, stepSize, resize) {
+ if (!graph.isSelectionEmpty() && graph.isEnabled()) {
+ stepSize = (stepSize != null) ? stepSize : 1;
+
+ var cells = graph.getCompositeParents(graph.getSelectionCells());
+ var cell = (cells.length > 0) ? cells[0] : null;
+
+ if (cell != null) {
+ if (resize) {
+ // Resizes all selected vertices
+ graph.getModel().beginUpdate();
+ try {
+ for (var i = 0; i < cells.length; i++) {
+ if (graph.getModel().isVertex(cells[i]) && graph.isCellResizable(cells[i])) {
+ var geo = graph.getCellGeometry(cells[i]);
+
+ if (geo != null) {
+ geo = geo.clone();
+
+ if (keyCode == 37) {
+ geo.width = Math.max(0, geo.width - stepSize);
+ }
+ else if (keyCode == 38) {
+ geo.height = Math.max(0, geo.height - stepSize);
+ }
+ else if (keyCode == 39) {
+ geo.width += stepSize;
+ }
+ else if (keyCode == 40) {
+ geo.height += stepSize;
+ }
+
+ graph.getModel().setGeometry(cells[i], geo);
+ }
+ }
+ }
+ }
+ finally {
+ graph.getModel().endUpdate();
+ }
+ }
+ else {
+ // Moves vertices up/down in a stack layout
+ var parent = graph.model.getParent(cell);
+ var scale = graph.getView().scale;
+ var layout = null;
+
+ if (graph.getSelectionCount() == 1 && graph.model.isVertex(cell) &&
+ graph.layoutManager != null && !graph.isCellLocked(cell)) {
+ layout = graph.layoutManager.getLayout(parent);
+ }
+
+ if (layout != null && layout.constructor == mxStackLayout) {
+ var index = parent.getIndex(cell);
+
+ if (keyCode == 37 || keyCode == 38) {
+ graph.model.add(parent, cell, Math.max(0, index - 1));
+ }
+ else if (keyCode == 39 || keyCode == 40) {
+ graph.model.add(parent, cell, Math.min(graph.model.getChildCount(parent), index + 1));
+ }
+ }
+ else {
+ var handler = graph.graphHandler;
+
+ if (handler != null) {
+ if (handler.first == null) {
+ handler.start(cell, 0, 0, graph.getMovableCells(cells));
+ }
+
+ if (handler.first != null) {
+ var dx = 0;
+ var dy = 0;
+
+ if (keyCode == 37) {
+ dx = -stepSize;
+ }
+ else if (keyCode == 38) {
+ dy = -stepSize;
+ }
+ else if (keyCode == 39) {
+ dx = stepSize;
+ }
+ else if (keyCode == 40) {
+ dy = stepSize;
+ }
+
+ handler.currentDx += dx * scale;
+ handler.currentDy += dy * scale;
+ handler.checkPreview();
+ handler.updatePreview();
+ }
+
+ // Groups move steps in undoable change
+ if (thread != null) {
+ window.clearTimeout(thread);
+ }
+
+ thread = window.setTimeout(function () {
+ if (handler.first != null) {
+ var dx = handler.roundLength(handler.currentDx / scale);
+ var dy = handler.roundLength(handler.currentDy / scale);
+ handler.moveCells(handler.cells, dx, dy);
+ handler.reset();
+ }
+ }, 400);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ // Overridden to handle special alt+shift+cursor keyboard shortcuts
+ var directions = {
+ 37: mxConstants.DIRECTION_WEST, 38: mxConstants.DIRECTION_NORTH,
+ 39: mxConstants.DIRECTION_EAST, 40: mxConstants.DIRECTION_SOUTH
+ };
+ var keyHandlerGetFunction = keyHandler.getFunction;
+
+ mxKeyHandler.prototype.getFunction = function (evt) {
+ if (graph.isEnabled()) {
+ // TODO: Add alt modifier state in core API, here are some specific cases
+ if (mxEvent.isShiftDown(evt) && mxEvent.isAltDown(evt)) {
+ var action = editorUi.actions.get(editorUi.altShiftActions[evt.keyCode]);
+
+ if (action != null) {
+ return action.funct;
+ }
+ }
+
+ if (directions[evt.keyCode] != null && !graph.isSelectionEmpty()) {
+ // On macOS, Control+Cursor is used by Expose so allow for Alt+Control to resize
+ if (!this.isControlDown(evt) && mxEvent.isShiftDown(evt) && mxEvent.isAltDown(evt)) {
+ if (graph.model.isVertex(graph.getSelectionCell())) {
+ return function () {
+ var cells = graph.connectVertex(graph.getSelectionCell(), directions[evt.keyCode],
+ graph.defaultEdgeLength, evt, true);
+
+ if (cells != null && cells.length > 0) {
+ if (cells.length == 1 && graph.model.isEdge(cells[0])) {
+ graph.setSelectionCell(graph.model.getTerminal(cells[0], false));
+ }
+ else {
+ graph.setSelectionCell(cells[cells.length - 1]);
+ }
+
+ graph.scrollCellToVisible(graph.getSelectionCell());
+
+ if (editorUi.hoverIcons != null) {
+ editorUi.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
+ }
+ }
+ };
+ }
+ }
+ else {
+ // Avoids consuming event if no vertex is selected by returning null below
+ // Cursor keys move and resize (ctrl) cells
+ if (this.isControlDown(evt)) {
+ return function () {
+ nudge(evt.keyCode, (mxEvent.isShiftDown(evt)) ? graph.gridSize : null, true);
+ };
+ }
+ else {
+ return function () {
+ nudge(evt.keyCode, (mxEvent.isShiftDown(evt)) ? graph.gridSize : null);
+ };
+ }
+ }
+ }
+ }
+
+ return keyHandlerGetFunction.apply(this, arguments);
+ };
+
+ // Binds keystrokes to actions
+ keyHandler.bindAction = mxUtils.bind(this, function (code, control, key, shift) {
+ var action = this.actions.get(key);
+
+ if (action != null) {
+ var f = function () {
+ if (action.isEnabled()) {
+ action.funct.apply(this, arguments);
+ }
+ };
+
+ if (control) {
+ if (shift) {
+ keyHandler.bindControlShiftKey(code, f);
+ }
+ else {
+ keyHandler.bindControlKey(code, f);
+ }
+ }
+ else {
+ if (shift) {
+ keyHandler.bindShiftKey(code, f);
+ }
+ else {
+ keyHandler.bindKey(code, f);
+ }
+ }
+ }
+ });
+
+ var ui = this;
+ var keyHandlerEscape = keyHandler.escape;
+ keyHandler.escape = function (evt) {
+ keyHandlerEscape.apply(this, arguments);
+ };
+
+ // Ignores enter keystroke. Remove this line if you want the
+ // enter keystroke to stop editing. N, W, T are reserved.
+ keyHandler.enter = function () { };
+
+ keyHandler.bindControlShiftKey(36, function () { graph.exitGroup(); }); // Ctrl+Shift+Home
+ keyHandler.bindControlShiftKey(35, function () { graph.enterGroup(); }); // Ctrl+Shift+End
+ keyHandler.bindShiftKey(36, function () { graph.home(); }); // Ctrl+Shift+Home
+ keyHandler.bindKey(35, function () { graph.refresh(); }); // End
+ keyHandler.bindAction(107, true, 'zoomIn'); // Ctrl+Plus
+ keyHandler.bindAction(109, true, 'zoomOut'); // Ctrl+Minus
+ keyHandler.bindAction(80, true, 'print'); // Ctrl+P
+
+ if (!this.editor.chromeless || this.editor.editable) {
+ keyHandler.bindAction(79, true, 'outline', true); // Ctrl+Shift+O
+ keyHandler.bindControlKey(36, function () { if (graph.isEnabled()) { graph.foldCells(true); } }); // Ctrl+Home
+ keyHandler.bindControlKey(35, function () { if (graph.isEnabled()) { graph.foldCells(false); } }); // Ctrl+End
+ keyHandler.bindControlKey(13, function () { ui.ctrlEnter(); }); // Ctrl+Enter
+ keyHandler.bindAction(8, false, 'delete'); // Backspace
+ keyHandler.bindAction(8, true, 'deleteAll'); // Ctrl+Backspace
+ keyHandler.bindAction(8, false, 'deleteLabels', true); // Shift+Backspace
+ keyHandler.bindAction(46, false, 'delete'); // Delete
+ keyHandler.bindAction(46, true, 'deleteAll'); // Ctrl+Delete
+ keyHandler.bindAction(46, false, 'deleteLabels', true); // Shift+Delete
+ keyHandler.bindAction(36, false, 'resetView'); // Home
+ keyHandler.bindAction(72, true, 'fitWindow', true); // Ctrl+Shift+H
+ keyHandler.bindAction(74, true, 'fitPage'); // Ctrl+J
+ keyHandler.bindAction(74, true, 'fitTwoPages', true); // Ctrl+Shift+J
+ keyHandler.bindAction(48, true, 'customZoom'); // Ctrl+0
+ keyHandler.bindAction(82, true, 'turn'); // Ctrl+R
+ keyHandler.bindAction(82, true, 'clearDefaultStyle', true); // Ctrl+Shift+R
+ keyHandler.bindAction(83, true, 'save'); // Ctrl+S
+ keyHandler.bindAction(83, true, 'saveAs', true); // Ctrl+Shift+S
+ keyHandler.bindAction(65, true, 'selectAll'); // Ctrl+A
+ keyHandler.bindAction(65, true, 'selectNone', true); // Ctrl+A
+ keyHandler.bindAction(73, true, 'selectVertices', true); // Ctrl+Shift+I
+ keyHandler.bindAction(69, true, 'selectEdges', true); // Ctrl+Shift+E
+ keyHandler.bindAction(69, true, 'editStyle'); // Ctrl+E
+ keyHandler.bindAction(66, true, 'bold'); // Ctrl+B
+ keyHandler.bindAction(66, true, 'toBack', true); // Ctrl+Shift+B
+ keyHandler.bindAction(70, true, 'toFront', true); // Ctrl+Shift+F
+ keyHandler.bindAction(68, true, 'duplicate'); // Ctrl+D
+ keyHandler.bindAction(68, true, 'setAsDefaultStyle', true); // Ctrl+Shift+D
+ keyHandler.bindAction(90, true, 'undo'); // Ctrl+Z
+ keyHandler.bindAction(89, true, 'autosize', true); // Ctrl+Shift+Y
+ keyHandler.bindAction(88, true, 'cut'); // Ctrl+X
+ keyHandler.bindAction(67, true, 'copy'); // Ctrl+C
+ keyHandler.bindAction(86, true, 'paste'); // Ctrl+V
+ keyHandler.bindAction(71, true, 'group'); // Ctrl+G
+ keyHandler.bindAction(77, true, 'editData'); // Ctrl+M
+ keyHandler.bindAction(71, true, 'grid', true); // Ctrl+Shift+G
+ keyHandler.bindAction(73, true, 'italic'); // Ctrl+I
+ keyHandler.bindAction(76, true, 'lockUnlock'); // Ctrl+L
+ keyHandler.bindAction(76, true, 'layers', true); // Ctrl+Shift+L
+ keyHandler.bindAction(80, true, 'format', true); // Ctrl+Shift+P
+ keyHandler.bindAction(85, true, 'underline'); // Ctrl+U
+ keyHandler.bindAction(85, true, 'ungroup', true); // Ctrl+Shift+U
+ keyHandler.bindAction(109, true, 'decreaseFontSize', true); // Ctrl+Shift+Minus
+ keyHandler.bindAction(107, true, 'increaseFontSize', true); // Ctrl+Shift+Plus
+ keyHandler.bindAction(219, true, 'decreaseFontSize', true); // Ctrl+{
+ keyHandler.bindAction(221, true, 'increaseFontSize', true); // Ctrl+}
+ keyHandler.bindAction(190, true, 'superscript'); // Ctrl+.
+ keyHandler.bindAction(188, true, 'subscript'); // Ctrl+,
+ keyHandler.bindAction(13, false, 'keyPressEnter'); // Enter
+ keyHandler.bindKey(113, function () { if (graph.isEnabled()) { graph.startEditingAtCell(); } }); // F2
+ }
+
+ if (!mxClient.IS_WIN) {
+ keyHandler.bindAction(90, true, 'redo', true); // Ctrl+Shift+Z
+ }
+ else {
+ keyHandler.bindAction(89, true, 'redo'); // Ctrl+Y
+ }
+
+ return keyHandler;
+};
+
+/**
+ * Creates the keyboard event handler for the current graph and history.
+ */
+EditorUi.prototype.destroy = function () {
+ var graph = this.editor.graph;
+
+ if (graph != null && this.selectionStateListener != null) {
+ graph.getSelectionModel().removeListener(mxEvent.CHANGE, this.selectionStateListener);
+ graph.getModel().removeListener(mxEvent.CHANGE, this.selectionStateListener);
+ graph.removeListener(mxEvent.EDITING_STARTED, this.selectionStateListener);
+ graph.removeListener(mxEvent.EDITING_STOPPED, this.selectionStateListener);
+ graph.getView().removeListener('unitChanged', this.selectionStateListener);
+ this.selectionStateListener = null;
+ }
+
+ if (this.editor != null) {
+ this.editor.destroy();
+ this.editor = null;
+ }
+
+ if (this.menubar != null) {
+ this.menubar.destroy();
+ this.menubar = null;
+ }
+
+ if (this.toolbar != null) {
+ this.toolbar.destroy();
+ this.toolbar = null;
+ }
+
+ if (this.sidebar != null) {
+ this.sidebar.destroy();
+ this.sidebar = null;
+ }
+
+ if (this.keyHandler != null) {
+ this.keyHandler.destroy();
+ this.keyHandler = null;
+ }
+
+ if (this.keydownHandler != null) {
+ mxEvent.removeListener(document, 'keydown', this.keydownHandler);
+ this.keydownHandler = null;
+ }
+
+ if (this.keyupHandler != null) {
+ mxEvent.removeListener(document, 'keyup', this.keyupHandler);
+ this.keyupHandler = null;
+ }
+
+ if (this.resizeHandler != null) {
+ mxEvent.removeListener(window, 'resize', this.resizeHandler);
+ this.resizeHandler = null;
+ }
+
+ if (this.gestureHandler != null) {
+ mxEvent.removeGestureListeners(document, this.gestureHandler);
+ this.gestureHandler = null;
+ }
+
+ if (this.orientationChangeHandler != null) {
+ mxEvent.removeListener(window, 'orientationchange', this.orientationChangeHandler);
+ this.orientationChangeHandler = null;
+ }
+
+ if (this.scrollHandler != null) {
+ mxEvent.removeListener(window, 'scroll', this.scrollHandler);
+ this.scrollHandler = null;
+ }
+
+ if (this.destroyFunctions != null) {
+ for (var i = 0; i < this.destroyFunctions.length; i++) {
+ this.destroyFunctions[i]();
+ }
+
+ this.destroyFunctions = null;
+ }
+
+ var c = [this.menubarContainer, this.toolbarContainer, this.sidebarContainer,
+ this.formatContainer, this.diagramContainer, this.footerContainer,
+ this.chromelessToolbar, this.hsplit, this.layersDialog];
+
+ for (var i = 0; i < c.length; i++) {
+ if (c[i] != null && c[i].parentNode != null) {
+ c[i].parentNode.removeChild(c[i]);
+ }
+ }
+};
+
+EditorUi.prototype.exportImage = function (scale, transparentBackground, ignoreSelection, addShadow, editable, border, noCrop, format, exportHandler) {
+ format = (format != null) ? format : 'png';
+
+ var selectionEmpty = this.editor.graph.isSelectionEmpty();
+ ignoreSelection = (ignoreSelection != null) ? ignoreSelection : selectionEmpty;
+
+ // Caches images
+ if (this.thumbImageCache == null) {
+ this.thumbImageCache = new Object();
+ }
+
+ try {
+ this.exportToCanvas(mxUtils.bind(this, function (canvas) {
+
+ try {
+ this.saveCanvas(canvas, (editable) ? this.getFileData(true, null,
+ null, null, ignoreSelection) : null, format, exportHandler);
+ }
+ catch (e) {
+ this.handleError(e);
+ }
+ }), null, this.thumbImageCache, null, mxUtils.bind(this, function (e) {
+ this.handleError(e);
+ }), null, ignoreSelection, scale || 1, transparentBackground,
+ addShadow, null, null, border, noCrop);
+ }
+ catch (e) {
+ this.handleError(e);
+ }
+};
+
+EditorUi.prototype.handleError = function (error) {
+ throw error;
+}
+
+/**
+ *
+ */
+EditorUi.prototype.exportToCanvas = function (callback, width, imageCache, background, error, limitHeight,
+ ignoreSelection, scale, transparentBackground, addShadow, converter, graph, border, noCrop) {
+ limitHeight = (limitHeight != null) ? limitHeight : true;
+ ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
+ graph = (graph != null) ? graph : this.editor.graph;
+ border = (border != null) ? border : 0;
+
+ this.convertImages(graph.getSvg(null, null, null),
+ mxUtils.bind(this, function (svgRoot) {
+ var img = new Image();
+
+
+ img.onload = mxUtils.bind(this, function () {
+ try {
+ var canvas = document.createElement('canvas');
+ var w = parseInt(svgRoot.getAttribute('width'));
+ var h = parseInt(svgRoot.getAttribute('height'));
+ scale = (scale != null) ? scale : 1;
+
+ if (width != null) {
+ scale = (!limitHeight) ? width / w : Math.min(1, Math.min((width * 3) / (h * 4), width / w));
+ }
+
+ w = Math.ceil(scale * w) + 2 * border;
+ h = Math.ceil(scale * h) + 2 * border;
+
+ canvas.setAttribute('width', w);
+ canvas.setAttribute('height', h);
+ var ctx = canvas.getContext('2d');
+
+ ctx.scale(scale, scale);
+
+ // Workaround for broken data URI images in Safari on first export
+ if (mxClient.IS_SF) {
+ window.setTimeout(function () {
+ ctx.drawImage(img, border / scale, border / scale);
+ callback(canvas);
+ }, 0);
+ }
+ else {
+ ctx.drawImage(img, border / scale, border / scale);
+ callback(canvas);
+ }
+ }
+ catch (e) {
+ this.handleError(e);
+ }
+ });
+
+ img.onerror = function (e) {
+ this.handleError(e);
+ };
+
+ try {
+ if (addShadow) {
+ this.editor.graph.addSvgShadow(svgRoot);
+ }
+
+ var done = mxUtils.bind(this, function () {
+ if (this.editor.resolvedFontCss != null) {
+ var st = document.createElement('style');
+ st.setAttribute('type', 'text/css');
+ st.innerHTML = this.editor.resolvedFontCss;
+
+ // Must be in defs section for FF to work
+ var defs = svgRoot.getElementsByTagName('defs');
+ defs[0].appendChild(st);
+ }
+ img.src = this.createSvgDataUri(mxUtils.getXml(svgRoot));
+ });
+
+ this.loadFonts(done);
+ }
+ catch (e) {
+ //console.log('src', e, img.src);
+ this.handleError(e);
+ }
+ }), imageCache, converter);
+};
+
+/**
+ * Converts all images in the SVG output to data URIs for immediate rendering
+ */
+EditorUi.prototype.convertImages = function (svgRoot, callback, imageCache, converter) {
+ // Converts images to data URLs for immediate painting
+ if (converter == null) {
+ converter = this.createImageUrlConverter();
+ }
+
+ // Barrier for asynchronous image loading
+ var counter = 0;
+
+ function inc() {
+ counter++;
+ };
+
+ function dec() {
+ counter--;
+
+ if (counter == 0) {
+ callback(svgRoot);
+ }
+ };
+
+ var cache = imageCache || new Object();
+
+ var convertImages = mxUtils.bind(this, function (tagName, srcAttr) {
+ var images = svgRoot.getElementsByTagName(tagName);
+
+ for (var i = 0; i < images.length; i++) {
+ (mxUtils.bind(this, function (img) {
+ var src = converter.convert(img.getAttribute(srcAttr));
+
+ // Data URIs are pass-through
+ if (src != null && src.substring(0, 5) != 'data:') {
+ var tmp = cache[src];
+
+ if (tmp == null) {
+ inc();
+
+ this.convertImageToDataUri(src, function (uri) {
+ if (uri != null) {
+ cache[src] = uri;
+ img.setAttribute(srcAttr, uri);
+ }
+
+ dec();
+ });
+ }
+ else {
+ img.setAttribute(srcAttr, tmp);
+ }
+ }
+ else if (src != null) {
+ img.setAttribute(srcAttr, src);
+ }
+ }))(images[i]);
+ }
+ });
+
+ // Converts all known image tags in output
+ // LATER: Add support for images in CSS
+ convertImages('image', 'xlink:href');
+ convertImages('img', 'src');
+
+ // All from cache or no images
+ if (counter == 0) {
+ callback(svgRoot);
+ }
+};
+
+/**
+ * Converts all images in the SVG output to data URIs for immediate rendering
+ */
+EditorUi.prototype.createImageUrlConverter = function () {
+ var converter = new mxUrlConverter();
+ converter.updateBaseUrl();
+
+ // Extends convert to avoid CORS using an image proxy server where needed
+ var convert = converter.convert;
+ var self = this;
+
+ converter.convert = function (src) {
+ // if (src != null)
+ // {
+ // var remote = src.substring(0, 7) == 'http://' || src.substring(0, 8) == 'https://';
+
+ // if (remote && !navigator.onLine)
+ // {
+ // src = self.svgBrokenImage.src;
+ // }
+ // else if (remote && src.substring(0, converter.baseUrl.length) != converter.baseUrl &&
+ // (!self.crossOriginImages || !self.isCorsEnabledForUrl(src)))
+ // {
+ // src = PROXY_URL + '?url=' + encodeURIComponent(src);
+ // }
+ // else if (src.substring(0, 19) != 'chrome-extension://')
+ // {
+ // src = convert.apply(this, arguments);
+ // }
+ // }
+
+ return src;
+ };
+
+ return converter;
+};
+
+/**
+ * For the fontCSS to be applied when rendering images on canvas, the actual
+ * font data must be made available via a data URI encoding of the file.
+ */
+EditorUi.prototype.loadFonts = function (then) {
+ if (this.editor.fontCss != null && this.editor.resolvedFontCss == null) {
+ var parts = this.editor.fontCss.split('url(');
+ var waiting = 0;
+ var fonts = {};
+
+ // Strips leading and trailing quotes and spaces
+ function trimString(str) {
+ return str.replace(new RegExp("^[\\s\"']+", "g"), "").replace(new RegExp("[\\s\"']+$", "g"), "");
+ };
+
+ var finish = mxUtils.bind(this, function () {
+ if (waiting == 0) {
+ // Constructs string
+ var result = [parts[0]];
+
+ for (var j = 1; j < parts.length; j++) {
+ var idx = parts[j].indexOf(')');
+ result.push('url("');
+ result.push(fonts[trimString(parts[j].substring(0, idx))]);
+ result.push('"' + parts[j].substring(idx));
+ }
+
+ this.editor.resolvedFontCss = result.join('');
+ then();
+ }
+ });
+
+ if (parts.length > 0) {
+ for (var i = 1; i < parts.length; i++) {
+ var idx = parts[i].indexOf(')');
+ var format = null;
+
+ // Checks if there is a format directive
+ var fmtIdx = parts[i].indexOf('format(', idx);
+
+ if (fmtIdx > 0) {
+ format = trimString(parts[i].substring(fmtIdx + 7, parts[i].indexOf(')', fmtIdx)));
+ }
+
+ (mxUtils.bind(this, function (url) {
+ if (fonts[url] == null) {
+ // Mark font es being fetched and fetch it
+ fonts[url] = url;
+ waiting++;
+
+ var mime = 'application/x-font-ttf';
+
+ // See https://stackoverflow.com/questions/2871655/proper-mime-type-for-fonts
+ if (format == 'svg' || /(\.svg)($|\?)/i.test(url)) {
+ mime = 'image/svg+xml';
+ }
+ else if (format == 'otf' || format == 'embedded-opentype' || /(\.otf)($|\?)/i.test(url)) {
+ mime = 'application/x-font-opentype';
+ }
+ else if (format == 'woff' || /(\.woff)($|\?)/i.test(url)) {
+ mime = 'application/font-woff';
+ }
+ else if (format == 'woff2' || /(\.woff2)($|\?)/i.test(url)) {
+ mime = 'application/font-woff2';
+ }
+ else if (format == 'eot' || /(\.eot)($|\?)/i.test(url)) {
+ mime = 'application/vnd.ms-fontobject';
+ }
+ else if (format == 'sfnt' || /(\.sfnt)($|\?)/i.test(url)) {
+ mime = 'application/font-sfnt';
+ }
+
+ var realUrl = url;
+
+ if ((/^https?:\/\//.test(realUrl)) && !this.isCorsEnabledForUrl(realUrl)) {
+ realUrl = PROXY_URL + '?url=' + encodeURIComponent(url);
+ }
+
+ // LATER: Remove cache-control header
+ this.loadUrl(realUrl, mxUtils.bind(this, function (uri) {
+ fonts[url] = uri;
+ waiting--;
+ finish();
+ }), mxUtils.bind(this, function (err) {
+ // LATER: handle error
+ waiting--;
+ finish();
+ }), true, null, 'data:' + mime + ';charset=utf-8;base64,');
+ }
+ }))(trimString(parts[i].substring(0, idx)), format);
+ }
+ }
+ }
+ else {
+ then();
+ }
+};
+
+/**
+ * Translates this point by the given vector.
+ *
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+EditorUi.prototype.createSvgDataUri = function (svg) {
+ return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
+};
+
+/**
+ * Translates this point by the given vector.
+ *
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+EditorUi.prototype.getFileData = function (forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection, currentPage, node, compact, file) {
+ ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
+ currentPage = (currentPage != null) ? currentPage : false;
+
+ node = this.editor.getGraphXml(ignoreSelection);
+ var graph = this.editor.graph;
+
+
+ var result = this.createFileData(node, graph, file, window.location.href,
+ forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection, compact);
+
+ // Removes temporary graph from DOM
+ if (graph != this.editor.graph) {
+ graph.container.parentNode.removeChild(graph.container);
+ }
+
+ return result;
+};
+
+/**
+ * Translates this point by the given vector.
+ *
+ * @param {number} dx X-coordinate of the translation.
+ * @param {number} dy Y-coordinate of the translation.
+ */
+EditorUi.prototype.createFileData = function (node, graph, file, url, forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection, compact) {
+ graph = (graph != null) ? graph : this.editor.graph;
+ forceXml = (forceXml != null) ? forceXml : false;
+ ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
+
+ var editLink = null;
+ var redirect = null;
+
+ if (file == null || file.getMode() == App.MODE_DEVICE || file.getMode() == App.MODE_BROWSER) {
+ editLink = '_blank';
+ }
+ else {
+ editLink = url;
+ redirect = editLink;
+ }
+
+ if (node == null) {
+ return '';
+ }
+ else {
+ var fileNode = node;
+
+ // Ignores case for possible HTML or XML nodes
+ if (fileNode.nodeName.toLowerCase() != 'mxfile') {
+ // Removes control chars in input for correct roundtrip check
+ var text = graph.zapGremlins(mxUtils.getXml(node));
+ var data = graph.compress(text);
+
+ // Fallback to plain XML for invalid compression
+ // TODO: Remove this fallback with active pages
+ if (graph.decompress(data) != text) {
+ return text;
+ }
+ else {
+ var diagramNode = node.ownerDocument.createElement('diagram');
+ diagramNode.setAttribute('id', 'tapd-' + Editor.guid());
+ mxUtils.setTextContent(diagramNode, data);
+
+ fileNode = node.ownerDocument.createElement('mxfile');
+ fileNode.appendChild(diagramNode);
+ }
+ }
+
+ if (!compact) {
+ // Removes old metadata
+ fileNode.removeAttribute('userAgent');
+ fileNode.removeAttribute('version');
+ fileNode.removeAttribute('editor');
+ fileNode.removeAttribute('type');
+
+ // Adds new metadata
+ fileNode.setAttribute('modified', new Date().toISOString());
+ fileNode.setAttribute('host', window.location.hostname);
+ fileNode.setAttribute('agent', navigator.userAgent);
+ fileNode.setAttribute('version', EditorUi.VERSION);
+ fileNode.setAttribute('etag', 'tapd-' + Editor.guid());
+
+ var md = (file != null) ? file.getMode() : this.mode;
+
+ if (md != null) {
+ fileNode.setAttribute('type', md);
+ }
+ }
+ else {
+ fileNode = fileNode.cloneNode(true);
+ fileNode.removeAttribute('userAgent');
+ fileNode.removeAttribute('version');
+ fileNode.removeAttribute('editor');
+ fileNode.removeAttribute('type');
+ }
+
+ var xml = mxUtils.getXml(fileNode);
+
+ return xml;
+ }
+};
+
+/**
+ *
+ */
+EditorUi.prototype.saveCanvas = function (canvas, xml, format, exportHandler) {
+ var ext = ((format == 'jpeg') ? 'jpg' : format);
+ var filename = this.getBaseFilename() + '.' + ext;
+ var data = this.createImageDataUri(canvas, xml, format);
+
+ if (exportHandler && typeof exportHandler === 'function') {
+ exportHandler(data, filename);
+ } else {
+ this.doDownloadFile(data.substring(data.lastIndexOf(',') + 1), filename, 'image/' + format, true, format);
+ }
+
+};
+
+
+EditorUi.prototype.getBaseFilename = function () {
+ return this.editor.getFilename();
+};
+
+
+EditorUi.prototype.createImageDataUri = function (canvas, xml, format) {
+ var data = canvas.toDataURL('image/' + format);
+
+ // Checks if output is invalid or empty
+ if (data.length <= 6 || data == canvas.cloneNode(false).toDataURL('image/' + format)) {
+ throw { message: 'Invalid image' };
+ }
+
+ if (xml != null) {
+ data = this.writeGraphModelToPng(data, 'zTXt', 'mxGraphModel', atob(this.editor.graph.compress(xml)));
+ }
+
+ return data;
+};
+
+/**
+ * Adds the given text to the compressed or non-compressed text chunk.
+ */
+EditorUi.prototype.writeGraphModelToPng = function (data, type, key, value, error) {
+ var base64 = data.substring(data.indexOf(',') + 1);
+ var f = (window.atob) ? atob(base64) : Base64.decode(base64, true);
+ var pos = 0;
+
+ function fread(d, count) {
+ var start = pos;
+ pos += count;
+
+ return d.substring(start, pos);
+ };
+
+ // Reads unsigned long 32 bit big endian
+ function _freadint(d) {
+ var bytes = fread(d, 4);
+
+ return bytes.charCodeAt(3) + (bytes.charCodeAt(2) << 8) +
+ (bytes.charCodeAt(1) << 16) + (bytes.charCodeAt(0) << 24);
+ };
+
+ function writeInt(num) {
+ return String.fromCharCode((num >> 24) & 0x000000ff, (num >> 16) & 0x000000ff,
+ (num >> 8) & 0x000000ff, num & 0x000000ff);
+ };
+
+ // Checks signature
+ if (fread(f, 8) != String.fromCharCode(137) + 'PNG' + String.fromCharCode(13, 10, 26, 10)) {
+ if (error != null) {
+ error();
+ }
+
+ return;
+ }
+
+ // Reads header chunk
+ fread(f, 4);
+
+ if (fread(f, 4) != 'IHDR') {
+ if (error != null) {
+ error();
+ }
+
+ return;
+ }
+
+ fread(f, 17);
+ var result = f.substring(0, pos);
+
+ do {
+ var n = _freadint(f);
+ var chunk = fread(f, 4);
+
+ if (chunk == 'IDAT') {
+ result = f.substring(0, pos - 8);
+
+ var chunkData = key + String.fromCharCode(0) +
+ ((type == 'zTXt') ? String.fromCharCode(0) : '') +
+ value;
+
+ // FIXME: Wrong crc
+ var crc = 0xffffffff;
+ crc = this.updateCRC(crc, type, 0, 4);
+ crc = this.updateCRC(crc, chunkData, 0, chunkData.length);
+
+ result += writeInt(chunkData.length) + type + chunkData + writeInt(crc ^ 0xffffffff);
+ result += f.substring(pos - 8, f.length);
+
+ break;
+ }
+
+ result += f.substring(pos - 8, pos - 4 + n);
+ fread(f, n);
+ fread(f, 4);
+ }
+ while (n);
+
+ return 'data:image/png;base64,' + ((window.btoa) ? btoa(result) : Base64.encode(result, true));
+};
+
+EditorUi.prototype.crcTable = [];
+
+for (var n = 0; n < 256; n++) {
+ var c = n;
+
+ for (var k = 0; k < 8; k++) {
+ if ((c & 1) == 1) {
+ c = 0xedb88320 ^ (c >>> 1);
+ }
+ else {
+ c >>>= 1;
+ }
+
+ EditorUi.prototype.crcTable[n] = c;
+ }
+}
+
+EditorUi.prototype.updateCRC = function (crc, data, off, len) {
+ var c = crc;
+
+ for (var n = 0; n < len; n++) {
+ c = EditorUi.prototype.crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8);
+ }
+
+ return c;
+};
diff --git a/oaweb/public/cherry/drawio/Format.js b/oaweb/public/cherry/drawio/Format.js
new file mode 100644
index 0000000..e3ad097
--- /dev/null
+++ b/oaweb/public/cherry/drawio/Format.js
@@ -0,0 +1,8103 @@
+/**
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+Format = function(editorUi, container)
+{
+ this.editorUi = editorUi;
+ this.container = container;
+};
+
+/**
+ * Background color for inactive tabs.
+ */
+Format.inactiveTabBackgroundColor = '#e4e4e4';
+
+/**
+ * Icons for markers (24x16).
+ */
+Format.classicFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.classicThinFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.openFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.openThinFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.openAsyncFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.blockFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.blockThinFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.asyncFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.ovalFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.diamondFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.diamondThinFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.classicMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.classicThinMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.blockMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.blockThinMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.asyncMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.ovalMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.diamondMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.diamondThinMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.boxMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.halfCircleMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.dashMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.crossMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.circlePlusMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.circleMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.ERmandOneMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.ERmanyMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.ERoneToManyMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.ERzeroToOneMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.ERzeroToManyMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.EROneMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.baseDashMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.doubleBlockMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+Format.doubleBlockFilledMarkerImage = Graph.createSvgImage(20, 22, '', 32, 20);
+
+/**
+ * Adds a style change item to the given menu.
+ */
+Format.processMenuIcon = function(elt, transform)
+{
+ var imgs = elt.getElementsByTagName('img');
+
+ if (imgs.length > 0)
+ {
+ imgs[0].className = 'geIcon geAdaptiveAsset';
+ imgs[0].style.padding = '0px';
+ imgs[0].style.margin = '0 0 0 2px';
+
+ if (transform != null)
+ {
+ mxUtils.setPrefixedStyle(imgs[0].style, 'transform', transform);
+ }
+ }
+
+ return elt;
+};
+
+/**
+ * Returns information about the current selection.
+ */
+Format.prototype.labelIndex = 0;
+
+/**
+ * Returns information about the current selection.
+ */
+Format.prototype.diagramIndex = 0;
+
+/**
+ * Returns information about the current selection.
+ */
+Format.prototype.currentIndex = 0;
+
+/**
+ * Returns information about the current selection.
+ */
+Format.prototype.showCloseButton = true;
+
+/**
+ * Returns information about the current selection.
+ */
+Format.prototype.rounded = false;
+
+/**
+ * Returns information about the current selection.
+ */
+Format.prototype.curved = false;
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Format.prototype.init = function()
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+
+ this.update = mxUtils.bind(this, function(sender, evt)
+ {
+ this.refresh();
+ });
+
+ graph.getSelectionModel().addListener(mxEvent.CHANGE, this.update);
+ graph.getModel().addListener(mxEvent.CHANGE, this.update);
+ graph.addListener(mxEvent.EDITING_STARTED, this.update);
+ graph.addListener(mxEvent.EDITING_STOPPED, this.update);
+ graph.getView().addListener('unitChanged', this.update);
+ editor.addListener('autosaveChanged', this.update);
+ graph.addListener(mxEvent.ROOT, this.update);
+ ui.addListener('styleChanged', this.update);
+ ui.addListener('darkModeChanged', this.update);
+
+ this.refresh();
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Format.prototype.clear = function()
+{
+ this.container.innerText = '';
+
+ // Destroy existing panels
+ if (this.panels != null)
+ {
+ for (var i = 0; i < this.panels.length; i++)
+ {
+ this.panels[i].destroy();
+ }
+ }
+
+ this.panels = [];
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Format.prototype.refresh = function()
+{
+ if (this.pendingRefresh != null)
+ {
+ window.clearTimeout(this.pendingRefresh);
+ this.pendingRefresh = null;
+ }
+
+ this.pendingRefresh = window.setTimeout(mxUtils.bind(this, function()
+ {
+ this.immediateRefresh();
+ }));
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+Format.prototype.immediateRefresh = function()
+{
+ // Performance tweak: No refresh needed if not visible
+ if (this.container.style.width == '0px')
+ {
+ return;
+ }
+
+ this.clear();
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+
+ var div = document.createElement('div');
+ div.style.whiteSpace = 'nowrap';
+ div.style.color = 'rgb(112, 112, 112)';
+ div.style.textAlign = 'left';
+ div.style.cursor = 'default';
+
+ var label = document.createElement('div');
+ label.className = 'geFormatSection';
+ label.style.textAlign = 'center';
+ label.style.fontWeight = 'bold';
+ label.style.paddingTop = '8px';
+ label.style.fontSize = '13px';
+ label.style.borderWidth = '0px 0px 1px 1px';
+ label.style.borderStyle = 'solid';
+ label.style.display = 'inline-block';
+ label.style.height = '25px';
+ label.style.overflow = 'hidden';
+ label.style.width = '100%';
+ this.container.appendChild(div);
+
+ // Prevents text selection
+ mxEvent.addListener(label, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
+ mxUtils.bind(this, function(evt)
+ {
+ evt.preventDefault();
+ }));
+
+ var ss = ui.getSelectionState();
+ var containsLabel = ss.containsLabel;
+ var currentLabel = null;
+ var currentPanel = null;
+
+ var addClickHandler = mxUtils.bind(this, function(elt, panel, index, lastEntry)
+ {
+ var clickHandler = mxUtils.bind(this, function(evt)
+ {
+ if (currentLabel != elt)
+ {
+ if (containsLabel)
+ {
+ this.labelIndex = index;
+ }
+ else if (graph.isSelectionEmpty())
+ {
+ this.diagramIndex = index;
+ }
+ else
+ {
+ this.currentIndex = index;
+ }
+
+ if (currentLabel != null)
+ {
+ currentLabel.style.backgroundColor = Format.inactiveTabBackgroundColor;
+ currentLabel.style.borderBottomWidth = '1px';
+ }
+
+ currentLabel = elt;
+ currentLabel.style.backgroundColor = '';
+ currentLabel.style.borderBottomWidth = '0px';
+
+ if (currentPanel != panel)
+ {
+ if (currentPanel != null)
+ {
+ currentPanel.style.display = 'none';
+ }
+
+ currentPanel = panel;
+ currentPanel.style.display = '';
+ }
+ }
+ });
+
+ mxEvent.addListener(elt, 'click', clickHandler);
+
+ // Prevents text selection
+ mxEvent.addListener(elt, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
+ mxUtils.bind(this, function(evt)
+ {
+ evt.preventDefault();
+ }));
+
+ if ((lastEntry && currentLabel == null) ||
+ (index == ((containsLabel) ? this.labelIndex : ((graph.isSelectionEmpty()) ?
+ this.diagramIndex : this.currentIndex))))
+ {
+ // Invokes handler directly as a workaround for no click on DIV in KHTML.
+ clickHandler();
+ }
+ });
+
+ var idx = 0;
+
+ if (graph.isSelectionEmpty())
+ {
+ mxUtils.write(label, mxResources.get('diagram'));
+ label.style.borderLeftWidth = '0px';
+
+ div.appendChild(label);
+ var diagramPanel = div.cloneNode(false);
+ this.panels.push(new DiagramFormatPanel(this, ui, diagramPanel));
+ this.container.appendChild(diagramPanel);
+
+ if (Editor.styles != null)
+ {
+ diagramPanel.style.display = 'none';
+ label.style.width = (this.showCloseButton) ? '106px' : '50%';
+ label.style.cursor = 'pointer';
+ label.style.backgroundColor = Format.inactiveTabBackgroundColor;
+
+ var label2 = label.cloneNode(false);
+ label2.style.borderLeftWidth = '1px';
+ label2.style.borderRightWidth = '1px';
+ label2.style.backgroundColor = Format.inactiveTabBackgroundColor;
+
+ addClickHandler(label, diagramPanel, idx++);
+
+ var stylePanel = div.cloneNode(false);
+ stylePanel.style.display = 'none';
+ mxUtils.write(label2, mxResources.get('style'));
+ div.appendChild(label2);
+ this.panels.push(new DiagramStylePanel(this, ui, stylePanel));
+ this.container.appendChild(stylePanel);
+
+ addClickHandler(label2, stylePanel, idx++);
+ }
+
+ // Adds button to hide the format panel since
+ // people don't seem to find the toolbar button
+ // and the menu item in the format menu
+ if (this.showCloseButton)
+ {
+ var label2 = label.cloneNode(false);
+ label2.style.borderLeftWidth = '1px';
+ label2.style.borderRightWidth = '1px';
+ label2.style.borderBottomWidth = '1px';
+ label2.style.backgroundColor = Format.inactiveTabBackgroundColor;
+ label2.style.position = 'absolute';
+ label2.style.right = '0px';
+ label2.style.top = '0px';
+ label2.style.width = '25px';
+
+ var img = document.createElement('img');
+ img.setAttribute('border', '0');
+ img.setAttribute('src', Dialog.prototype.closeImage);
+ img.setAttribute('title', mxResources.get('hide'));
+ img.style.position = 'absolute';
+ img.style.display = 'block';
+ img.style.right = '0px';
+ img.style.top = '8px';
+ img.style.cursor = 'pointer';
+ img.style.marginTop = '1px';
+ img.style.marginRight = '6px';
+ img.style.border = '1px solid transparent';
+ img.style.padding = '1px';
+ img.style.opacity = 0.5;
+ label2.appendChild(img)
+
+ mxEvent.addListener(img, 'click', function()
+ {
+ ui.actions.get('format').funct();
+ });
+
+ div.appendChild(label2);
+ }
+ }
+ else if (graph.isEditing())
+ {
+ mxUtils.write(label, mxResources.get('text'));
+ div.appendChild(label);
+ label.style.borderLeftStyle = 'none';
+ this.panels.push(new TextFormatPanel(this, ui, div));
+ }
+ else
+ {
+ label.style.backgroundColor = Format.inactiveTabBackgroundColor;
+ label.style.borderLeftWidth = '1px';
+ label.style.cursor = 'pointer';
+ label.style.width = ss.cells.length == 0 ? '100%' :
+ (containsLabel ? '50%' : '33.3%');
+ var label2 = label.cloneNode(false);
+ var label3 = label2.cloneNode(false);
+
+ // Workaround for ignored background in IE
+ label2.style.backgroundColor = Format.inactiveTabBackgroundColor;
+ label3.style.backgroundColor = Format.inactiveTabBackgroundColor;
+
+ // Style
+ if (containsLabel)
+ {
+ label2.style.borderLeftWidth = '0px';
+ }
+ else if (ss.cells.length > 0)
+ {
+ label.style.borderLeftWidth = '0px';
+ mxUtils.write(label, mxResources.get('style'));
+ div.appendChild(label);
+
+ var stylePanel = div.cloneNode(false);
+ stylePanel.style.display = 'none';
+ this.panels.push(new StyleFormatPanel(this, ui, stylePanel));
+ this.container.appendChild(stylePanel);
+
+ addClickHandler(label, stylePanel, idx++);
+ }
+
+ // Text
+ mxUtils.write(label2, mxResources.get('text'));
+ div.appendChild(label2);
+
+ var textPanel = div.cloneNode(false);
+ textPanel.style.display = 'none';
+ this.panels.push(new TextFormatPanel(this, ui, textPanel));
+ this.container.appendChild(textPanel);
+
+ // Arrange
+ mxUtils.write(label3, mxResources.get('arrange'));
+ div.appendChild(label3);
+
+ var arrangePanel = div.cloneNode(false);
+ arrangePanel.style.display = 'none';
+ this.panels.push(new ArrangePanel(this, ui, arrangePanel));
+ this.container.appendChild(arrangePanel);
+
+ if (ss.cells.length > 0)
+ {
+ addClickHandler(label2, textPanel, idx + 1);
+ }
+ else
+ {
+ label2.style.display = 'none';
+ }
+
+ addClickHandler(label3, arrangePanel, idx++, true);
+ }
+};
+
+/**
+ * Base class for format panels.
+ */
+BaseFormatPanel = function(format, editorUi, container)
+{
+ this.format = format;
+ this.editorUi = editorUi;
+ this.container = container;
+ this.listeners = [];
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.buttonBackgroundColor = 'transparent';
+
+/**
+ * Install input handler.
+ */
+BaseFormatPanel.prototype.installInputHandler = function(input, key, defaultValue, min, max, unit, textEditFallback, isFloat)
+{
+ unit = (unit != null) ? unit : '';
+ isFloat = (isFloat != null) ? isFloat : false;
+
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+
+ min = (min != null) ? min : 1;
+ max = (max != null) ? max : 999;
+
+ var selState = null;
+ var updating = false;
+
+ var update = mxUtils.bind(this, function(evt)
+ {
+ var value = (isFloat) ? parseFloat(input.value) : parseInt(input.value);
+
+ // Special case: angle mod 360
+ if (!isNaN(value) && key == mxConstants.STYLE_ROTATION)
+ {
+ // Workaround for decimal rounding errors in floats is to
+ // use integer and round all numbers to two decimal point
+ value = mxUtils.mod(Math.round(value * 100), 36000) / 100;
+ }
+
+ value = Math.min(max, Math.max(min, (isNaN(value)) ? defaultValue : value));
+
+ if (graph.cellEditor.isContentEditing() && textEditFallback)
+ {
+ if (!updating)
+ {
+ updating = true;
+
+ if (selState != null)
+ {
+ graph.cellEditor.restoreSelection(selState);
+ selState = null;
+ }
+
+ textEditFallback(value);
+ input.value = value + unit;
+
+ // Restore focus and selection in input
+ updating = false;
+ }
+ }
+ else if (value != mxUtils.getValue(ui.getSelectionState().style, key, defaultValue))
+ {
+ if (graph.isEditing())
+ {
+ graph.stopEditing(true);
+ }
+
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = ui.getSelectionState().cells;
+ graph.setCellStyles(key, value, cells);
+
+ // Handles special case for fontSize where HTML labels are parsed and updated
+ if (key == mxConstants.STYLE_FONTSIZE)
+ {
+ graph.updateLabelElements(cells, function(elt)
+ {
+ elt.style.fontSize = value + 'px';
+ elt.removeAttribute('size');
+ });
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (graph.model.getChildCount(cells[i]) == 0)
+ {
+ graph.autoSizeCell(cells[i], false);
+ }
+ }
+
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [key],
+ 'values', [value], 'cells', cells));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+
+ input.value = value + unit;
+ mxEvent.consume(evt);
+ });
+
+ if (textEditFallback && graph.cellEditor.isContentEditing())
+ {
+ // KNOWN: Arrow up/down clear selection text in quirks/IE 8
+ // Text size via arrow button limits to 16 in IE11. Why?
+ mxEvent.addListener(input, 'mousedown', function()
+ {
+ if (document.activeElement == graph.cellEditor.textarea)
+ {
+ selState = graph.cellEditor.saveSelection();
+ }
+ });
+
+ mxEvent.addListener(input, 'touchstart', function()
+ {
+ if (document.activeElement == graph.cellEditor.textarea)
+ {
+ selState = graph.cellEditor.saveSelection();
+ }
+ });
+ }
+
+ mxEvent.addListener(input, 'change', update);
+ mxEvent.addListener(input, 'blur', update);
+
+ return update;
+};
+
+/**
+ * Adds the given option.
+ */
+BaseFormatPanel.prototype.createPanel = function()
+{
+ var div = document.createElement('div');
+ div.className = 'geFormatSection';
+ div.style.padding = '12px 0px 8px 14px';
+
+ return div;
+};
+
+/**
+ * Adds the given option.
+ */
+BaseFormatPanel.prototype.createTitle = function(title)
+{
+ var div = document.createElement('div');
+ div.style.padding = '0px 0px 6px 0px';
+ div.style.whiteSpace = 'nowrap';
+ div.style.overflow = 'hidden';
+ div.style.width = '200px';
+ div.style.fontWeight = 'bold';
+ mxUtils.write(div, title);
+
+ return div;
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.addAction = function(div, name)
+{
+ var action = this.editorUi.actions.get(name);
+ var btn = null;
+
+ if (action != null && action.isEnabled())
+ {
+ btn = mxUtils.button(action.label, mxUtils.bind(this, function(evt)
+ {
+ try
+ {
+ action.funct(evt, evt);
+ }
+ catch (e)
+ {
+ this.editorUi.handleError(e);
+ }
+ }));
+
+ var short = (action.shortcut != null) ? ' (' + action.shortcut + ')' : '';
+ btn.setAttribute('title', action.label + short);
+ btn.style.marginBottom = '2px';
+ btn.style.width = '210px';
+ div.appendChild(btn);
+ result = true;
+ }
+
+ return btn;
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.addActions = function(div, names)
+{
+ var lastBr = null;
+ var last = null;
+ var count = 0;
+
+ for (var i = 0; i < names.length; i++)
+ {
+ var btn = this.addAction(div, names[i]);
+
+ if (btn != null)
+ {
+ count++;
+
+ if (mxUtils.mod(count, 2) == 0)
+ {
+ last.style.marginRight = '2px';
+ last.style.width = '104px';
+ btn.style.width = '104px';
+ lastBr.parentNode.removeChild(lastBr);
+ }
+
+ lastBr = mxUtils.br(div);
+ last = btn;
+ }
+ }
+
+ return count;
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.createStepper = function(input, update, step, height, disableFocus, defaultValue, isFloat)
+{
+ step = (step != null) ? step : 1;
+ height = (height != null) ? height : 9;
+ var bigStep = 10 * step;
+
+ var stepper = document.createElement('div');
+ stepper.className = 'geBtnStepper';
+ stepper.style.position = 'absolute';
+
+ var up = document.createElement('div');
+ up.style.position = 'relative';
+ up.style.height = height + 'px';
+ up.style.width = '10px';
+ up.className = 'geBtnUp';
+ stepper.appendChild(up);
+
+ var down = up.cloneNode(false);
+ down.style.border = 'none';
+ down.style.height = height + 'px';
+ down.className = 'geBtnDown';
+ stepper.appendChild(down);
+
+ mxEvent.addGestureListeners(down, function(evt)
+ {
+ // Stops text selection on shift+click
+ mxEvent.consume(evt);
+ }, null, function(evt)
+ {
+ if (input.value == '')
+ {
+ input.value = defaultValue || '2';
+ }
+
+ var val = isFloat? parseFloat(input.value) : parseInt(input.value);
+
+ if (!isNaN(val))
+ {
+ input.value = val - (mxEvent.isShiftDown(evt) ? bigStep : step);
+
+ if (update != null)
+ {
+ update(evt);
+ }
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addGestureListeners(up, function(evt)
+ {
+ // Stops text selection on shift+click
+ mxEvent.consume(evt);
+ }, null, function(evt)
+ {
+ if (input.value == '')
+ {
+ input.value = defaultValue || '0';
+ }
+
+ var val = isFloat? parseFloat(input.value) : parseInt(input.value);
+
+ if (!isNaN(val))
+ {
+ input.value = val + (mxEvent.isShiftDown(evt) ? bigStep : step);
+
+ if (update != null)
+ {
+ update(evt);
+ }
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ // Disables transfer of focus to DIV but also :active CSS
+ // so it's only used for fontSize where the focus should
+ // stay on the selected text, but not for any other input.
+ if (disableFocus)
+ {
+ var currentSelection = null;
+
+ mxEvent.addGestureListeners(stepper,
+ function(evt)
+ {
+ mxEvent.consume(evt);
+ },
+ null,
+ function(evt)
+ {
+ // Workaround for lost current selection in page because of focus in IE
+ if (currentSelection != null)
+ {
+ try
+ {
+ currentSelection.select();
+ }
+ catch (e)
+ {
+ // ignore
+ }
+
+ currentSelection = null;
+ mxEvent.consume(evt);
+ }
+ }
+ );
+ }
+ else
+ {
+ // Stops propagation on checkbox labels
+ mxEvent.addListener(stepper, 'click', function(evt)
+ {
+ mxEvent.consume(evt);
+ });
+ }
+
+ return stepper;
+};
+
+/**
+ * Adds the given option.
+ */
+BaseFormatPanel.prototype.createOption = function(label, isCheckedFn, setCheckedFn, listener, fn)
+{
+ var div = document.createElement('div');
+ div.style.display = 'flex';
+ div.style.alignItems = 'center';
+ div.style.padding = '3px 0px 3px 0px';
+ div.style.height = '18px';
+
+ var cb = document.createElement('input');
+ cb.setAttribute('type', 'checkbox');
+ cb.style.margin = '1px 6px 0px 0px';
+ cb.style.verticalAlign = 'top';
+ div.appendChild(cb);
+
+ var elt = document.createElement('div');
+ elt.style.display = 'inline-block';
+ elt.style.whiteSpace = 'nowrap';
+ elt.style.textOverflow = 'ellipsis';
+ elt.style.overflow = 'hidden';
+ elt.style.maxWidth = '160px';
+ elt.style.maxWidth = '160px';
+ elt.style.userSelect = 'none';
+ mxUtils.write(elt, label);
+ div.appendChild(elt);
+
+ var applying = false;
+ var value = isCheckedFn();
+
+ var apply = function(newValue, evt)
+ {
+ if (!applying)
+ {
+ applying = true;
+
+ if (newValue)
+ {
+ cb.setAttribute('checked', 'checked');
+ cb.defaultChecked = true;
+ cb.checked = true;
+ }
+ else
+ {
+ cb.removeAttribute('checked');
+ cb.defaultChecked = false;
+ cb.checked = false;
+ }
+
+ if (value != newValue)
+ {
+ value = newValue;
+
+ // Checks if the color value needs to be updated in the model
+ if (isCheckedFn() != value)
+ {
+ setCheckedFn(value, evt);
+ }
+ }
+
+ applying = false;
+ }
+ };
+
+ mxEvent.addListener(div, 'click', function(evt)
+ {
+ if (cb.getAttribute('disabled') != 'disabled')
+ {
+ // Toggles checkbox state for click on label
+ var source = mxEvent.getSource(evt);
+
+ if (source == div || source == elt)
+ {
+ cb.checked = !cb.checked;
+ }
+
+ apply(cb.checked, evt);
+ }
+ });
+
+ apply(value);
+
+ if (listener != null)
+ {
+ listener.install(apply);
+ this.listeners.push(listener);
+ }
+
+ if (fn != null)
+ {
+ fn(div);
+ }
+
+ return div;
+};
+
+/**
+ * The string 'null' means use null in values.
+ */
+BaseFormatPanel.prototype.createCellOption = function(label, key, defaultValue, enabledValue, disabledValue, fn, action, stopEditing, cells)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+
+ enabledValue = (enabledValue != null) ? ((enabledValue == 'null') ? null : enabledValue) : 1;
+ disabledValue = (disabledValue != null) ? ((disabledValue == 'null') ? null : disabledValue) : 0;
+
+ var style = (cells != null) ? graph.getCommonStyle(cells) : ui.getSelectionState().style;
+
+ return this.createOption(label, function()
+ {
+ return mxUtils.getValue(style, key, defaultValue) != disabledValue;
+ }, function(checked)
+ {
+ if (stopEditing)
+ {
+ graph.stopEditing();
+ }
+
+ if (action != null)
+ {
+ action.funct();
+ }
+ else
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var temp = (cells != null) ? cells : ui.getSelectionState().cells;
+ var value = (checked) ? enabledValue : disabledValue;
+ graph.setCellStyles(key, value, temp);
+
+ if (fn != null)
+ {
+ fn(temp, value);
+ }
+
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys',
+ [key], 'values', [value], 'cells', temp));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }
+ },
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ apply(mxUtils.getValue(style, key, defaultValue) != disabledValue);
+ };
+
+ graph.getModel().addListener(mxEvent.CHANGE, this.listener);
+ },
+ destroy: function()
+ {
+ graph.getModel().removeListener(this.listener);
+ }
+ });
+};
+
+/**
+ * Adds the given color option.
+ */
+BaseFormatPanel.prototype.createColorOption = function(label, getColorFn, setColorFn,
+ defaultColor, listener, callbackFn, hideCheckbox, defaultColorValue)
+{
+ var graph = this.editorUi.editor.graph;
+
+ var div = document.createElement('div');
+ div.style.padding = '3px 0px 3px 0px';
+ div.style.whiteSpace = 'nowrap';
+ div.style.overflow = 'hidden';
+ div.style.width = '200px';
+ div.style.height = '18px';
+
+ var cb = document.createElement('input');
+ cb.setAttribute('type', 'checkbox');
+ cb.style.margin = '1px 6px 0px 0px';
+ cb.style.verticalAlign = 'top';
+
+ if (!hideCheckbox)
+ {
+ div.appendChild(cb);
+ }
+
+ var span = document.createElement('span');
+ span.style.verticalAlign = 'top';
+ mxUtils.write(span, label);
+ div.appendChild(span);
+
+ var value = getColorFn();
+ var applying = false;
+ var dropper = null;
+ var btn = null;
+
+ var clrInput = document.createElement('input');
+ clrInput.setAttribute('type', 'color');
+ clrInput.style.position = 'relative';
+ clrInput.style.visibility = 'hidden';
+ clrInput.style.top = '10px';
+ clrInput.style.width = '0px';
+ clrInput.style.height = '0px';
+ clrInput.style.border = 'none';
+
+ // Adds native color dialog
+ if (!mxClient.IS_IE && !mxClient.IS_IE11 && !mxClient.IS_TOUCH)
+ {
+ dropper = document.createElement('img');
+ dropper.src = Editor.colorDropperImage;
+ dropper.className = 'geColorDropper geAdaptiveAsset';
+ dropper.style.position = 'relative';
+ dropper.style.right = '-20px';
+ dropper.style.top = '-1px';
+ dropper.style.width = 'auto';
+ dropper.style.height = '14px';
+
+ mxEvent.addListener(dropper, 'click', function(evt)
+ {
+ var color = value;
+
+ if (color == 'default')
+ {
+ color = defaultColorValue;
+ }
+
+ clrInput.value = color;
+ clrInput.click();
+
+ mxEvent.consume(evt);
+ });
+ }
+
+ var apply = function(color, disableUpdate, forceUpdate)
+ {
+ if (!applying)
+ {
+ var defaultValue = (defaultColor == 'null') ? null : defaultColor;
+
+ applying = true;
+ color = (/(^#?[a-zA-Z0-9]*$)/.test(color)) ? color : defaultValue;
+ var tempColor = (color != null && color != mxConstants.NONE) ? color : defaultValue;
+
+ var div = document.createElement('div');
+ div.style.width = '21px';
+ div.style.height = '12px';
+ div.style.margin = '2px 18px 2px 3px';
+ div.style.border = '1px solid black';
+ div.style.backgroundColor = (tempColor == 'default') ? defaultColorValue : tempColor;
+ btn.innerText = '';
+ btn.appendChild(div);
+
+ if (dropper != null)
+ {
+ div.style.width = '21px';
+ div.style.margin = '2px 18px 2px 3px';
+ div.appendChild(dropper);
+ }
+ else
+ {
+ div.style.width = '36px';
+ div.style.margin = '3px';
+ }
+
+ if (color != null && color != mxConstants.NONE && color.length > 1 && typeof color === 'string')
+ {
+ var clr = (color.charAt(0) == '#') ? color.substring(1).toUpperCase() : color;
+ var name = ColorDialog.prototype.colorNames[clr];
+
+ if (name != null)
+ {
+ btn.setAttribute('title', name);
+ }
+ }
+
+ if (color != null && color != mxConstants.NONE &&
+ !graph.isSpecialColor(color))
+ {
+ cb.setAttribute('checked', 'checked');
+ cb.defaultChecked = true;
+ cb.checked = true;
+ }
+ else
+ {
+ cb.removeAttribute('checked');
+ cb.defaultChecked = false;
+ cb.checked = false;
+ }
+
+ btn.style.display = (cb.checked || hideCheckbox) ? '' : 'none';
+
+ if (callbackFn != null)
+ {
+ callbackFn(color == 'null' ? null : color);
+ }
+
+ value = color;
+
+ if (!disableUpdate)
+ {
+ // Checks if the color value needs to be updated in the model
+ if (forceUpdate || hideCheckbox || getColorFn() != value)
+ {
+ setColorFn(value == 'null' ? null : value, value);
+ }
+ }
+
+ applying = false;
+ }
+ };
+
+ div.appendChild(clrInput);
+
+ mxEvent.addListener(clrInput, 'change', function()
+ {
+ apply(clrInput.value, null, true);
+ });
+
+ btn = mxUtils.button('', mxUtils.bind(this, function(evt)
+ {
+ var color = value;
+
+ if (color == 'default')
+ {
+ color = defaultColorValue;
+ }
+
+ this.editorUi.pickColor(color, function(newColor)
+ {
+ apply(newColor, null, true);
+ }, defaultColorValue);
+
+ mxEvent.consume(evt);
+ }));
+
+ btn.style.position = 'absolute';
+ btn.style.marginTop = '-3px';
+ btn.style.left = '178px';
+ btn.style.height = '22px';
+ btn.className = 'geColorBtn';
+ btn.style.display = (cb.checked || hideCheckbox) ? '' : 'none';
+ div.appendChild(btn);
+
+ var clr = (value != null && typeof value === 'string' && value.charAt(0) == '#') ? value.substring(1).toUpperCase() : value;
+ var name = ColorDialog.prototype.colorNames[clr];
+
+ if (name != null)
+ {
+ btn.setAttribute('title', name);
+ }
+
+ mxEvent.addListener(div, 'click', function(evt)
+ {
+ var source = mxEvent.getSource(evt);
+
+ if (source == cb || source.nodeName != 'INPUT')
+ {
+ // Toggles checkbox state for click on label
+ if (source != cb)
+ {
+ cb.checked = !cb.checked;
+ }
+
+ // Overrides default value with current value to make it easier
+ // to restore previous value if the checkbox is clicked twice
+ if (!cb.checked && value != null && value != mxConstants.NONE &&
+ defaultColor != mxConstants.NONE)
+ {
+ defaultColor = value;
+ }
+
+ apply((cb.checked) ? defaultColor : mxConstants.NONE);
+ }
+ });
+
+ apply(value, true);
+
+ if (listener != null)
+ {
+ listener.install(apply);
+ this.listeners.push(listener);
+ }
+
+ return div;
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.createCellColorOption = function(label, colorKey, defaultColor, callbackFn, setStyleFn, defaultColorValue)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+
+ return this.createColorOption(label, function()
+ {
+ // Seems to be null sometimes, not sure why...
+ var state = graph.view.getState(ui.getSelectionState().cells[0]);
+
+ if (state != null)
+ {
+ return mxUtils.getValue(state.style, colorKey, null);
+ }
+
+ return null;
+ }, function(color)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = ui.getSelectionState().cells;
+ graph.setCellStyles(colorKey, color, cells);
+
+ if (setStyleFn != null)
+ {
+ setStyleFn(color);
+ }
+
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [colorKey],
+ 'values', [color], 'cells', cells));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }, defaultColor || mxConstants.NONE,
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ // Seems to be null sometimes, not sure why...
+ var state = graph.view.getState(ui.getSelectionState().cells[0]);
+
+ if (state != null)
+ {
+ apply(mxUtils.getValue(state.style, colorKey, null), true);
+ }
+ };
+
+ graph.getModel().addListener(mxEvent.CHANGE, this.listener);
+ },
+ destroy: function()
+ {
+ graph.getModel().removeListener(this.listener);
+ }
+ }, callbackFn, null, defaultColorValue);
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.addArrow = function(elt)
+{
+ elt.className = 'geColorBtn';
+ elt.style.display = 'inline-flex';
+ elt.style.alignItems = 'top';
+ elt.style.boxSizing = 'border-box';
+ elt.style.width = '64px';
+ elt.style.height = '22px';
+ elt.style.borderWidth = '1px';
+ elt.style.borderStyle = 'solid';
+ elt.style.margin = '2px 2px 2px 3px';
+
+ var arrow = document.createElement('div');
+ arrow.className = 'geAdaptiveAsset';
+ arrow.style.display = 'inline-block';
+ arrow.style.backgroundImage = 'url(' + Editor.thinExpandImage + ')';
+ arrow.style.backgroundRepeat = 'no-repeat';
+ arrow.style.backgroundPosition = '-2px 1px';
+ arrow.style.backgroundSize = '18px 18px';
+ arrow.style.opacity = '0.5';
+ arrow.style.height = '100%';
+ arrow.style.width = '14px';
+
+ elt.appendChild(arrow);
+
+ var symbol = elt.getElementsByTagName('div')[0];
+
+ if (symbol != null)
+ {
+ symbol.style.display = 'inline-block';
+ symbol.style.backgroundPositionX = 'center';
+ symbol.style.textAlign = 'center';
+ symbol.style.height = '100%';
+ symbol.style.flexGrow = '1';
+ symbol.style.opacity = '0.6';
+ }
+
+ return symbol;
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.addUnitInput = function(container, unit, right, width, update, step, marginTop, disableFocus, isFloat)
+{
+ marginTop = (marginTop != null) ? marginTop : 0;
+
+ var input = document.createElement('input');
+ input.style.position = 'absolute';
+ input.style.textAlign = 'right';
+ input.style.marginTop = '-2px';
+ input.style.left = (228 - right - width) + 'px';
+ input.style.width = width + 'px';
+ input.style.height = '21px';
+ input.style.borderWidth = '1px';
+ input.style.borderStyle = 'solid';
+ input.style.boxSizing = 'border-box';
+
+ container.appendChild(input);
+
+ var stepper = this.createStepper(input, update, step, null, disableFocus, null, isFloat);
+ stepper.style.marginTop = (marginTop - 2) + 'px';
+ stepper.style.left = (228 - right) + 'px';
+ container.appendChild(stepper);
+
+ return input;
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.addGenericInput = function(container, unit, left, width, readFn, writeFn)
+{
+ var graph = this.editorUi.editor.graph;
+
+ var update = function()
+ {
+ writeFn(input.value);
+ };
+
+ var input = this.addUnitInput(container, unit, left, width, update);
+
+ var listener = mxUtils.bind(this, function(sender, evt, force)
+ {
+ if (force || input != document.activeElement)
+ {
+ input.value = readFn() + unit;
+ }
+ });
+
+ mxEvent.addListener(input, 'keydown', function(e)
+ {
+ if (e.keyCode == 13)
+ {
+ graph.container.focus();
+ mxEvent.consume(e);
+ }
+ else if (e.keyCode == 27)
+ {
+ listener(null, null, true);
+ graph.container.focus();
+ mxEvent.consume(e);
+ }
+ });
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+ listener();
+
+ mxEvent.addListener(input, 'blur', update);
+ mxEvent.addListener(input, 'change', update);
+
+ return input;
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.createRelativeOption = function(label, key, width, handler, init)
+{
+ width = (width != null) ? width : 52;
+
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+ var div = this.createPanel();
+ div.style.paddingTop = '10px';
+ div.style.paddingBottom = '12px';
+ mxUtils.write(div, label);
+ div.style.fontWeight = 'bold';
+
+ var update = mxUtils.bind(this, function(evt)
+ {
+ if (handler != null)
+ {
+ handler(input);
+ }
+ else
+ {
+ var value = parseInt(input.value);
+ value = Math.min(100, Math.max(0, (isNaN(value)) ? 100 : value));
+ var state = graph.view.getState(ui.getSelectionState().cells[0]);
+
+ if (state != null && value != mxUtils.getValue(state.style, key, 100))
+ {
+ // Removes entry in style (assumes 100 is default for relative values)
+ if (value == 100)
+ {
+ value = null;
+ }
+
+ var cells = ui.getSelectionState().cells;
+ graph.setCellStyles(key, value, cells);
+ this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', [key],
+ 'values', [value], 'cells', cells));
+ }
+
+ input.value = ((value != null) ? value : '100') + ' %';
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ var input = this.addUnitInput(div, '%', 16, width, update, 10,
+ (mxClient.IS_MAC && mxClient.IS_GC) ? -14 :
+ ((mxClient.IS_WIN) ? -16 : -15), handler != null);
+
+ if (key != null)
+ {
+ var listener = mxUtils.bind(this, function(sender, evt, force)
+ {
+ if (force || input != document.activeElement)
+ {
+ var ss = ui.getSelectionState();
+ var tmp = parseInt(mxUtils.getValue(ss.style, key, 100));
+ input.value = (isNaN(tmp)) ? '' : tmp + ' %';
+ }
+ });
+
+ mxEvent.addListener(input, 'keydown', function(e)
+ {
+ if (e.keyCode == 13)
+ {
+ graph.container.focus();
+ mxEvent.consume(e);
+ }
+ else if (e.keyCode == 27)
+ {
+ listener(null, null, true);
+ graph.container.focus();
+ mxEvent.consume(e);
+ }
+ });
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+ listener();
+ }
+
+ mxEvent.addListener(input, 'blur', update);
+ mxEvent.addListener(input, 'change', update);
+
+ if (init != null)
+ {
+ init(input);
+ }
+
+ return div;
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.addLabel = function(div, title, right, width)
+{
+ width = (width != null) ? width : 61;
+
+ var label = document.createElement('div');
+ mxUtils.write(label, title);
+ label.style.position = 'absolute';
+ label.style.left = (240 - right - width) + 'px';
+ label.style.width = width + 'px';
+ label.style.marginTop = '6px';
+ label.style.display = 'flex';
+ label.style.justifyContent = 'center';
+ div.appendChild(label);
+
+ return label;
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.addKeyHandler = function(input, listener)
+{
+ mxEvent.addListener(input, 'keydown', mxUtils.bind(this, function(e)
+ {
+ if (e.keyCode == 13)
+ {
+ this.editorUi.editor.graph.container.focus();
+ mxEvent.consume(e);
+ }
+ else if (e.keyCode == 27)
+ {
+ if (listener != null)
+ {
+ listener(null, null, true);
+ }
+
+ this.editorUi.editor.graph.container.focus();
+ mxEvent.consume(e);
+ }
+ }));
+};
+
+/**
+ *
+ */
+BaseFormatPanel.prototype.styleButtons = function(elts)
+{
+ for (var i = 0; i < elts.length; i++)
+ {
+ mxUtils.setPrefixedStyle(elts[i].style, 'borderRadius', '3px');
+ mxUtils.setOpacity(elts[i], 100);
+ elts[i].style.border = '1px solid #a0a0a0';
+ elts[i].style.padding = '4px';
+ elts[i].style.paddingTop = '3px';
+ elts[i].style.paddingRight = '1px';
+ elts[i].style.margin = '1px';
+ elts[i].style.marginRight = '2px';
+ elts[i].style.width = '24px';
+ elts[i].style.height = '20px';
+ elts[i].className += ' geColorBtn';
+ }
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+BaseFormatPanel.prototype.destroy = function()
+{
+ if (this.listeners != null)
+ {
+ for (var i = 0; i < this.listeners.length; i++)
+ {
+ this.listeners[i].destroy();
+ }
+
+ this.listeners = null;
+ }
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+ArrangePanel = function(format, editorUi, container)
+{
+ BaseFormatPanel.call(this, format, editorUi, container);
+ this.init();
+};
+
+mxUtils.extend(ArrangePanel, BaseFormatPanel);
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+ArrangePanel.prototype.init = function()
+{
+ var ss = this.editorUi.getSelectionState();
+
+ if (ss.cells.length > 0)
+ {
+ this.container.appendChild(this.addLayerOps(this.createPanel()));
+
+ // Special case that adds two panels
+ this.addGeometry(this.container);
+ this.addEdgeGeometry(this.container);
+
+ if (!ss.containsLabel || ss.edges.length == 0)
+ {
+ this.container.appendChild(this.addAngle(this.createPanel()));
+ }
+
+ if (!ss.containsLabel)
+ {
+ this.container.appendChild(this.addFlip(this.createPanel()));
+ }
+
+ this.container.appendChild(this.addAlign(this.createPanel()));
+
+ if (ss.vertices.length > 1 && !ss.cell && !ss.row)
+ {
+ this.container.appendChild(this.addDistribute(this.createPanel()));
+ }
+
+ this.container.appendChild(this.addTable(this.createPanel()));
+ }
+
+ // Allows to lock/unload button to be added
+ this.container.appendChild(this.addGroupOps(this.createPanel()));
+
+ if (ss.containsLabel)
+ {
+ // Adds functions from hidden style format panel
+ var span = document.createElement('div');
+ span.style.width = '100%';
+ span.style.marginTop = '0px';
+ span.style.fontWeight = 'bold';
+ span.style.padding = '10px 0 0 14px';
+ mxUtils.write(span, mxResources.get('style'));
+ this.container.appendChild(span);
+
+ new StyleFormatPanel(this.format, this.editorUi, this.container);
+ }
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addTable = function(div)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+ var ss = ui.getSelectionState();
+ div.style.paddingTop = '6px';
+ div.style.paddingBottom = '10px';
+
+ var span = document.createElement('div');
+ span.style.marginTop = '0px';
+ span.style.marginBottom = '6px';
+ span.style.fontWeight = 'bold';
+ mxUtils.write(span, mxResources.get('table'));
+ div.appendChild(span);
+
+ var panel = document.createElement('div');
+ panel.style.position = 'relative';
+ panel.style.paddingLeft = '0px';
+ panel.style.borderWidth = '0px';
+ panel.style.width = '220px';
+ panel.className = 'geToolbarContainer';
+
+ var cell = ss.vertices[0];
+
+ if (graph.getSelectionCount() > 1)
+ {
+ if (graph.isTableCell(cell))
+ {
+ cell = graph.model.getParent(cell);
+ }
+
+ if (graph.isTableRow(cell))
+ {
+ cell = graph.model.getParent(cell);
+ }
+ }
+
+ var isTable = ss.table || ss.row || ss.cell;
+ var isStack = graph.isStack(cell) ||
+ graph.isStackChild(cell);
+
+ var showCols = isTable;
+ var showRows = isTable;
+
+ if (isStack)
+ {
+ var style = (graph.isStack(cell)) ? ss.style :
+ graph.getCellStyle(graph.model.getParent(cell));
+
+ showRows = style['horizontalStack'] == '0';
+ showCols = !showRows;
+ }
+
+ var btns = [];
+
+ if (showCols)
+ {
+ btns = btns.concat([
+ ui.toolbar.addButton('geSprite-insertcolumnbefore', mxResources.get('insertColumnBefore'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (isStack)
+ {
+ graph.insertLane(cell, true);
+ }
+ else
+ {
+ graph.insertTableColumn(cell, true);
+ }
+ }
+ catch (e)
+ {
+ ui.handleError(e);
+ }
+ }), panel),
+ ui.toolbar.addButton('geSprite-insertcolumnafter', mxResources.get('insertColumnAfter'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (isStack)
+ {
+ graph.insertLane(cell, false);
+ }
+ else
+ {
+ graph.insertTableColumn(cell, false);
+ }
+ }
+ catch (e)
+ {
+ ui.handleError(e);
+ }
+ }), panel),
+ ui.toolbar.addButton('geSprite-deletecolumn', mxResources.get('deleteColumn'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (isStack)
+ {
+ graph.deleteLane(cell);
+ }
+ else
+ {
+ graph.deleteTableColumn(cell);
+ }
+ }
+ catch (e)
+ {
+ ui.handleError(e);
+ }
+ }), panel)]);
+ }
+
+ if (showRows)
+ {
+ btns = btns.concat([ui.toolbar.addButton('geSprite-insertrowbefore', mxResources.get('insertRowBefore'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (isStack)
+ {
+ graph.insertLane(cell, true);
+ }
+ else
+ {
+ graph.insertTableRow(cell, true);
+ }
+ }
+ catch (e)
+ {
+ ui.handleError(e);
+ }
+ }), panel),
+ ui.toolbar.addButton('geSprite-insertrowafter', mxResources.get('insertRowAfter'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (isStack)
+ {
+ graph.insertLane(cell, false);
+ }
+ else
+ {
+ graph.insertTableRow(cell, false);
+ }
+ }
+ catch (e)
+ {
+ ui.handleError(e);
+ }
+ }), panel),
+ ui.toolbar.addButton('geSprite-deleterow', mxResources.get('deleteRow'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (isStack)
+ {
+ graph.deleteLane(cell);
+ }
+ else
+ {
+ graph.deleteTableRow(cell);
+ }
+ }
+ catch (e)
+ {
+ ui.handleError(e);
+ }
+ }), panel)]);
+ }
+
+ if (btns.length > 0)
+ {
+ this.styleButtons(btns);
+ div.appendChild(panel);
+
+ if (btns.length > 3)
+ {
+ btns[2].style.marginRight = '10px';
+ }
+
+ var count = 0;
+
+ if (ss.mergeCell != null)
+ {
+ count += this.addActions(div, ['mergeCells']);
+ }
+ else if (ss.style['colspan'] > 1 || ss.style['rowspan'] > 1)
+ {
+ count += this.addActions(div, ['unmergeCells']);
+ }
+
+ if (count > 0)
+ {
+ panel.style.paddingBottom = '2px';
+ }
+ }
+ else
+ {
+ div.style.display = 'none';
+ }
+
+ return div;
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addLayerOps = function(div)
+{
+ this.addActions(div, ['toFront', 'toBack']);
+ this.addActions(div, ['bringForward', 'sendBackward']);
+
+ return div;
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addGroupOps = function(div)
+{
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+ var ss = ui.getSelectionState();
+
+ div.style.paddingTop = '8px';
+ div.style.paddingBottom = '6px';
+
+ var count = 0;
+
+ if (!ss.cell && !ss.row)
+ {
+ count += this.addActions(div, ['group', 'ungroup', 'copySize', 'pasteSize']) +
+ this.addActions(div, ['removeFromGroup']);
+ }
+
+ var clearWaypoints = this.addAction(div, 'clearWaypoints');
+
+ if (clearWaypoints != null)
+ {
+ mxUtils.br(div);
+ clearWaypoints.setAttribute('title', mxResources.get('clearWaypoints') +
+ ' (' + this.editorUi.actions.get('clearWaypoints').shortcut + ')' +
+ ' Shift+Click to Clear Anchor Points');
+ count++;
+ }
+
+ count += this.addActions(div, ['lockUnlock']);
+
+ if (count == 0)
+ {
+ div.style.display = 'none';
+ }
+
+ return div;
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addAlign = function(div)
+{
+ var ss = this.editorUi.getSelectionState();
+ var graph = this.editorUi.editor.graph;
+ div.style.paddingTop = '6px';
+ div.style.paddingBottom = '8px';
+ div.appendChild(this.createTitle(mxResources.get('align')));
+
+ var stylePanel = document.createElement('div');
+ stylePanel.style.position = 'relative';
+ stylePanel.style.whiteSpace = 'nowrap';
+ stylePanel.style.paddingLeft = '0px';
+ stylePanel.style.paddingBottom = '2px';
+ stylePanel.style.borderWidth = '0px';
+ stylePanel.style.width = '220px';
+ stylePanel.className = 'geToolbarContainer';
+
+ if (ss.vertices.length > 1)
+ {
+ var left = this.editorUi.toolbar.addButton('geSprite-alignleft', mxResources.get('left'),
+ function() { graph.alignCells(mxConstants.ALIGN_LEFT); }, stylePanel);
+ var center = this.editorUi.toolbar.addButton('geSprite-aligncenter', mxResources.get('center'),
+ function() { graph.alignCells(mxConstants.ALIGN_CENTER); }, stylePanel);
+ var right = this.editorUi.toolbar.addButton('geSprite-alignright', mxResources.get('right'),
+ function() { graph.alignCells(mxConstants.ALIGN_RIGHT); }, stylePanel);
+
+ var top = this.editorUi.toolbar.addButton('geSprite-aligntop', mxResources.get('top'),
+ function() { graph.alignCells(mxConstants.ALIGN_TOP); }, stylePanel);
+ var middle = this.editorUi.toolbar.addButton('geSprite-alignmiddle', mxResources.get('middle'),
+ function() { graph.alignCells(mxConstants.ALIGN_MIDDLE); }, stylePanel);
+ var bottom = this.editorUi.toolbar.addButton('geSprite-alignbottom', mxResources.get('bottom'),
+ function() { graph.alignCells(mxConstants.ALIGN_BOTTOM); }, stylePanel);
+
+ this.styleButtons([left, center, right, top, middle, bottom]);
+ right.style.marginRight = '10px';
+ }
+
+ div.appendChild(stylePanel);
+ this.addActions(div, ['snapToGrid']);
+
+ return div;
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addFlip = function(div)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+ div.style.paddingTop = '6px';
+ div.style.paddingBottom = '10px';
+ var ss = this.editorUi.getSelectionState();
+
+ var span = document.createElement('div');
+ span.style.marginTop = '2px';
+ span.style.marginBottom = '8px';
+ span.style.fontWeight = 'bold';
+ mxUtils.write(span, mxResources.get('flip'));
+ div.appendChild(span);
+
+ var btn = mxUtils.button(mxResources.get('horizontal'), function(evt)
+ {
+ graph.flipCells(ss.cells, true);
+ })
+
+ btn.setAttribute('title', mxResources.get('horizontal'));
+ btn.style.width = '104px';
+ btn.style.marginRight = '2px';
+ div.appendChild(btn);
+
+ var btn = mxUtils.button(mxResources.get('vertical'), function(evt)
+ {
+ graph.flipCells(ss.cells, false);
+ })
+
+ btn.setAttribute('title', mxResources.get('vertical'));
+ btn.style.width = '104px';
+ div.appendChild(btn);
+
+ return div;
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addDistribute = function(div)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+ div.style.paddingTop = '6px';
+ div.style.paddingBottom = '8px';
+
+ div.appendChild(this.createTitle(mxResources.get('distribute')));
+
+ var btn = mxUtils.button(mxResources.get('horizontal'), function(evt)
+ {
+ graph.distributeCells(true, null, cb.checked);
+ })
+
+ btn.setAttribute('title', mxResources.get('horizontal'));
+ btn.style.width = '104px';
+ btn.style.marginRight = '2px';
+ div.appendChild(btn);
+
+ var btn = mxUtils.button(mxResources.get('vertical'), function(evt)
+ {
+ graph.distributeCells(false, null, cb.checked);
+ })
+
+ btn.setAttribute('title', mxResources.get('vertical'));
+ btn.style.width = '104px';
+ div.appendChild(btn);
+
+ mxUtils.br(div);
+
+ var panel = document.createElement('div');
+ panel.style.margin = '6px 0 0 0';
+ panel.style.display = 'flex';
+ panel.style.justifyContent = 'center';
+ panel.style.alignItems = 'center';
+
+ var cb = document.createElement('input');
+ cb.setAttribute('type', 'checkbox');
+ cb.setAttribute('id', 'spacingCheckbox');
+ cb.style.margin = '0 6px 0 0';
+ panel.appendChild(cb);
+
+ var label = document.createElement('label');
+ label.style.verticalAlign = 'top';
+ label.setAttribute('for', 'spacingCheckbox');
+ label.style.userSelect = 'none';
+ mxUtils.write(label, mxResources.get('spacing'));
+ panel.appendChild(label);
+ div.appendChild(panel);
+
+ return div;
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addAngle = function(div)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+ var ss = ui.getSelectionState();
+
+ div.style.paddingBottom = '12px';
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.width = '70px';
+ span.style.marginTop = '0px';
+ span.style.fontWeight = 'bold';
+
+ var input = null;
+ var update = null;
+ var btn = null;
+
+ if (ss.rotatable && !ss.table && !ss.row && !ss.cell)
+ {
+ mxUtils.write(span, mxResources.get('angle'));
+ div.appendChild(span);
+
+ input = this.addUnitInput(div, '°', 16, 52, function()
+ {
+ update.apply(this, arguments);
+ });
+
+ mxUtils.br(div);
+ div.style.paddingTop = '10px';
+ }
+ else
+ {
+ div.style.paddingTop = '8px';
+ }
+
+ if (!ss.containsLabel)
+ {
+ var label = mxResources.get('reverse');
+
+ if (ss.vertices.length > 0 && ss.edges.length > 0)
+ {
+ label = mxResources.get('turn') + ' / ' + label;
+ }
+ else if (ss.vertices.length > 0)
+ {
+ label = mxResources.get('turn');
+ }
+
+ btn = mxUtils.button(label, function(evt)
+ {
+ ui.actions.get('turn').funct(evt);
+ })
+
+ btn.setAttribute('title', label + ' (' + this.editorUi.actions.get('turn').shortcut + ')');
+ btn.style.width = '210px';
+ div.appendChild(btn);
+
+ if (input != null)
+ {
+ btn.style.marginTop = '8px';
+ }
+ }
+
+ if (input != null)
+ {
+ var listener = mxUtils.bind(this, function(sender, evt, force)
+ {
+ if (force || document.activeElement != input)
+ {
+ ss = ui.getSelectionState();
+ var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_ROTATION, 0));
+ input.value = (isNaN(tmp)) ? '' : tmp + '°';
+ }
+ });
+
+ update = this.installInputHandler(input, mxConstants.STYLE_ROTATION, 0, 0, 360, '°', null, true);
+ this.addKeyHandler(input, listener);
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+ listener();
+ }
+
+ return div;
+};
+
+BaseFormatPanel.prototype.getUnit = function()
+{
+ var unit = this.editorUi.editor.graph.view.unit;
+
+ switch(unit)
+ {
+ case mxConstants.POINTS:
+ return 'pt';
+ case mxConstants.INCHES:
+ return '"';
+ case mxConstants.MILLIMETERS:
+ return 'mm';
+ case mxConstants.METERS:
+ return 'm';
+ }
+};
+
+BaseFormatPanel.prototype.inUnit = function(pixels)
+{
+ return this.editorUi.editor.graph.view.formatUnitText(pixels);
+};
+
+BaseFormatPanel.prototype.fromUnit = function(value)
+{
+ var unit = this.editorUi.editor.graph.view.unit;
+
+ switch(unit)
+ {
+ case mxConstants.POINTS:
+ return value;
+ case mxConstants.INCHES:
+ return value * mxConstants.PIXELS_PER_INCH;
+ case mxConstants.MILLIMETERS:
+ return value * mxConstants.PIXELS_PER_MM;
+ case mxConstants.METERS:
+ return value * mxConstants.PIXELS_PER_MM * 1000;
+ }
+};
+
+BaseFormatPanel.prototype.isFloatUnit = function()
+{
+ return this.editorUi.editor.graph.view.unit != mxConstants.POINTS;
+};
+
+BaseFormatPanel.prototype.getUnitStep = function()
+{
+ var unit = this.editorUi.editor.graph.view.unit;
+
+ switch(unit)
+ {
+ case mxConstants.POINTS:
+ return 1;
+ case mxConstants.INCHES:
+ return 0.1;
+ case mxConstants.MILLIMETERS:
+ return 0.5;
+ case mxConstants.METERS:
+ return 0.001;
+ }
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addGeometry = function(container)
+{
+ var panel = this;
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+ var model = graph.getModel();
+ var rect = ui.getSelectionState();
+
+ var div = this.createPanel();
+ div.style.paddingBottom = '8px';
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.width = '50px';
+ span.style.marginTop = '0px';
+ span.style.fontWeight = 'bold';
+ mxUtils.write(span, mxResources.get('size'));
+ div.appendChild(span);
+
+ var widthUpdate, heightUpdate, leftUpdate, topUpdate;
+ var width = this.addUnitInput(div, this.getUnit(), 87, 52, function()
+ {
+ widthUpdate.apply(this, arguments);
+ }, this.getUnitStep(), null, null, this.isFloatUnit());
+ var height = this.addUnitInput(div, this.getUnit(), 16, 52, function()
+ {
+ heightUpdate.apply(this, arguments);
+ }, this.getUnitStep(), null, null, this.isFloatUnit());
+
+ var autosizeBtn = document.createElement('div');
+ autosizeBtn.className = 'geSprite geSprite-fit';
+ autosizeBtn.setAttribute('title', mxResources.get('autosize') + ' (' + this.editorUi.actions.get('autosize').shortcut + ')');
+ autosizeBtn.style.position = 'relative';
+ autosizeBtn.style.cursor = 'pointer';
+ autosizeBtn.style.marginTop = '-3px';
+ autosizeBtn.style.border = '0px';
+ autosizeBtn.style.left = '42px';
+ mxUtils.setOpacity(autosizeBtn, 50);
+
+ mxEvent.addListener(autosizeBtn, 'mouseenter', function()
+ {
+ mxUtils.setOpacity(autosizeBtn, 100);
+ });
+
+ mxEvent.addListener(autosizeBtn, 'mouseleave', function()
+ {
+ mxUtils.setOpacity(autosizeBtn, 50);
+ });
+
+ mxEvent.addListener(autosizeBtn, 'click', function()
+ {
+ ui.actions.get('autosize').funct();
+ });
+
+ div.appendChild(autosizeBtn);
+
+ if (rect.row)
+ {
+ width.style.visibility = 'hidden';
+ width.nextSibling.style.visibility = 'hidden';
+ }
+ else
+ {
+ this.addLabel(div, mxResources.get('width'), 87, 64);
+ }
+
+ this.addLabel(div, mxResources.get('height'), 16, 64);
+ mxUtils.br(div);
+
+ var wrapper = document.createElement('div');
+ wrapper.style.paddingTop = '8px';
+ wrapper.style.paddingRight = '20px';
+ wrapper.style.whiteSpace = 'nowrap';
+ wrapper.style.textAlign = 'right';
+ var opt = this.createCellOption(mxResources.get('constrainProportions'),
+ mxConstants.STYLE_ASPECT, null, 'fixed', 'null');
+ opt.style.width = '210px';
+ wrapper.appendChild(opt);
+
+ if (!rect.cell && !rect.row)
+ {
+ div.appendChild(wrapper);
+ }
+ else
+ {
+ autosizeBtn.style.visibility = 'hidden';
+ }
+
+ var constrainCheckbox = opt.getElementsByTagName('input')[0];
+ this.addKeyHandler(width, listener);
+ this.addKeyHandler(height, listener);
+
+ widthUpdate = this.addGeometryHandler(width, function(geo, value, cell)
+ {
+ if (graph.isTableCell(cell))
+ {
+ graph.setTableColumnWidth(cell, value - geo.width, true);
+
+ // Blocks processing in caller
+ return true;
+ }
+ else if (geo.width > 0)
+ {
+ var value = Math.max(1, panel.fromUnit(value));
+
+ if (constrainCheckbox.checked)
+ {
+ geo.height = Math.round((geo.height * value * 100) / geo.width) / 100;
+ }
+
+ geo.width = value;
+ }
+ });
+ heightUpdate = this.addGeometryHandler(height, function(geo, value, cell)
+ {
+ if (graph.isTableCell(cell))
+ {
+ cell = model.getParent(cell);
+ }
+
+ if (graph.isTableRow(cell))
+ {
+ graph.setTableRowHeight(cell, value - geo.height);
+
+ // Blocks processing in caller
+ return true;
+ }
+ else if (geo.height > 0)
+ {
+ var value = Math.max(1, panel.fromUnit(value));
+
+ if (constrainCheckbox.checked)
+ {
+ geo.width = Math.round((geo.width * value * 100) / geo.height) / 100;
+ }
+
+ geo.height = value;
+ }
+ });
+
+ if (rect.resizable || rect.row || rect.cell)
+ {
+ container.appendChild(div);
+ }
+
+ var div2 = this.createPanel();
+ div2.style.paddingBottom = '30px';
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.width = '70px';
+ span.style.marginTop = '0px';
+ span.style.fontWeight = 'bold';
+ mxUtils.write(span, mxResources.get('position'));
+ div2.appendChild(span);
+
+ var left = this.addUnitInput(div2, this.getUnit(), 87, 52, function()
+ {
+ leftUpdate.apply(this, arguments);
+ }, this.getUnitStep(), null, null, this.isFloatUnit());
+ var top = this.addUnitInput(div2, this.getUnit(), 16, 52, function()
+ {
+ topUpdate.apply(this, arguments);
+ }, this.getUnitStep(), null, null, this.isFloatUnit());
+
+ mxUtils.br(div2);
+
+ var coordinateLabels = true;
+ var dx = null;
+ var dy = null;
+
+ if (rect.movable)
+ {
+ if (rect.edges.length == 0 && rect.vertices.length == 1)
+ {
+ var geo = graph.getCellGeometry(rect.vertices[0]);
+
+ if (geo != null && geo.relative)
+ {
+ mxUtils.br(div2);
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.width = '70px';
+ span.style.marginTop = '0px';
+ mxUtils.write(span, mxResources.get('relative'));
+ div2.appendChild(span);
+
+ dx = this.addGenericInput(div2, ' %', 87, 52, function()
+ {
+ return (Math.round(geo.x * 1000) / 10);
+ }, function(value)
+ {
+ value = parseFloat(value);
+
+ if (!isNaN(value))
+ {
+ model.beginUpdate();
+ try
+ {
+ geo = geo.clone();
+ geo.x = parseFloat(value) / 100;
+ model.setGeometry(rect.vertices[0], geo);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ });
+
+ if (model.isEdge(model.getParent(rect.vertices[0])))
+ {
+ coordinateLabels = false;
+ var dyUpdate = null;
+
+ dy = this.addUnitInput(div2, this.getUnit(), 16, 52, function()
+ {
+ dyUpdate.apply(this, arguments);
+ });
+
+ dyUpdate = this.addGeometryHandler(dy, function(geo, value)
+ {
+ console.log('value', value);
+
+ geo.y = panel.fromUnit(value);
+ });
+ }
+ else
+ {
+ dy = this.addGenericInput(div2, ' %', 16, 52, function()
+ {
+ return (Math.round(geo.y * 1000) / 10);
+ }, function(value)
+ {
+ value = parseFloat(value);
+
+ if (!isNaN(value))
+ {
+ model.beginUpdate();
+ try
+ {
+ geo = geo.clone();
+ geo.y = parseFloat(value) / 100;
+ model.setGeometry(rect.vertices[0], geo);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ });
+ }
+
+ mxUtils.br(div2);
+ }
+ }
+ container.appendChild(div2);
+ }
+
+ this.addLabel(div2, mxResources.get(coordinateLabels ? 'left' : 'line'), 87, 64).style.marginTop = '8px';
+ this.addLabel(div2, mxResources.get(coordinateLabels ? 'top' : 'orthogonal'), 16, 64).style.marginTop = '8px';
+
+ var listener = mxUtils.bind(this, function(sender, evt, force)
+ {
+ rect = ui.getSelectionState();
+
+ if (!rect.containsLabel && rect.vertices.length == graph.getSelectionCount() &&
+ rect.width != null && rect.height != null)
+ {
+ div.style.display = '';
+
+ if (force || document.activeElement != width)
+ {
+ width.value = this.inUnit(rect.width) + ' ' + this.getUnit();
+ }
+
+ if (force || document.activeElement != height)
+ {
+ height.value = this.inUnit(rect.height) + ' ' + this.getUnit();
+ }
+ }
+ else
+ {
+ div.style.display = 'none';
+ }
+
+ if (rect.vertices.length == graph.getSelectionCount() &&
+ rect.vertices.length > 0 && rect.x != null &&
+ rect.y != null)
+ {
+ var geo = graph.getCellGeometry(rect.vertices[0]);
+ div2.style.display = '';
+
+ if (force || document.activeElement != left)
+ {
+ left.value = this.inUnit(rect.x) + ' ' + this.getUnit();
+ }
+
+ if (force || document.activeElement != top)
+ {
+ top.value = this.inUnit(rect.y) + ' ' + this.getUnit();
+ }
+
+ if (geo != null && geo.relative)
+ {
+ if (dx != null && (force || document.activeElement != dx))
+ {
+ dx.value = (Math.round(geo.x * 1000) / 10) + ' %';
+ }
+
+ if (dy != null && (force || document.activeElement != dy))
+ {
+ if (model.isEdge(model.getParent(rect.vertices[0])))
+ {
+ dy.value = this.inUnit(geo.y) + ' ' + this.getUnit();
+ }
+ else
+ {
+ dy.value = (Math.round(geo.y * 1000) / 10) + ' %';
+ }
+ }
+ }
+ }
+ else
+ {
+ div2.style.display = 'none';
+ }
+ });
+
+ this.listeners.push({destroy: function() { model.removeListener(listener); }});
+ model.addListener(mxEvent.CHANGE, listener);
+ this.addKeyHandler(left, listener);
+ this.addKeyHandler(top, listener);
+ listener();
+
+ leftUpdate = this.addGeometryHandler(left, function(geo, value)
+ {
+ value = panel.fromUnit(value);
+
+ if (geo.relative)
+ {
+ geo.offset.x = value;
+ }
+ else
+ {
+ geo.x = value;
+ }
+ });
+ topUpdate = this.addGeometryHandler(top, function(geo, value)
+ {
+ value = panel.fromUnit(value);
+
+ if (geo.relative)
+ {
+ geo.offset.y = value;
+ }
+ else
+ {
+ geo.y = value;
+ }
+ });
+
+ if (rect.movable)
+ {
+ if (rect.edges.length == 0 && rect.vertices.length == 1 &&
+ model.isEdge(model.getParent(rect.vertices[0])))
+ {
+ var geo = graph.getCellGeometry(rect.vertices[0]);
+
+ if (geo != null && geo.relative)
+ {
+ var btn = mxUtils.button(mxResources.get('center'), mxUtils.bind(this, function(evt)
+ {
+ model.beginUpdate();
+ try
+ {
+ geo = geo.clone();
+ geo.x = 0;
+ geo.y = 0;
+ geo.offset = new mxPoint();
+ model.setGeometry(rect.vertices[0], geo);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }));
+
+ btn.setAttribute('title', mxResources.get('center'));
+ btn.style.width = '134px';
+ btn.style.left = '89px';
+ btn.style.position = 'absolute';
+ mxUtils.br(div2);
+ mxUtils.br(div2);
+ div2.appendChild(btn);
+ }
+ }
+ container.appendChild(div2);
+ }
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addGeometryHandler = function(input, fn)
+{
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+ var initialValue = null;
+ var panel = this;
+
+ function update(evt)
+ {
+ if (input.value != '')
+ {
+ var value = parseFloat(input.value);
+
+ if (isNaN(value))
+ {
+ input.value = initialValue + ' ' + panel.getUnit();
+ }
+ else if (value != initialValue)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = ui.getSelectionState().cells;
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (graph.getModel().isVertex(cells[i]))
+ {
+ var geo = graph.getCellGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+
+ if (!fn(geo, value, cells[i]))
+ {
+ var state = graph.view.getState(cells[i]);
+
+ if (state != null && graph.isRecursiveVertexResize(state))
+ {
+ graph.resizeChildCells(cells[i], geo);
+ }
+
+ graph.getModel().setGeometry(cells[i], geo);
+ graph.constrainChildCells(cells[i]);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+
+ initialValue = value;
+ input.value = value + ' ' + panel.getUnit();
+ }
+ }
+
+ mxEvent.consume(evt);
+ };
+
+ mxEvent.addListener(input, 'blur', update);
+ mxEvent.addListener(input, 'change', update);
+ mxEvent.addListener(input, 'focus', function()
+ {
+ initialValue = input.value;
+ });
+
+ return update;
+};
+
+ArrangePanel.prototype.addEdgeGeometryHandler = function(input, fn)
+{
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+ var initialValue = null;
+
+ function update(evt)
+ {
+ if (input.value != '')
+ {
+ var value = parseFloat(input.value);
+
+ if (isNaN(value))
+ {
+ input.value = initialValue + ' pt';
+ }
+ else if (value != initialValue)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = ui.getSelectionState().cells;
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (graph.getModel().isEdge(cells[i]))
+ {
+ var geo = graph.getCellGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ fn(geo, value);
+
+ graph.getModel().setGeometry(cells[i], geo);
+ }
+ }
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+
+ initialValue = value;
+ input.value = value + ' pt';
+ }
+ }
+
+ mxEvent.consume(evt);
+ };
+
+ mxEvent.addListener(input, 'blur', update);
+ mxEvent.addListener(input, 'change', update);
+ mxEvent.addListener(input, 'focus', function()
+ {
+ initialValue = input.value;
+ });
+
+ return update;
+};
+
+/**
+ *
+ */
+ArrangePanel.prototype.addEdgeGeometry = function(container)
+{
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+ var rect = ui.getSelectionState();
+ var div = this.createPanel();
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.width = '70px';
+ span.style.marginTop = '0px';
+ span.style.fontWeight = 'bold';
+ mxUtils.write(span, mxResources.get('width'));
+ div.appendChild(span);
+
+ var widthUpdate, xtUpdate, ytUpdate, xsUpdate, ysUpdate;
+ var width = this.addUnitInput(div, 'pt', 12, 44, function()
+ {
+ widthUpdate.apply(this, arguments);
+ });
+
+ mxUtils.br(div);
+ this.addKeyHandler(width, listener);
+
+ var widthUpdate = mxUtils.bind(this, function(evt)
+ {
+ // Maximum stroke width is 999
+ var value = parseInt(width.value);
+ value = Math.min(999, Math.max(1, (isNaN(value)) ? 1 : value));
+
+ if (value != mxUtils.getValue(rect.style, 'width', mxCellRenderer.defaultShapes['flexArrow'].prototype.defaultWidth))
+ {
+ var cells = ui.getSelectionState().cells;
+ graph.setCellStyles('width', value, cells);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', ['width'],
+ 'values', [value], 'cells', cells));
+ }
+
+ width.value = value + ' pt';
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(width, 'blur', widthUpdate);
+ mxEvent.addListener(width, 'change', widthUpdate);
+
+ container.appendChild(div);
+
+ var divs = this.createPanel();
+ divs.style.paddingBottom = '30px';
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.width = '70px';
+ span.style.marginTop = '0px';
+ mxUtils.write(span, mxResources.get('linestart'));
+ divs.appendChild(span);
+
+ var xs = this.addUnitInput(divs, 'pt', 87, 52, function()
+ {
+ xsUpdate.apply(this, arguments);
+ });
+ var ys = this.addUnitInput(divs, 'pt', 16, 52, function()
+ {
+ ysUpdate.apply(this, arguments);
+ });
+
+ mxUtils.br(divs);
+ this.addLabel(divs, mxResources.get('left'), 87, 64);
+ this.addLabel(divs, mxResources.get('top'), 16, 64);
+ container.appendChild(divs);
+ this.addKeyHandler(xs, listener);
+ this.addKeyHandler(ys, listener);
+
+ var divt = this.createPanel();
+ divt.style.paddingBottom = '30px';
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.width = '70px';
+ span.style.marginTop = '0px';
+ mxUtils.write(span, mxResources.get('lineend'));
+ divt.appendChild(span);
+
+ var xt = this.addUnitInput(divt, 'pt', 87, 52, function()
+ {
+ xtUpdate.apply(this, arguments);
+ });
+ var yt = this.addUnitInput(divt, 'pt', 16, 52, function()
+ {
+ ytUpdate.apply(this, arguments);
+ });
+
+ mxUtils.br(divt);
+ this.addLabel(divt, mxResources.get('left'), 87, 64);
+ this.addLabel(divt, mxResources.get('top'), 16, 64);
+ container.appendChild(divt);
+ this.addKeyHandler(xt, listener);
+ this.addKeyHandler(yt, listener);
+
+ var listener = mxUtils.bind(this, function(sender, evt, force)
+ {
+ rect = ui.getSelectionState();
+ var cell = rect.cells[0];
+
+ if (rect.style.shape == 'link' || rect.style.shape == 'flexArrow')
+ {
+ div.style.display = '';
+
+ if (force || document.activeElement != width)
+ {
+ var value = mxUtils.getValue(rect.style, 'width',
+ mxCellRenderer.defaultShapes['flexArrow'].prototype.defaultWidth);
+ width.value = value + ' pt';
+ }
+ }
+ else
+ {
+ div.style.display = 'none';
+ }
+
+ if (rect.cells.length == 1 && graph.model.isEdge(cell))
+ {
+ var geo = graph.model.getGeometry(cell);
+
+ if (geo != null && geo.sourcePoint != null &&
+ graph.model.getTerminal(cell, true) == null)
+ {
+ xs.value = geo.sourcePoint.x;
+ ys.value = geo.sourcePoint.y;
+ }
+ else
+ {
+ divs.style.display = 'none';
+ }
+
+ if (geo != null && geo.targetPoint != null &&
+ graph.model.getTerminal(cell, false) == null)
+ {
+ xt.value = geo.targetPoint.x;
+ yt.value = geo.targetPoint.y;
+ }
+ else
+ {
+ divt.style.display = 'none';
+ }
+ }
+ else
+ {
+ divs.style.display = 'none';
+ divt.style.display = 'none';
+ }
+ });
+
+ xsUpdate = this.addEdgeGeometryHandler(xs, function(geo, value)
+ {
+ geo.sourcePoint.x = value;
+ });
+
+ ysUpdate = this.addEdgeGeometryHandler(ys, function(geo, value)
+ {
+ geo.sourcePoint.y = value;
+ });
+
+ xtUpdate = this.addEdgeGeometryHandler(xt, function(geo, value)
+ {
+ geo.targetPoint.x = value;
+ });
+
+ ytUpdate = this.addEdgeGeometryHandler(yt, function(geo, value)
+ {
+ geo.targetPoint.y = value;
+ });
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+ listener();
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+TextFormatPanel = function(format, editorUi, container)
+{
+ BaseFormatPanel.call(this, format, editorUi, container);
+ this.init();
+};
+
+mxUtils.extend(TextFormatPanel, BaseFormatPanel);
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+TextFormatPanel.prototype.init = function()
+{
+ this.container.style.borderBottom = 'none';
+ this.addFont(this.container);
+
+ // Allows to lock/unload button to be added
+ this.container.appendChild(this.addFontOps(this.createPanel()));
+};
+
+
+/**
+ *
+ */
+TextFormatPanel.prototype.addFontOps = function(div)
+{
+ var ui = this.editorUi;
+ div.style.paddingTop = '8px';
+ div.style.paddingBottom = '6px';
+
+ var count = this.addActions(div, ['removeFormat']);
+
+ if (count == 0)
+ {
+ div.style.display = 'none';
+ }
+
+ return div;
+};
+
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+TextFormatPanel.prototype.addFont = function(container)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+ var ss = ui.getSelectionState();
+
+ var title = this.createTitle(mxResources.get('font'));
+ title.style.paddingLeft = '14px';
+ title.style.paddingTop = '10px';
+ title.style.paddingBottom = '6px';
+ container.appendChild(title);
+
+ var stylePanel = this.createPanel();
+ stylePanel.style.paddingTop = '2px';
+ stylePanel.style.paddingBottom = '2px';
+ stylePanel.style.position = 'relative';
+ stylePanel.style.marginLeft = '-2px';
+ stylePanel.style.borderWidth = '0px';
+ stylePanel.className = 'geToolbarContainer';
+
+ if (graph.cellEditor.isContentEditing())
+ {
+ var cssPanel = stylePanel.cloneNode();
+
+ var cssMenu = this.editorUi.toolbar.addMenu(mxResources.get('style'),
+ mxResources.get('style'), true, 'formatBlock', cssPanel, null, true);
+ this.addArrow(cssMenu);
+ cssMenu.style.width = '211px';
+ cssMenu.style.alignItems = 'center';
+ cssMenu.style.justifyContent = 'center';
+ cssMenu.style.whiteSpace = 'nowrap';
+ cssMenu.style.overflow = 'hidden';
+ cssMenu.style.margin = '0px';
+ cssMenu.style.position = 'relative';
+
+ var arrow = cssMenu.getElementsByTagName('div')[0];
+ arrow.style.position = 'absolute';
+ arrow.style.right = '2px';
+ container.appendChild(cssPanel);
+ }
+
+ container.appendChild(stylePanel);
+
+ var colorPanel = this.createPanel();
+ colorPanel.style.marginTop = '8px';
+ colorPanel.style.borderWidth = '1px';
+ colorPanel.style.borderStyle = 'solid';
+ colorPanel.style.paddingTop = '6px';
+ colorPanel.style.paddingBottom = '2px';
+
+ var fontMenu = this.editorUi.toolbar.addMenu('Helvetica', mxResources.get('fontFamily'),
+ true, 'fontFamily', stylePanel, null, true);
+
+ this.addArrow(fontMenu);
+ fontMenu.style.width = '211px';
+ fontMenu.style.alignItems = 'center';
+ fontMenu.style.justifyContent = 'center';
+ fontMenu.style.whiteSpace = 'nowrap';
+ fontMenu.style.overflow = 'hidden';
+ fontMenu.style.margin = '0px';
+ fontMenu.style.position = 'relative';
+
+ var arrow = fontMenu.getElementsByTagName('div')[0];
+ arrow.style.position = 'absolute';
+ arrow.style.right = '2px';
+
+ var stylePanel2 = stylePanel.cloneNode(false);
+ stylePanel2.style.marginLeft = '-3px';
+ var fontStyleItems = this.editorUi.toolbar.addItems(['bold', 'italic', 'underline'], stylePanel2, true);
+ fontStyleItems[0].setAttribute('title', mxResources.get('bold') + ' (' + this.editorUi.actions.get('bold').shortcut + ')');
+ fontStyleItems[1].setAttribute('title', mxResources.get('italic') + ' (' + this.editorUi.actions.get('italic').shortcut + ')');
+ fontStyleItems[2].setAttribute('title', mxResources.get('underline') + ' (' + this.editorUi.actions.get('underline').shortcut + ')');
+
+ var verticalItem = this.editorUi.toolbar.addItems(['vertical'], stylePanel2, true)[0];
+
+ container.appendChild(stylePanel2);
+
+ this.styleButtons(fontStyleItems);
+ this.styleButtons([verticalItem]);
+
+ var stylePanel3 = stylePanel.cloneNode(false);
+ stylePanel3.style.marginLeft = '-3px';
+ stylePanel3.style.paddingBottom = '0px';
+
+ // Helper function to return a wrapper function does not pass any arguments
+ var callFn = function(fn)
+ {
+ return function()
+ {
+ return fn();
+ };
+ };
+
+ var left = this.editorUi.toolbar.addButton('geSprite-left', mxResources.get('left'),
+ (graph.cellEditor.isContentEditing()) ?
+ function(evt)
+ {
+ graph.cellEditor.alignText(mxConstants.ALIGN_LEFT, evt);
+ ui.fireEvent(new mxEventObject('styleChanged',
+ 'keys', [mxConstants.STYLE_ALIGN],
+ 'values', [mxConstants.ALIGN_LEFT],
+ 'cells', ss.cells));
+ } : callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_LEFT])), stylePanel3);
+ var center = this.editorUi.toolbar.addButton('geSprite-center', mxResources.get('center'),
+ (graph.cellEditor.isContentEditing()) ?
+ function(evt)
+ {
+ graph.cellEditor.alignText(mxConstants.ALIGN_CENTER, evt);
+ ui.fireEvent(new mxEventObject('styleChanged',
+ 'keys', [mxConstants.STYLE_ALIGN],
+ 'values', [mxConstants.ALIGN_CENTER],
+ 'cells', ss.cells));
+ } : callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_CENTER])), stylePanel3);
+ var right = this.editorUi.toolbar.addButton('geSprite-right', mxResources.get('right'),
+ (graph.cellEditor.isContentEditing()) ?
+ function(evt)
+ {
+ graph.cellEditor.alignText(mxConstants.ALIGN_RIGHT, evt);
+ ui.fireEvent(new mxEventObject('styleChanged',
+ 'keys', [mxConstants.STYLE_ALIGN],
+ 'values', [mxConstants.ALIGN_RIGHT],
+ 'cells', ss.cells));
+ } : callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_RIGHT])), stylePanel3);
+
+ this.styleButtons([left, center, right]);
+
+ // Quick hack for strikethrough
+ // TODO: Add translations and toggle state
+ if (graph.cellEditor.isContentEditing())
+ {
+ var strike = this.editorUi.toolbar.addButton('geSprite-removeformat', mxResources.get('strikethrough'),
+ function()
+ {
+ document.execCommand('strikeThrough', false, null);
+ }, stylePanel2);
+ this.styleButtons([strike]);
+
+ strike.firstChild.style.background = 'url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PHBhdGggaWQ9ImEiIGQ9Ik0wIDBoMjR2MjRIMFYweiIvPjwvZGVmcz48Y2xpcFBhdGggaWQ9ImIiPjx1c2UgeGxpbms6aHJlZj0iI2EiIG92ZXJmbG93PSJ2aXNpYmxlIi8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjYikiIGZpbGw9IiMwMTAxMDEiIGQ9Ik03LjI0IDguNzVjLS4yNi0uNDgtLjM5LTEuMDMtLjM5LTEuNjcgMC0uNjEuMTMtMS4xNi40LTEuNjcuMjYtLjUuNjMtLjkzIDEuMTEtMS4yOS40OC0uMzUgMS4wNS0uNjMgMS43LS44My42Ni0uMTkgMS4zOS0uMjkgMi4xOC0uMjkuODEgMCAxLjU0LjExIDIuMjEuMzQuNjYuMjIgMS4yMy41NCAxLjY5Ljk0LjQ3LjQuODMuODggMS4wOCAxLjQzLjI1LjU1LjM4IDEuMTUuMzggMS44MWgtMy4wMWMwLS4zMS0uMDUtLjU5LS4xNS0uODUtLjA5LS4yNy0uMjQtLjQ5LS40NC0uNjgtLjItLjE5LS40NS0uMzMtLjc1LS40NC0uMy0uMS0uNjYtLjE2LTEuMDYtLjE2LS4zOSAwLS43NC4wNC0xLjAzLjEzLS4yOS4wOS0uNTMuMjEtLjcyLjM2LS4xOS4xNi0uMzQuMzQtLjQ0LjU1LS4xLjIxLS4xNS40My0uMTUuNjYgMCAuNDguMjUuODguNzQgMS4yMS4zOC4yNS43Ny40OCAxLjQxLjdINy4zOWMtLjA1LS4wOC0uMTEtLjE3LS4xNS0uMjV6TTIxIDEydi0ySDN2Mmg5LjYyYy4xOC4wNy40LjE0LjU1LjIuMzcuMTcuNjYuMzQuODcuNTEuMjEuMTcuMzUuMzYuNDMuNTcuMDcuMi4xMS40My4xMS42OSAwIC4yMy0uMDUuNDUtLjE0LjY2LS4wOS4yLS4yMy4zOC0uNDIuNTMtLjE5LjE1LS40Mi4yNi0uNzEuMzUtLjI5LjA4LS42My4xMy0xLjAxLjEzLS40MyAwLS44My0uMDQtMS4xOC0uMTNzLS42Ni0uMjMtLjkxLS40MmMtLjI1LS4xOS0uNDUtLjQ0LS41OS0uNzUtLjE0LS4zMS0uMjUtLjc2LS4yNS0xLjIxSDYuNGMwIC41NS4wOCAxLjEzLjI0IDEuNTguMTYuNDUuMzcuODUuNjUgMS4yMS4yOC4zNS42LjY2Ljk4LjkyLjM3LjI2Ljc4LjQ4IDEuMjIuNjUuNDQuMTcuOS4zIDEuMzguMzkuNDguMDguOTYuMTMgMS40NC4xMy44IDAgMS41My0uMDkgMi4xOC0uMjhzMS4yMS0uNDUgMS42Ny0uNzljLjQ2LS4zNC44Mi0uNzcgMS4wNy0xLjI3cy4zOC0xLjA3LjM4LTEuNzFjMC0uNi0uMS0xLjE0LS4zMS0xLjYxLS4wNS0uMTEtLjExLS4yMy0uMTctLjMzSDIxeiIvPjwvc3ZnPg==)';
+ strike.firstChild.style.backgroundPosition = '2px 2px';
+ strike.firstChild.style.backgroundSize = '18px 18px';
+
+ this.styleButtons([strike]);
+ }
+
+ var top = this.editorUi.toolbar.addButton('geSprite-top', mxResources.get('top'),
+ callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_VERTICAL_ALIGN],
+ [mxConstants.ALIGN_TOP])), stylePanel3);
+ var middle = this.editorUi.toolbar.addButton('geSprite-middle', mxResources.get('middle'),
+ callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_VERTICAL_ALIGN],
+ [mxConstants.ALIGN_MIDDLE])), stylePanel3);
+ var bottom = this.editorUi.toolbar.addButton('geSprite-bottom', mxResources.get('bottom'),
+ callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_VERTICAL_ALIGN],
+ [mxConstants.ALIGN_BOTTOM])), stylePanel3);
+
+ this.styleButtons([top, middle, bottom]);
+
+ container.appendChild(stylePanel3);
+
+ // Hack for updating UI state below based on current text selection
+ // currentTable is the current selected DOM table updated below
+ var sub, sup, full, tableWrapper, currentTable, tableCell, tableRow;
+
+ if (graph.cellEditor.isContentEditing())
+ {
+ top.style.display = 'none';
+ middle.style.display = 'none';
+ bottom.style.display = 'none';
+ verticalItem.style.display = 'none';
+
+ full = this.editorUi.toolbar.addButton('geSprite-justifyfull', mxResources.get('block'),
+ function()
+ {
+ if (full.style.opacity == 1)
+ {
+ document.execCommand('justifyfull', false, null);
+ }
+ }, stylePanel3);
+ full.style.marginRight = '9px';
+ full.style.opacity = 1;
+
+ this.styleButtons([full,
+ sub = this.editorUi.toolbar.addButton('geSprite-subscript',
+ mxResources.get('subscript') + ' (' + Editor.ctrlKey + '+,)',
+ function()
+ {
+ document.execCommand('subscript', false, null);
+ }, stylePanel3), sup = this.editorUi.toolbar.addButton('geSprite-superscript',
+ mxResources.get('superscript') + ' (' + Editor.ctrlKey + '+.)',
+ function()
+ {
+ document.execCommand('superscript', false, null);
+ }, stylePanel3)]);
+ sub.style.marginLeft = '10px';
+
+ var tmp = stylePanel3.cloneNode(false);
+ tmp.style.paddingTop = '4px';
+ var btns = [this.editorUi.toolbar.addButton('geSprite-orderedlist', mxResources.get('numberedList'),
+ function()
+ {
+ document.execCommand('insertorderedlist', false, null);
+ }, tmp),
+ this.editorUi.toolbar.addButton('geSprite-unorderedlist', mxResources.get('bulletedList'),
+ function()
+ {
+ document.execCommand('insertunorderedlist', false, null);
+ }, tmp),
+ this.editorUi.toolbar.addButton('geSprite-outdent', mxResources.get('decreaseIndent'),
+ function()
+ {
+ document.execCommand('outdent', false, null);
+ }, tmp),
+ this.editorUi.toolbar.addButton('geSprite-indent', mxResources.get('increaseIndent'),
+ function()
+ {
+ document.execCommand('indent', false, null);
+ }, tmp),
+ this.editorUi.toolbar.addButton('geSprite-removeformat', mxResources.get('removeFormat'),
+ function()
+ {
+ document.execCommand('removeformat', false, null);
+ }, tmp),
+ this.editorUi.toolbar.addButton('geSprite-code', mxResources.get('html'),
+ function()
+ {
+ graph.cellEditor.toggleViewMode();
+ }, tmp)];
+ this.styleButtons(btns);
+ btns[btns.length - 2].style.marginLeft = '10px';
+
+ container.appendChild(tmp);
+ }
+ else
+ {
+ fontStyleItems[2].style.marginRight = '12px';
+ right.style.marginRight = '12px';
+ }
+
+ // Label position
+ var stylePanel4 = stylePanel.cloneNode(false);
+ stylePanel4.removeAttribute('class');
+ stylePanel4.style.marginLeft = '0px';
+ stylePanel4.style.paddingTop = '8px';
+ stylePanel4.style.paddingBottom = '4px';
+ stylePanel4.style.fontWeight = 'normal';
+
+ mxUtils.write(stylePanel4, mxResources.get('position'));
+
+ // Adds label position options
+ var positionSelect = document.createElement('select');
+ positionSelect.style.position = 'absolute';
+ positionSelect.style.left = '126px';
+ positionSelect.style.width = '98px';
+ positionSelect.style.borderWidth = '1px';
+ positionSelect.style.borderStyle = 'solid';
+ positionSelect.style.marginTop = '-3px';
+
+ var directions = ['topLeft', 'top', 'topRight', 'left', 'center', 'right', 'bottomLeft', 'bottom', 'bottomRight'];
+ var lset = {'topLeft': [mxConstants.ALIGN_LEFT, mxConstants.ALIGN_TOP, mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_BOTTOM],
+ 'top': [mxConstants.ALIGN_CENTER, mxConstants.ALIGN_TOP, mxConstants.ALIGN_CENTER, mxConstants.ALIGN_BOTTOM],
+ 'topRight': [mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_TOP, mxConstants.ALIGN_LEFT, mxConstants.ALIGN_BOTTOM],
+ 'left': [mxConstants.ALIGN_LEFT, mxConstants.ALIGN_MIDDLE, mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_MIDDLE],
+ 'center': [mxConstants.ALIGN_CENTER, mxConstants.ALIGN_MIDDLE, mxConstants.ALIGN_CENTER, mxConstants.ALIGN_MIDDLE],
+ 'right': [mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_MIDDLE, mxConstants.ALIGN_LEFT, mxConstants.ALIGN_MIDDLE],
+ 'bottomLeft': [mxConstants.ALIGN_LEFT, mxConstants.ALIGN_BOTTOM, mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_TOP],
+ 'bottom': [mxConstants.ALIGN_CENTER, mxConstants.ALIGN_BOTTOM, mxConstants.ALIGN_CENTER, mxConstants.ALIGN_TOP],
+ 'bottomRight': [mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_BOTTOM, mxConstants.ALIGN_LEFT, mxConstants.ALIGN_TOP]};
+
+ for (var i = 0; i < directions.length; i++)
+ {
+ var positionOption = document.createElement('option');
+ positionOption.setAttribute('value', directions[i]);
+ mxUtils.write(positionOption, mxResources.get(directions[i]));
+ positionSelect.appendChild(positionOption);
+ }
+
+ stylePanel4.appendChild(positionSelect);
+
+ // Writing direction
+ var stylePanel5 = stylePanel.cloneNode(false);
+ stylePanel5.removeAttribute('class');
+ stylePanel5.style.marginLeft = '0px';
+ stylePanel5.style.paddingTop = '4px';
+ stylePanel5.style.paddingBottom = '4px';
+ stylePanel5.style.fontWeight = 'normal';
+
+ mxUtils.write(stylePanel5, mxResources.get('writingDirection'));
+
+ // Adds writing direction options
+ // LATER: Handle reselect of same option in all selects (change event
+ // is not fired for same option so have opened state on click) and
+ // handle multiple different styles for current selection
+ var dirSelect = document.createElement('select');
+ dirSelect.style.position = 'absolute';
+ dirSelect.style.borderWidth = '1px';
+ dirSelect.style.borderStyle = 'solid';
+ dirSelect.style.left = '126px';
+ dirSelect.style.width = '98px';
+ dirSelect.style.marginTop = '-3px';
+
+ // NOTE: For automatic we use the value null since automatic
+ // requires the text to be non formatted and non-wrapped
+ var dirs = ['automatic', 'leftToRight', 'rightToLeft'];
+ var dirSet = {'automatic': null,
+ 'leftToRight': mxConstants.TEXT_DIRECTION_LTR,
+ 'rightToLeft': mxConstants.TEXT_DIRECTION_RTL};
+
+ for (var i = 0; i < dirs.length; i++)
+ {
+ var dirOption = document.createElement('option');
+ dirOption.setAttribute('value', dirs[i]);
+ mxUtils.write(dirOption, mxResources.get(dirs[i]));
+ dirSelect.appendChild(dirOption);
+ }
+
+ stylePanel5.appendChild(dirSelect);
+
+ if (!graph.isEditing())
+ {
+ container.appendChild(stylePanel4);
+
+ mxEvent.addListener(positionSelect, 'change', function(evt)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var vals = lset[positionSelect.value];
+
+ if (vals != null)
+ {
+ graph.setCellStyles(mxConstants.STYLE_LABEL_POSITION, vals[0], ss.cells);
+ graph.setCellStyles(mxConstants.STYLE_VERTICAL_LABEL_POSITION, vals[1], ss.cells);
+ graph.setCellStyles(mxConstants.STYLE_ALIGN, vals[2], ss.cells);
+ graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, vals[3], ss.cells);
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ // LATER: Update dir in text editor while editing and update style with label
+ // NOTE: The tricky part is handling and passing on the auto value
+ container.appendChild(stylePanel5);
+
+ mxEvent.addListener(dirSelect, 'change', function(evt)
+ {
+ graph.setCellStyles(mxConstants.STYLE_TEXT_DIRECTION, dirSet[dirSelect.value], ss.cells);
+ mxEvent.consume(evt);
+ });
+ }
+
+ // Fontsize
+ var input = document.createElement('input');
+ input.style.position = 'absolute';
+ input.style.borderWidth = '1px';
+ input.style.borderStyle = 'solid';
+ input.style.textAlign = 'right';
+ input.style.marginTop = '4px';
+ input.style.left = '161px';
+ input.style.width = '53px';
+ input.style.height = '23px';
+ input.style.boxSizing = 'border-box';
+ stylePanel2.appendChild(input);
+
+ // Workaround for font size 4 if no text is selected is update font size below
+ // after first character was entered (as the font element is lazy created)
+ var pendingFontSize = null;
+
+ var inputUpdate = this.installInputHandler(input, mxConstants.STYLE_FONTSIZE, Menus.prototype.defaultFontSize, 1, 999, ' pt',
+ function(fontSize)
+ {
+ // IE does not support containsNode
+ // KNOWN: Fixes font size issues but bypasses undo
+ if (window.getSelection && !mxClient.IS_IE && !mxClient.IS_IE11)
+ {
+ var selection = window.getSelection();
+ var container = (selection.rangeCount > 0) ? selection.getRangeAt(0).commonAncestorContainer :
+ graph.cellEditor.textarea;
+
+ function updateSize(elt, ignoreContains)
+ {
+ if (graph.cellEditor.textarea != null && elt != graph.cellEditor.textarea &&
+ graph.cellEditor.textarea.contains(elt) &&
+ (ignoreContains || selection.containsNode(elt, true)))
+ {
+ if (elt.nodeName == 'FONT')
+ {
+ elt.removeAttribute('size');
+ elt.style.fontSize = fontSize + 'px';
+ }
+ else
+ {
+ var css = mxUtils.getCurrentStyle(elt);
+
+ if (css.fontSize != fontSize + 'px')
+ {
+ if (mxUtils.getCurrentStyle(elt.parentNode).fontSize != fontSize + 'px')
+ {
+ elt.style.fontSize = fontSize + 'px';
+ }
+ else
+ {
+ elt.style.fontSize = '';
+ }
+ }
+ }
+ }
+
+ ui.fireEvent(new mxEventObject('styleChanged',
+ 'keys', [mxConstants.STYLE_FONTSIZE],
+ 'values', [fontSize], 'cells', ss.cells));
+ };
+
+ // Wraps text node or mixed selection with leading text in a font element
+ if (container == graph.cellEditor.textarea ||
+ container.nodeType != mxConstants.NODETYPE_ELEMENT)
+ {
+ document.execCommand('fontSize', false, '1');
+ }
+
+ if (container != graph.cellEditor.textarea)
+ {
+ container = container.parentNode;
+ }
+
+ if (container != null && container.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ var elts = container.getElementsByTagName('*');
+ updateSize(container);
+
+ for (var i = 0; i < elts.length; i++)
+ {
+ updateSize(elts[i]);
+ }
+ }
+
+ input.value = fontSize + ' pt';
+ }
+ else if (window.getSelection || document.selection)
+ {
+ // Checks selection
+ var par = null;
+
+ if (document.selection)
+ {
+ par = document.selection.createRange().parentElement();
+ }
+ else
+ {
+ var selection = window.getSelection();
+
+ if (selection.rangeCount > 0)
+ {
+ par = selection.getRangeAt(0).commonAncestorContainer;
+ }
+ }
+
+ // Node.contains does not work for text nodes in IE11
+ function isOrContains(container, node)
+ {
+ while (node != null)
+ {
+ if (node === container)
+ {
+ return true;
+ }
+
+ node = node.parentNode;
+ }
+
+ return false;
+ };
+
+ if (par != null && isOrContains(graph.cellEditor.textarea, par))
+ {
+ pendingFontSize = fontSize;
+
+ // Workaround for can't set font size in px is to change font size afterwards
+ document.execCommand('fontSize', false, '4');
+ var elts = graph.cellEditor.textarea.getElementsByTagName('font');
+
+ for (var i = 0; i < elts.length; i++)
+ {
+ if (elts[i].getAttribute('size') == '4')
+ {
+ elts[i].removeAttribute('size');
+ elts[i].style.fontSize = pendingFontSize + 'px';
+
+ // Overrides fontSize in input with the one just assigned as a workaround
+ // for potential fontSize values of parent elements that don't match
+ window.setTimeout(function()
+ {
+ input.value = pendingFontSize + ' pt';
+ pendingFontSize = null;
+ }, 0);
+
+ break;
+ }
+ }
+ }
+ }
+ }, true);
+
+ var stepper = this.createStepper(input, inputUpdate, 1, 10, true, Menus.prototype.defaultFontSize);
+ stepper.style.display = input.style.display;
+ stepper.style.marginTop = '4px';
+ stepper.style.left = '214px';
+
+ stylePanel2.appendChild(stepper);
+
+ var arrow = fontMenu.getElementsByTagName('div')[0];
+ arrow.style.cssFloat = 'right';
+
+ var bgColorApply = null;
+ var currentBgColor = graph.shapeBackgroundColor;
+
+ var fontColorApply = null;
+ var currentFontColor = graph.shapeForegroundColor;
+
+ var bgPanel = (graph.cellEditor.isContentEditing()) ? this.createColorOption(mxResources.get('backgroundColor'), function()
+ {
+ return currentBgColor;
+ }, function(color)
+ {
+ document.execCommand('backcolor', false, (color != mxConstants.NONE) ? color : 'transparent');
+ ui.fireEvent(new mxEventObject('styleChanged',
+ 'keys', [mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
+ 'values', [color], 'cells', ss.cells));
+ }, graph.shapeBackgroundColor,
+ {
+ install: function(apply) { bgColorApply = apply; },
+ destroy: function() { bgColorApply = null; }
+ }, null, true) : this.createCellColorOption(mxResources.get('backgroundColor'),
+ mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, 'default', null, function(color)
+ {
+ graph.updateLabelElements(ss.cells, function(elt)
+ {
+ elt.style.backgroundColor = null;
+ });
+ }, graph.shapeBackgroundColor);
+ bgPanel.style.fontWeight = 'bold';
+
+ var borderPanel = this.createCellColorOption(mxResources.get('borderColor'),
+ mxConstants.STYLE_LABEL_BORDERCOLOR, 'default', null, null,
+ graph.shapeForegroundColor);
+ borderPanel.style.fontWeight = 'bold';
+
+ var defs = (ss.vertices.length >= 1) ?
+ graph.stylesheet.getDefaultVertexStyle() :
+ graph.stylesheet.getDefaultEdgeStyle();
+
+ var panel = (graph.cellEditor.isContentEditing()) ? this.createColorOption(mxResources.get('fontColor'), function()
+ {
+ return currentFontColor;
+ }, function(color)
+ {
+ if (mxClient.IS_FF)
+ {
+ // Workaround for Firefox that adds the font element around
+ // anchor elements which ignore inherited colors is to move
+ // the font element inside anchor elements
+ var tmp = graph.cellEditor.textarea.getElementsByTagName('font');
+ var oldFonts = [];
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ oldFonts.push(
+ {
+ node: tmp[i],
+ color: tmp[i].getAttribute('color')
+ });
+ }
+
+ document.execCommand('forecolor', false, (color != mxConstants.NONE) ?
+ color : 'transparent');
+ ui.fireEvent(new mxEventObject('styleChanged',
+ 'keys', [mxConstants.STYLE_FONTCOLOR],
+ 'values', [color], 'cells', ss.cells));
+
+ // Finds the new or changed font element
+ var newFonts = graph.cellEditor.textarea.getElementsByTagName('font');
+
+ for (var i = 0; i < newFonts.length; i++)
+ {
+ if (i >= oldFonts.length || newFonts[i] != oldFonts[i].node ||
+ (newFonts[i] == oldFonts[i].node &&
+ newFonts[i].getAttribute('color') != oldFonts[i].color))
+ {
+ var child = newFonts[i].firstChild;
+
+ // Moves the font element to inside the anchor element and adopts all children
+ if (child != null && child.nodeName == 'A' && child.nextSibling == null &&
+ child.firstChild != null)
+ {
+ var parent = newFonts[i].parentNode;
+ parent.insertBefore(child, newFonts[i]);
+ var tmp = child.firstChild;
+
+ while (tmp != null)
+ {
+ var next = tmp.nextSibling;
+ newFonts[i].appendChild(tmp);
+ tmp = next;
+ }
+
+ child.appendChild(newFonts[i]);
+ }
+
+ break;
+ }
+ }
+ }
+ else
+ {
+ document.execCommand('forecolor', false, (color != mxConstants.NONE) ?
+ color : 'transparent');
+ ui.fireEvent(new mxEventObject('styleChanged',
+ 'keys', [mxConstants.STYLE_FONTCOLOR],
+ 'values', [color], 'cells', ss.cells));
+ }
+ }, (defs[mxConstants.STYLE_FONTCOLOR] != null) ? defs[mxConstants.STYLE_FONTCOLOR] : graph.shapeForegroundColor,
+ {
+ install: function(apply) { fontColorApply = apply; },
+ destroy: function() { fontColorApply = null; }
+ }, null, true) : this.createCellColorOption(mxResources.get('fontColor'),
+ mxConstants.STYLE_FONTCOLOR, 'default', function(color)
+ {
+ if (color == mxConstants.NONE)
+ {
+ bgPanel.style.display = 'none';
+ }
+ else
+ {
+ bgPanel.style.display = '';
+ }
+
+ borderPanel.style.display = bgPanel.style.display;
+ }, function(color)
+ {
+ if (color == mxConstants.NONE)
+ {
+ graph.setCellStyles(mxConstants.STYLE_NOLABEL, '1', ss.cells);
+ }
+ else
+ {
+ graph.setCellStyles(mxConstants.STYLE_NOLABEL, null, ss.cells);
+ }
+
+ graph.setCellStyles(mxConstants.STYLE_FONTCOLOR, color, ss.cells);
+
+ graph.updateLabelElements(ss.cells, function(elt)
+ {
+ elt.removeAttribute('color');
+ elt.style.color = null;
+ });
+ }, graph.shapeForegroundColor);
+ panel.style.fontWeight = 'bold';
+
+ colorPanel.appendChild(panel);
+ colorPanel.appendChild(bgPanel);
+
+ if (!graph.cellEditor.isContentEditing())
+ {
+ colorPanel.appendChild(borderPanel);
+ }
+
+ container.appendChild(colorPanel);
+
+ var extraPanel = this.createPanel();
+ extraPanel.style.paddingTop = '2px';
+ extraPanel.style.paddingBottom = '4px';
+
+ var wwCells = graph.filterSelectionCells(mxUtils.bind(this, function(cell)
+ {
+ var state = graph.view.getState(cell);
+
+ return state == null || graph.isAutoSizeState(state) ||
+ graph.getModel().isEdge(cell) || (!graph.isTableRow(cell) &&
+ !graph.isTableCell(cell) && !graph.isCellResizable(cell));
+ }));
+
+ var wwOpt = this.createCellOption(mxResources.get('wordWrap'), mxConstants.STYLE_WHITE_SPACE,
+ null, 'wrap', 'null', null, null, true, wwCells);
+ wwOpt.style.fontWeight = 'bold';
+
+ // Word wrap in edge labels only supported via labelWidth style
+ if (wwCells.length > 0)
+ {
+ extraPanel.appendChild(wwOpt);
+ }
+
+ // Delegates switch of style to formattedText action as it also convertes newlines
+ var htmlOpt = this.createCellOption(mxResources.get('formattedText'), 'html', 0,
+ null, null, null, ui.actions.get('formattedText'));
+ htmlOpt.style.fontWeight = 'bold';
+ extraPanel.appendChild(htmlOpt);
+
+ var spacingPanel = this.createPanel();
+ spacingPanel.style.paddingTop = '10px';
+ spacingPanel.style.paddingBottom = '28px';
+ spacingPanel.style.fontWeight = 'normal';
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.width = '70px';
+ span.style.marginTop = '0px';
+ span.style.fontWeight = 'bold';
+ mxUtils.write(span, mxResources.get('spacing'));
+ spacingPanel.appendChild(span);
+
+ var topUpdate, globalUpdate, leftUpdate, bottomUpdate, rightUpdate;
+ var topSpacing = this.addUnitInput(spacingPanel, 'pt', 87, 52, function()
+ {
+ topUpdate.apply(this, arguments);
+ });
+ var globalSpacing = this.addUnitInput(spacingPanel, 'pt', 16, 52, function()
+ {
+ globalUpdate.apply(this, arguments);
+ });
+
+ mxUtils.br(spacingPanel);
+ this.addLabel(spacingPanel, mxResources.get('top'), 87, 64);
+ this.addLabel(spacingPanel, mxResources.get('global'), 16, 64);
+ mxUtils.br(spacingPanel);
+ mxUtils.br(spacingPanel);
+
+ var leftSpacing = this.addUnitInput(spacingPanel, 'pt', 158, 52, function()
+ {
+ leftUpdate.apply(this, arguments);
+ });
+ var bottomSpacing = this.addUnitInput(spacingPanel, 'pt', 87, 52, function()
+ {
+ bottomUpdate.apply(this, arguments);
+ });
+ var rightSpacing = this.addUnitInput(spacingPanel, 'pt', 16, 52, function()
+ {
+ rightUpdate.apply(this, arguments);
+ });
+
+ mxUtils.br(spacingPanel);
+ this.addLabel(spacingPanel, mxResources.get('left'), 158, 64);
+ this.addLabel(spacingPanel, mxResources.get('bottom'), 87, 64);
+ this.addLabel(spacingPanel, mxResources.get('right'), 16, 64);
+
+ if (!graph.cellEditor.isContentEditing())
+ {
+ container.appendChild(extraPanel);
+ container.appendChild(this.createRelativeOption(mxResources.get('opacity'), mxConstants.STYLE_TEXT_OPACITY));
+ container.appendChild(spacingPanel);
+ }
+ else
+ {
+ var selState = null;
+ var lineHeightInput = null;
+
+ container.appendChild(this.createRelativeOption(mxResources.get('lineheight'), null, null, function(input)
+ {
+ var value = (input.value == '') ? 120 : parseInt(input.value);
+ value = Math.max(0, (isNaN(value)) ? 120 : value);
+
+ if (selState != null)
+ {
+ graph.cellEditor.restoreSelection(selState);
+ selState = null;
+ }
+
+ var blocks = graph.getSelectedTextBlocks();
+
+ // Adds paragraph tags if no block element is selected
+ if (blocks.length == 0 && graph.cellEditor.textarea != null &&
+ graph.cellEditor.textarea.firstChild != null)
+ {
+ if (graph.cellEditor.textarea.firstChild.nodeName != 'P')
+ {
+ graph.cellEditor.textarea.innerHTML = '' + graph.cellEditor.textarea.innerHTML + '
';
+ }
+
+ blocks = [graph.cellEditor.textarea.firstChild];
+ }
+
+ for (var i = 0; i < blocks.length; i++)
+ {
+ blocks[i].style.lineHeight = value + '%';
+ }
+
+ input.value = value + ' %';
+ }, function(input)
+ {
+ // Used in CSS handler to update current value
+ lineHeightInput = input;
+
+ // KNOWN: Arrow up/down clear selection text in quirks/IE 8
+ // Text size via arrow button limits to 16 in IE11. Why?
+ mxEvent.addListener(input, 'mousedown', function()
+ {
+ if (document.activeElement == graph.cellEditor.textarea)
+ {
+ selState = graph.cellEditor.saveSelection();
+ }
+ });
+
+ mxEvent.addListener(input, 'touchstart', function()
+ {
+ if (document.activeElement == graph.cellEditor.textarea)
+ {
+ selState = graph.cellEditor.saveSelection();
+ }
+ });
+
+ input.value = '120 %';
+ }));
+
+ var insertPanel = stylePanel.cloneNode(false);
+ insertPanel.style.paddingLeft = '0px';
+ var insertBtns = this.editorUi.toolbar.addItems(['link', 'image'], insertPanel, true);
+
+ var btns = [
+ this.editorUi.toolbar.addButton('geSprite-horizontalrule', mxResources.get('insertHorizontalRule'),
+ function()
+ {
+ document.execCommand('inserthorizontalrule', false);
+ }, insertPanel),
+ this.editorUi.toolbar.addMenuFunctionInContainer(insertPanel, 'geSprite-table', mxResources.get('table'), false, mxUtils.bind(this, function(menu)
+ {
+ this.editorUi.menus.addInsertTableItem(menu, null, null, false);
+ }))];
+ this.styleButtons(insertBtns);
+ this.styleButtons(btns);
+
+ var wrapper2 = this.createPanel();
+ wrapper2.style.paddingTop = '10px';
+ wrapper2.style.paddingBottom = '10px';
+ wrapper2.appendChild(this.createTitle(mxResources.get('insert')));
+ wrapper2.appendChild(insertPanel);
+ container.appendChild(wrapper2);
+
+ var tablePanel = stylePanel.cloneNode(false);
+ tablePanel.style.paddingLeft = '0px';
+
+ var btns = [
+ this.editorUi.toolbar.addButton('geSprite-insertcolumnbefore', mxResources.get('insertColumnBefore'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (currentTable != null)
+ {
+ graph.insertColumn(currentTable, (tableCell != null) ? tableCell.cellIndex : 0);
+ }
+ }
+ catch (e)
+ {
+ this.editorUi.handleError(e);
+ }
+ }), tablePanel),
+ this.editorUi.toolbar.addButton('geSprite-insertcolumnafter', mxResources.get('insertColumnAfter'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (currentTable != null)
+ {
+ graph.insertColumn(currentTable, (tableCell != null) ? tableCell.cellIndex + 1 : -1);
+ }
+ }
+ catch (e)
+ {
+ this.editorUi.handleError(e);
+ }
+ }), tablePanel),
+ this.editorUi.toolbar.addButton('geSprite-deletecolumn', mxResources.get('deleteColumn'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (currentTable != null && tableCell != null)
+ {
+ graph.deleteColumn(currentTable, tableCell.cellIndex);
+ }
+ }
+ catch (e)
+ {
+ this.editorUi.handleError(e);
+ }
+ }), tablePanel),
+ this.editorUi.toolbar.addButton('geSprite-insertrowbefore', mxResources.get('insertRowBefore'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (currentTable != null && tableRow != null)
+ {
+ graph.insertRow(currentTable, tableRow.sectionRowIndex);
+ }
+ }
+ catch (e)
+ {
+ this.editorUi.handleError(e);
+ }
+ }), tablePanel),
+ this.editorUi.toolbar.addButton('geSprite-insertrowafter', mxResources.get('insertRowAfter'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (currentTable != null && tableRow != null)
+ {
+ graph.insertRow(currentTable, tableRow.sectionRowIndex + 1);
+ }
+ }
+ catch (e)
+ {
+ this.editorUi.handleError(e);
+ }
+ }), tablePanel),
+ this.editorUi.toolbar.addButton('geSprite-deleterow', mxResources.get('deleteRow'),
+ mxUtils.bind(this, function()
+ {
+ try
+ {
+ if (currentTable != null && tableRow != null)
+ {
+ graph.deleteRow(currentTable, tableRow.sectionRowIndex);
+ }
+ }
+ catch (e)
+ {
+ this.editorUi.handleError(e);
+ }
+ }), tablePanel)];
+ this.styleButtons(btns);
+ btns[2].style.marginRight = '10px';
+
+ var wrapper3 = this.createPanel();
+ wrapper3.style.paddingTop = '10px';
+ wrapper3.style.paddingBottom = '10px';
+ wrapper3.appendChild(this.createTitle(mxResources.get('table')));
+ wrapper3.appendChild(tablePanel);
+
+ var tablePanel2 = stylePanel.cloneNode(false);
+ tablePanel2.style.paddingLeft = '0px';
+
+ var btns = [
+ this.editorUi.toolbar.addButton('geSprite-strokecolor', mxResources.get('borderColor'),
+ mxUtils.bind(this, function(evt)
+ {
+ if (currentTable != null)
+ {
+ // Converts rgb(r,g,b) values
+ var color = currentTable.style.borderColor.replace(
+ /\brgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/g,
+ function($0, $1, $2, $3) {
+ return "#" + ("0"+Number($1).toString(16)).substr(-2) + ("0"+Number($2).toString(16)).substr(-2) + ("0"+Number($3).toString(16)).substr(-2);
+ });
+ this.editorUi.pickColor(color, function(newColor)
+ {
+ var targetElt = (tableCell != null && (evt == null || !mxEvent.isShiftDown(evt))) ? tableCell : currentTable;
+
+ graph.processElements(targetElt, function(elt)
+ {
+ elt.style.border = null;
+ });
+
+ if (newColor == null || newColor == mxConstants.NONE)
+ {
+ targetElt.removeAttribute('border');
+ targetElt.style.border = '';
+ targetElt.style.borderCollapse = '';
+ }
+ else
+ {
+ targetElt.setAttribute('border', '1');
+ targetElt.style.border = '1px solid ' + newColor;
+ targetElt.style.borderCollapse = 'collapse';
+ }
+ });
+ }
+ }), tablePanel2),
+ this.editorUi.toolbar.addButton('geSprite-fillcolor', mxResources.get('backgroundColor'),
+ mxUtils.bind(this, function(evt)
+ {
+ // Converts rgb(r,g,b) values
+ if (currentTable != null)
+ {
+ var color = currentTable.style.backgroundColor.replace(
+ /\brgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/g,
+ function($0, $1, $2, $3) {
+ return "#" + ("0"+Number($1).toString(16)).substr(-2) + ("0"+Number($2).toString(16)).substr(-2) + ("0"+Number($3).toString(16)).substr(-2);
+ });
+ this.editorUi.pickColor(color, function(newColor)
+ {
+ var targetElt = (tableCell != null && (evt == null || !mxEvent.isShiftDown(evt))) ? tableCell : currentTable;
+
+ graph.processElements(targetElt, function(elt)
+ {
+ elt.style.backgroundColor = null;
+ });
+
+ if (newColor == null || newColor == mxConstants.NONE)
+ {
+ targetElt.style.backgroundColor = '';
+ }
+ else
+ {
+ targetElt.style.backgroundColor = newColor;
+ }
+ });
+ }
+ }), tablePanel2),
+ this.editorUi.toolbar.addButton('geSprite-fit', mxResources.get('spacing'),
+ function()
+ {
+ if (currentTable != null)
+ {
+ var value = currentTable.getAttribute('cellPadding') || 0;
+
+ var dlg = new FilenameDialog(ui, value, mxResources.get('apply'), mxUtils.bind(this, function(newValue)
+ {
+ if (newValue != null && newValue.length > 0)
+ {
+ currentTable.setAttribute('cellPadding', newValue);
+ }
+ else
+ {
+ currentTable.removeAttribute('cellPadding');
+ }
+ }), mxResources.get('spacing'));
+ ui.showDialog(dlg.container, 300, 80, true, true);
+ dlg.init();
+ }
+ }, tablePanel2),
+ this.editorUi.toolbar.addButton('geSprite-left', mxResources.get('left'),
+ function()
+ {
+ if (currentTable != null)
+ {
+ currentTable.setAttribute('align', 'left');
+ }
+ }, tablePanel2),
+ this.editorUi.toolbar.addButton('geSprite-center', mxResources.get('center'),
+ function()
+ {
+ if (currentTable != null)
+ {
+ currentTable.setAttribute('align', 'center');
+ }
+ }, tablePanel2),
+ this.editorUi.toolbar.addButton('geSprite-right', mxResources.get('right'),
+ function()
+ {
+ if (currentTable != null)
+ {
+ currentTable.setAttribute('align', 'right');
+ }
+ }, tablePanel2)];
+ this.styleButtons(btns);
+ btns[2].style.marginRight = '10px';
+
+ wrapper3.appendChild(tablePanel2);
+ container.appendChild(wrapper3);
+
+ tableWrapper = wrapper3;
+ }
+
+ function setSelected(elt, selected)
+ {
+ elt.style.backgroundImage = (selected) ? (Editor.isDarkMode() ?
+ 'linear-gradient(rgb(0 161 241) 0px, rgb(0, 97, 146) 100%)':
+ 'linear-gradient(#c5ecff 0px,#87d4fb 100%)') : '';
+ };
+
+ // Updates font style state before typing
+ for (var i = 0; i < 3; i++)
+ {
+ (function(index)
+ {
+ mxEvent.addListener(fontStyleItems[index], 'click', function()
+ {
+ setSelected(fontStyleItems[index], fontStyleItems[index].style.backgroundImage == '');
+ });
+ })(i);
+ }
+
+ var listener = mxUtils.bind(this, function(sender, evt, force)
+ {
+ ss = ui.getSelectionState();
+ var fontStyle = mxUtils.getValue(ss.style, mxConstants.STYLE_FONTSTYLE, 0);
+ setSelected(fontStyleItems[0], (fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD);
+ setSelected(fontStyleItems[1], (fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC);
+ setSelected(fontStyleItems[2], (fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE);
+ fontMenu.firstChild.nodeValue = mxUtils.getValue(ss.style, mxConstants.STYLE_FONTFAMILY, Menus.prototype.defaultFont);
+
+ setSelected(verticalItem, mxUtils.getValue(ss.style, mxConstants.STYLE_HORIZONTAL, '1') == '0');
+
+ if (force || document.activeElement != input)
+ {
+ var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_FONTSIZE, Menus.prototype.defaultFontSize));
+ input.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ var align = mxUtils.getValue(ss.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
+ setSelected(left, align == mxConstants.ALIGN_LEFT);
+ setSelected(center, align == mxConstants.ALIGN_CENTER);
+ setSelected(right, align == mxConstants.ALIGN_RIGHT);
+
+ var valign = mxUtils.getValue(ss.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
+ setSelected(top, valign == mxConstants.ALIGN_TOP);
+ setSelected(middle, valign == mxConstants.ALIGN_MIDDLE);
+ setSelected(bottom, valign == mxConstants.ALIGN_BOTTOM);
+
+ var pos = mxUtils.getValue(ss.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+ var vpos = mxUtils.getValue(ss.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+ if (pos == mxConstants.ALIGN_LEFT && vpos == mxConstants.ALIGN_TOP)
+ {
+ positionSelect.value = 'topLeft';
+ }
+ else if (pos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_TOP)
+ {
+ positionSelect.value = 'top';
+ }
+ else if (pos == mxConstants.ALIGN_RIGHT && vpos == mxConstants.ALIGN_TOP)
+ {
+ positionSelect.value = 'topRight';
+ }
+ else if (pos == mxConstants.ALIGN_LEFT && vpos == mxConstants.ALIGN_BOTTOM)
+ {
+ positionSelect.value = 'bottomLeft';
+ }
+ else if (pos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_BOTTOM)
+ {
+ positionSelect.value = 'bottom';
+ }
+ else if (pos == mxConstants.ALIGN_RIGHT && vpos == mxConstants.ALIGN_BOTTOM)
+ {
+ positionSelect.value = 'bottomRight';
+ }
+ else if (pos == mxConstants.ALIGN_LEFT)
+ {
+ positionSelect.value = 'left';
+ }
+ else if (pos == mxConstants.ALIGN_RIGHT)
+ {
+ positionSelect.value = 'right';
+ }
+ else
+ {
+ positionSelect.value = 'center';
+ }
+
+ var dir = mxUtils.getValue(ss.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+
+ if (dir == mxConstants.TEXT_DIRECTION_RTL)
+ {
+ dirSelect.value = 'rightToLeft';
+ }
+ else if (dir == mxConstants.TEXT_DIRECTION_LTR)
+ {
+ dirSelect.value = 'leftToRight';
+ }
+ else if (dir == mxConstants.TEXT_DIRECTION_AUTO)
+ {
+ dirSelect.value = 'automatic';
+ }
+
+ if (force || document.activeElement != globalSpacing)
+ {
+ var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING, 2));
+ globalSpacing.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ if (force || document.activeElement != topSpacing)
+ {
+ var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING_TOP, 0));
+ topSpacing.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ if (force || document.activeElement != rightSpacing)
+ {
+ var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING_RIGHT, 0));
+ rightSpacing.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ if (force || document.activeElement != bottomSpacing)
+ {
+ var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING_BOTTOM, 0));
+ bottomSpacing.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ if (force || document.activeElement != leftSpacing)
+ {
+ var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING_LEFT, 0));
+ leftSpacing.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+ });
+
+ globalUpdate = this.installInputHandler(globalSpacing, mxConstants.STYLE_SPACING, 2, -999, 999, ' pt');
+ topUpdate = this.installInputHandler(topSpacing, mxConstants.STYLE_SPACING_TOP, 0, -999, 999, ' pt');
+ rightUpdate = this.installInputHandler(rightSpacing, mxConstants.STYLE_SPACING_RIGHT, 0, -999, 999, ' pt');
+ bottomUpdate = this.installInputHandler(bottomSpacing, mxConstants.STYLE_SPACING_BOTTOM, 0, -999, 999, ' pt');
+ leftUpdate = this.installInputHandler(leftSpacing, mxConstants.STYLE_SPACING_LEFT, 0, -999, 999, ' pt');
+
+ this.addKeyHandler(input, listener);
+ this.addKeyHandler(globalSpacing, listener);
+ this.addKeyHandler(topSpacing, listener);
+ this.addKeyHandler(rightSpacing, listener);
+ this.addKeyHandler(bottomSpacing, listener);
+ this.addKeyHandler(leftSpacing, listener);
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+ listener();
+
+ if (graph.cellEditor.isContentEditing())
+ {
+ var updating = false;
+
+ var updateCssHandler = function()
+ {
+ if (!updating)
+ {
+ updating = true;
+
+ window.setTimeout(function()
+ {
+ var node = graph.getSelectedEditingElement();
+
+ if (node != null)
+ {
+ function getRelativeLineHeight(fontSize, css, elt)
+ {
+ if (elt.style != null && css != null)
+ {
+ var lineHeight = css.lineHeight
+
+ if (elt.style.lineHeight != null && elt.style.lineHeight.substring(elt.style.lineHeight.length - 1) == '%')
+ {
+ return parseInt(elt.style.lineHeight) / 100;
+ }
+ else
+ {
+ return (lineHeight.substring(lineHeight.length - 2) == 'px') ?
+ parseFloat(lineHeight) / fontSize : parseInt(lineHeight);
+ }
+ }
+ else
+ {
+ return '';
+ }
+ };
+
+ function getAbsoluteFontSize(css)
+ {
+ var fontSize = (css != null) ? css.fontSize : null;
+
+ if (fontSize != null && fontSize.substring(fontSize.length - 2) == 'px')
+ {
+ return parseFloat(fontSize);
+ }
+ else
+ {
+ return mxConstants.DEFAULT_FONTSIZE;
+ }
+ };
+
+ var css = mxUtils.getCurrentStyle(node);
+ var fontSize = getAbsoluteFontSize(css);
+ var lineHeight = getRelativeLineHeight(fontSize, css, node);
+
+ // Finds common font size
+ var elts = node.getElementsByTagName('*');
+
+ // IE does not support containsNode
+ if (elts.length > 0 && window.getSelection && !mxClient.IS_IE && !mxClient.IS_IE11)
+ {
+ var selection = window.getSelection();
+
+ for (var i = 0; i < elts.length; i++)
+ {
+ if (selection.containsNode(elts[i], true))
+ {
+ temp = mxUtils.getCurrentStyle(elts[i]);
+ fontSize = Math.max(getAbsoluteFontSize(temp), fontSize);
+ var lh = getRelativeLineHeight(fontSize, temp, elts[i]);
+
+ if (lh != lineHeight || isNaN(lh))
+ {
+ lineHeight = '';
+ }
+ }
+ }
+ }
+
+ function hasParentOrOnlyChild(name)
+ {
+ if (graph.getParentByName(node, name, graph.cellEditor.textarea) != null)
+ {
+ return true;
+ }
+ else
+ {
+ var child = node;
+
+ while (child != null && child.childNodes.length == 1)
+ {
+ child = child.childNodes[0];
+
+ if (child.nodeName == name)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ };
+
+ function isEqualOrPrefixed(str, value)
+ {
+ if (str != null && value != null)
+ {
+ if (str == value)
+ {
+ return true;
+ }
+ else if (str.length > value.length + 1)
+ {
+ return str.substring(str.length - value.length - 1, str.length) == '-' + value;
+ }
+ }
+
+ return false;
+ };
+
+ if (css != null)
+ {
+ setSelected(fontStyleItems[0], css.fontWeight == 'bold' ||
+ css.fontWeight > 400 || hasParentOrOnlyChild('B') ||
+ hasParentOrOnlyChild('STRONG'));
+ setSelected(fontStyleItems[1], css.fontStyle == 'italic' ||
+ hasParentOrOnlyChild('I') || hasParentOrOnlyChild('EM'));
+ setSelected(fontStyleItems[2], hasParentOrOnlyChild('U'));
+ setSelected(sup, hasParentOrOnlyChild('SUP'));
+ setSelected(sub, hasParentOrOnlyChild('SUB'));
+
+ if (!graph.cellEditor.isTableSelected())
+ {
+ var align = graph.cellEditor.align || mxUtils.getValue(ss.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
+
+ if (isEqualOrPrefixed(css.textAlign, 'justify'))
+ {
+ setSelected(full, isEqualOrPrefixed(css.textAlign, 'justify'));
+ setSelected(left, false);
+ setSelected(center, false);
+ setSelected(right, false);
+ }
+ else
+ {
+ setSelected(full, false);
+ setSelected(left, align == mxConstants.ALIGN_LEFT);
+ setSelected(center, align == mxConstants.ALIGN_CENTER);
+ setSelected(right, align == mxConstants.ALIGN_RIGHT);
+ }
+ }
+ else
+ {
+ setSelected(full, isEqualOrPrefixed(css.textAlign, 'justify'));
+ setSelected(left, isEqualOrPrefixed(css.textAlign, 'left'));
+ setSelected(center, isEqualOrPrefixed(css.textAlign, 'center'));
+ setSelected(right, isEqualOrPrefixed(css.textAlign, 'right'));
+ }
+
+ currentTable = graph.getParentByName(node, 'TABLE', graph.cellEditor.textarea);
+ tableRow = (currentTable == null) ? null : graph.getParentByName(node, 'TR', currentTable);
+ tableCell = (currentTable == null) ? null : graph.getParentByNames(node, ['TD', 'TH'], currentTable);
+ tableWrapper.style.display = (currentTable != null) ? '' : 'none';
+
+ if (document.activeElement != input)
+ {
+ if (node.nodeName == 'FONT' && node.getAttribute('size') == '4' &&
+ pendingFontSize != null)
+ {
+ node.removeAttribute('size');
+ node.style.fontSize = pendingFontSize + ' pt';
+ pendingFontSize = null;
+ }
+ else
+ {
+ input.value = (isNaN(fontSize)) ? '' : fontSize + ' pt';
+ }
+
+ var lh = parseFloat(lineHeight);
+
+ if (!isNaN(lh))
+ {
+ lineHeightInput.value = Math.round(lh * 100) + ' %';
+ }
+ else
+ {
+ lineHeightInput.value = '100 %';
+ }
+ }
+
+ // Updates the color picker for the current font
+ if (fontColorApply != null)
+ {
+ if (css.color == 'rgba(0, 0, 0, 0)' ||
+ css.color == 'transparent')
+ {
+ currentFontColor = mxConstants.NONE;
+ }
+ else
+ {
+ currentFontColor = mxUtils.rgba2hex(css.color)
+ }
+
+ fontColorApply(currentFontColor, true);
+ }
+
+ if (bgColorApply != null)
+ {
+ if (css.backgroundColor == 'rgba(0, 0, 0, 0)' ||
+ css.backgroundColor == 'transparent')
+ {
+ currentBgColor = mxConstants.NONE;
+ }
+ else
+ {
+ currentBgColor = mxUtils.rgba2hex(css.backgroundColor);
+ }
+
+ bgColorApply(currentBgColor, true);
+ }
+
+ // Workaround for firstChild is null or not an object
+ // in the log which seems to be IE8- only / 29.01.15
+ if (fontMenu.firstChild != null)
+ {
+ fontMenu.firstChild.nodeValue = Graph.stripQuotes(css.fontFamily);
+ }
+ }
+ }
+
+ updating = false;
+ }, 0);
+ }
+ };
+
+ if (mxClient.IS_FF || mxClient.IS_EDGE || mxClient.IS_IE || mxClient.IS_IE11)
+ {
+ mxEvent.addListener(graph.cellEditor.textarea, 'DOMSubtreeModified', updateCssHandler);
+ }
+
+ mxEvent.addListener(graph.cellEditor.textarea, 'input', updateCssHandler);
+ mxEvent.addListener(graph.cellEditor.textarea, 'touchend', updateCssHandler);
+ mxEvent.addListener(graph.cellEditor.textarea, 'mouseup', updateCssHandler);
+ mxEvent.addListener(graph.cellEditor.textarea, 'keyup', updateCssHandler);
+ this.listeners.push({destroy: function()
+ {
+ // No need to remove listener since textarea is destroyed after edit
+ }});
+ updateCssHandler();
+ }
+
+ return container;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel = function(format, editorUi, container)
+{
+ BaseFormatPanel.call(this, format, editorUi, container);
+ this.init();
+};
+
+mxUtils.extend(StyleFormatPanel, BaseFormatPanel);
+
+/**
+ *
+ */
+StyleFormatPanel.prototype.defaultStrokeColor = 'black';
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel.prototype.init = function()
+{
+ var ui = this.editorUi;
+ var ss = ui.getSelectionState();
+
+ if (!ss.containsLabel && ss.cells.length > 0)
+ {
+ if (ss.containsImage && ss.vertices.length == 1 && ss.style.shape == 'image' &&
+ ss.style.image != null && ss.style.image.substring(0, 19) == 'data:image/svg+xml;')
+ {
+ this.container.appendChild(this.addSvgStyles(this.createPanel()));
+ }
+
+ if (ss.fill)
+ {
+ this.container.appendChild(this.addFill(this.createPanel()));
+ }
+
+ this.container.appendChild(this.addStroke(this.createPanel()));
+ this.container.appendChild(this.addLineJumps(this.createPanel()));
+ var opacityPanel = this.createRelativeOption(mxResources.get('opacity'), mxConstants.STYLE_OPACITY);
+ opacityPanel.style.paddingTop = '8px';
+ opacityPanel.style.paddingBottom = '10px';
+ this.container.appendChild(opacityPanel);
+ this.container.appendChild(this.addEffects(this.createPanel()));
+ }
+
+ var opsPanel = this.createPanel();
+ opsPanel.style.paddingTop = '8px';
+
+ if (ss.cells.length == 1)
+ {
+ this.addEditOps(opsPanel);
+
+ if (opsPanel.firstChild != null)
+ {
+ mxUtils.br(opsPanel);
+ }
+ }
+
+ if (ss.cells.length >= 1)
+ {
+ this.addStyleOps(opsPanel);
+ }
+
+ if (opsPanel.firstChild != null)
+ {
+ this.container.appendChild(opsPanel);
+ }
+};
+
+/**
+ * Use browser for parsing CSS.
+ */
+StyleFormatPanel.prototype.getCssRules = function(css)
+{
+ var doc = document.implementation.createHTMLDocument('');
+ var styleElement = document.createElement('style');
+
+ mxUtils.setTextContent(styleElement, css);
+ doc.body.appendChild(styleElement);
+
+ return styleElement.sheet.cssRules;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel.prototype.addSvgStyles = function(container)
+{
+ var ui = this.editorUi;
+ var ss = ui.getSelectionState();
+ container.style.paddingTop = '6px';
+ container.style.paddingBottom = '6px';
+ container.style.fontWeight = 'bold';
+ container.style.display = 'none';
+
+ try
+ {
+ var exp = ss.style.editableCssRules;
+
+ if (exp != null)
+ {
+ var regex = new RegExp(exp);
+
+ var data = ss.style.image.substring(ss.style.image.indexOf(',') + 1);
+ var xml = (window.atob) ? atob(data) : Base64.decode(data, true);
+ var svg = mxUtils.parseXml(xml);
+
+ if (svg != null)
+ {
+ var styles = svg.getElementsByTagName('style');
+
+ for (var i = 0; i < styles.length; i++)
+ {
+ var rules = this.getCssRules(mxUtils.getTextContent(styles[i]));
+
+ for (var j = 0; j < rules.length; j++)
+ {
+ this.addSvgRule(container, rules[j], svg, styles[i], rules, j, regex);
+ }
+ }
+ }
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+
+ return container;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel.prototype.addSvgRule = function(container, rule, svg, styleElem, rules, ruleIndex, regex)
+{
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+
+ if (regex.test(rule.selectorText))
+ {
+ function rgb2hex(rgb)
+ {
+ rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
+
+ return (rgb && rgb.length === 4) ? "#" +
+ ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
+ ("0" + parseInt(rgb[2],10).toString(16)).slice(-2) +
+ ("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : '';
+ };
+
+ var addStyleRule = mxUtils.bind(this, function(rule, key, label)
+ {
+ var value = mxUtils.trim(rule.style[key]);
+
+ if (value != '' && value.substring(0, 4) != 'url(')
+ {
+ var option = this.createColorOption(label + ' ' + rule.selectorText, function()
+ {
+ return rgb2hex(value);
+ }, mxUtils.bind(this, function(color)
+ {
+ rules[ruleIndex].style[key] = color;
+ var cssTxt = '';
+
+ for (var i = 0; i < rules.length; i++)
+ {
+ cssTxt += rules[i].cssText + ' ';
+ }
+
+ styleElem.textContent = cssTxt;
+ var xml = mxUtils.getXml(svg.documentElement);
+
+ graph.setCellStyles(mxConstants.STYLE_IMAGE, 'data:image/svg+xml,' +
+ ((window.btoa) ? btoa(xml) : Base64.encode(xml, true)),
+ ui.getSelectionState().cells);
+ }), '#ffffff',
+ {
+ install: function(apply)
+ {
+ // ignore
+ },
+ destroy: function()
+ {
+ // ignore
+ }
+ });
+
+ container.appendChild(option);
+
+ // Shows container if rules are added
+ container.style.display = '';
+ }
+ });
+
+ addStyleRule(rule, 'fill', mxResources.get('fill'));
+ addStyleRule(rule, 'stroke', mxResources.get('line'));
+ addStyleRule(rule, 'stop-color', mxResources.get('gradient'));
+ }
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel.prototype.addEditOps = function(div)
+{
+ var ss = this.editorUi.getSelectionState();
+
+ if (ss.cells.length == 1)
+ {
+ var editSelect = document.createElement('select');
+ editSelect.style.width = '210px';
+ editSelect.style.textAlign = 'center';
+ editSelect.style.marginBottom = '2px';
+
+ var ops = ['edit', 'editLink', 'editShape', 'editImage', 'editData',
+ 'copyData', 'pasteData', 'editConnectionPoints', 'editGeometry',
+ 'editTooltip', 'editStyle'];
+
+ for (var i = 0; i < ops.length; i++)
+ {
+ var action = this.editorUi.actions.get(ops[i]);
+
+ if (action == null || action.enabled)
+ {
+ var editOption = document.createElement('option');
+ editOption.setAttribute('value', ops[i]);
+ var title = mxResources.get(ops[i]);
+ mxUtils.write(editOption, title + ((ops[i] == 'edit') ? '' : '...'));
+
+ if (action != null && action.shortcut != null)
+ {
+ title += ' (' + action.shortcut + ')';
+ }
+
+ editOption.setAttribute('title', title);
+ editSelect.appendChild(editOption);
+ }
+ }
+
+ if (editSelect.children.length > 1)
+ {
+ div.appendChild(editSelect);
+
+ mxEvent.addListener(editSelect, 'change', mxUtils.bind(this, function(evt)
+ {
+ var action = this.editorUi.actions.get(editSelect.value);
+ editSelect.value = 'edit';
+
+ if (action != null)
+ {
+ action.funct();
+ }
+ }));
+
+ if (ss.image && ss.cells.length > 0)
+ {
+ var graph = this.editorUi.editor.graph;
+ var state = graph.view.getState(graph.getSelectionCell());
+
+ if (state != null && mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE, null) != null)
+ {
+ var btn = mxUtils.button(mxResources.get('crop') + '...',
+ mxUtils.bind(this, function(evt)
+ {
+ this.editorUi.actions.get('crop').funct();
+ }));
+
+ btn.setAttribute('title', mxResources.get('crop'));
+ editSelect.style.width = '104px';
+ btn.style.width = '104px';
+ btn.style.marginLeft = '2px';
+ btn.style.marginBottom = '2px';
+
+ div.appendChild(btn);
+ }
+ }
+ }
+ }
+
+ return div;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel.prototype.addFill = function(container)
+{
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+ var ss = ui.getSelectionState();
+ container.style.paddingTop = '6px';
+ container.style.paddingBottom = '6px';
+
+ // Adds gradient direction option
+ var gradientSelect = document.createElement('select');
+ gradientSelect.style.position = 'absolute';
+ gradientSelect.style.left = '104px';
+ gradientSelect.style.width = '70px';
+ gradientSelect.style.height = '22px';
+ gradientSelect.style.padding = '0px';
+ gradientSelect.style.marginTop = '-3px';
+ gradientSelect.style.borderWidth = '1px';
+ gradientSelect.style.borderStyle = 'solid';
+ gradientSelect.style.boxSizing = 'border-box';
+
+ var fillStyleSelect = gradientSelect.cloneNode(false);
+
+ // Stops events from bubbling to color option event handler
+ mxEvent.addListener(gradientSelect, 'click', function(evt)
+ {
+ mxEvent.consume(evt);
+ });
+ mxEvent.addListener(fillStyleSelect, 'click', function(evt)
+ {
+ mxEvent.consume(evt);
+ });
+
+ var gradientPanel = this.createCellColorOption(mxResources.get('gradient'),
+ mxConstants.STYLE_GRADIENTCOLOR, 'default', function(color)
+ {
+ if (color == null || color == mxConstants.NONE)
+ {
+ gradientSelect.style.display = 'none';
+ }
+ else
+ {
+ gradientSelect.style.display = '';
+ }
+ }, function(color)
+ {
+ graph.updateCellStyles({'gradientColor': color}, graph.getSelectionCells());
+ }, graph.getDefaultColor(ss.style, mxConstants.STYLE_GRADIENTCOLOR,
+ graph.shapeForegroundColor, graph.shapeBackgroundColor));
+
+ var fillKey = (ss.style.shape == 'image') ? mxConstants.STYLE_IMAGE_BACKGROUND : mxConstants.STYLE_FILLCOLOR;
+
+ var fillPanel = this.createCellColorOption(mxResources.get('fill'),
+ fillKey, 'default', null, mxUtils.bind(this, function(color)
+ {
+ graph.setCellStyles(fillKey, color, ss.cells);
+ }), graph.getDefaultColor(ss.style, fillKey, graph.shapeBackgroundColor,
+ graph.shapeForegroundColor));
+
+ fillPanel.style.fontWeight = 'bold';
+ var tmpColor = mxUtils.getValue(ss.style, fillKey, null);
+ gradientPanel.style.display = (tmpColor != null && tmpColor != mxConstants.NONE &&
+ ss.fill && ss.style.shape != 'image') ? '' : 'none';
+
+ var directions = [mxConstants.DIRECTION_NORTH, mxConstants.DIRECTION_EAST,
+ mxConstants.DIRECTION_SOUTH, mxConstants.DIRECTION_WEST,
+ mxConstants.DIRECTION_RADIAL];
+
+ for (var i = 0; i < directions.length; i++)
+ {
+ var gradientOption = document.createElement('option');
+ gradientOption.setAttribute('value', directions[i]);
+ mxUtils.write(gradientOption, mxResources.get(directions[i]));
+ gradientSelect.appendChild(gradientOption);
+ }
+
+ gradientPanel.appendChild(gradientSelect);
+
+ var curFillStyle;
+
+ function populateFillStyle()
+ {
+ fillStyleSelect.innerHTML = '';
+ curFillStyle = 1;
+
+ for (var i = 0; i < Editor.fillStyles.length; i++)
+ {
+ var fillStyleOption = document.createElement('option');
+ fillStyleOption.setAttribute('value', Editor.fillStyles[i].val);
+ mxUtils.write(fillStyleOption, Editor.fillStyles[i].dispName);
+ fillStyleSelect.appendChild(fillStyleOption);
+ }
+ };
+
+ function populateRoughFillStyle()
+ {
+ fillStyleSelect.innerHTML = '';
+ curFillStyle = 2;
+
+ for (var i = 0; i < Editor.roughFillStyles.length; i++)
+ {
+ var fillStyleOption = document.createElement('option');
+ fillStyleOption.setAttribute('value', Editor.roughFillStyles[i].val);
+ mxUtils.write(fillStyleOption, Editor.roughFillStyles[i].dispName);
+ fillStyleSelect.appendChild(fillStyleOption);
+ }
+
+ fillStyleSelect.value = 'auto';
+ };
+
+ populateFillStyle();
+
+ if (ss.gradient)
+ {
+ fillPanel.appendChild(fillStyleSelect);
+ }
+
+ var listener = mxUtils.bind(this, function()
+ {
+ ss = ui.getSelectionState();
+ var value = mxUtils.getValue(ss.style, mxConstants.STYLE_GRADIENT_DIRECTION,
+ mxConstants.DIRECTION_SOUTH);
+ var fillStyle = mxUtils.getValue(ss.style, 'fillStyle', 'auto');
+
+ // Handles empty string which is not allowed as a value
+ if (value == '')
+ {
+ value = mxConstants.DIRECTION_SOUTH;
+ }
+
+ gradientSelect.value = value;
+ container.style.display = (ss.fill) ? '' : 'none';
+
+ var fillColor = mxUtils.getValue(ss.style, fillKey, null);
+
+ if (!ss.fill || fillColor == null || fillColor == mxConstants.NONE ||
+ ss.style.shape == 'filledEdge')
+ {
+ fillStyleSelect.style.display = 'none';
+ gradientPanel.style.display = 'none';
+ }
+ else
+ {
+ if (ss.style.sketch == '1')
+ {
+ if (curFillStyle != 2)
+ {
+ populateRoughFillStyle()
+ }
+ }
+ else if (curFillStyle != 1)
+ {
+ populateFillStyle();
+ }
+
+ fillStyleSelect.value = fillStyle;
+
+ //In case of switching from sketch to regular and fill type is not there
+ if (!fillStyleSelect.value)
+ {
+ fillStyle = 'auto';
+ fillStyleSelect.value = fillStyle;
+ }
+
+ fillStyleSelect.style.display = ss.style.sketch == '1' ||
+ gradientSelect.style.display == 'none'? '' : 'none';
+ gradientPanel.style.display = (ss.gradient &&
+ !ss.containsImage && (ss.style.sketch != '1' ||
+ fillStyle == 'solid' || fillStyle == 'auto')) ?
+ '' : 'none';
+ }
+ });
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+ listener();
+
+ mxEvent.addListener(gradientSelect, 'change', function(evt)
+ {
+ graph.setCellStyles(mxConstants.STYLE_GRADIENT_DIRECTION, gradientSelect.value, ss.cells);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_GRADIENT_DIRECTION],
+ 'values', [gradientSelect.value], 'cells', ss.cells));
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(fillStyleSelect, 'change', function(evt)
+ {
+ graph.setCellStyles('fillStyle', fillStyleSelect.value, ss.cells);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', ['fillStyle'],
+ 'values', [fillStyleSelect.value], 'cells', ss.cells));
+ mxEvent.consume(evt);
+ });
+
+ container.appendChild(fillPanel);
+ container.appendChild(gradientPanel);
+
+ // Adds custom colors
+ var custom = this.getCustomColors();
+
+ for (var i = 0; i < custom.length; i++)
+ {
+ container.appendChild(this.createCellColorOption(custom[i].title,
+ custom[i].key, custom[i].defaultValue));
+ }
+
+ return container;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel.prototype.getCustomColors = function()
+{
+ var ss = this.editorUi.getSelectionState();
+ var result = [];
+
+ if (ss.swimlane)
+ {
+ result.push({title: mxResources.get('laneColor'),
+ key: 'swimlaneFillColor', defaultValue: 'default'});
+ }
+
+ return result;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel.prototype.addStroke = function(container)
+{
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+ var ss = ui.getSelectionState();
+
+ container.style.paddingTop = '6px';
+ container.style.paddingBottom = '4px';
+ container.style.whiteSpace = 'normal';
+
+ var colorPanel = document.createElement('div');
+ colorPanel.style.fontWeight = 'bold';
+
+ if (!ss.stroke)
+ {
+ colorPanel.style.display = 'none';
+ }
+
+ // Adds gradient direction option
+ var styleSelect = document.createElement('select');
+ styleSelect.style.position = 'absolute';
+ styleSelect.style.height = '22px';
+ styleSelect.style.padding = '0px';
+ styleSelect.style.marginTop = '-3px';
+ styleSelect.style.textAlign = 'center';
+ styleSelect.style.boxSizing = 'border-box';
+ styleSelect.style.left = '90px';
+ styleSelect.style.width = '83px';
+ styleSelect.style.borderWidth = '1px';
+ styleSelect.style.borderStyle = 'solid';
+
+ var styles = ['sharp', 'rounded', 'curved'];
+
+ for (var i = 0; i < styles.length; i++)
+ {
+ var styleOption = document.createElement('option');
+ styleOption.setAttribute('value', styles[i]);
+ mxUtils.write(styleOption, mxResources.get(styles[i]));
+ styleSelect.appendChild(styleOption);
+ }
+
+ mxEvent.addListener(styleSelect, 'change', function(evt)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var keys = [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED];
+ // Default for rounded is 1
+ var values = ['0', null];
+
+ if (styleSelect.value == 'rounded')
+ {
+ values = ['1', null];
+ }
+ else if (styleSelect.value == 'curved')
+ {
+ values = [null, '1'];
+ }
+
+ for (var i = 0; i < keys.length; i++)
+ {
+ graph.setCellStyles(keys[i], values[i], ss.cells);
+ }
+
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', keys,
+ 'values', values, 'cells', ss.cells));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ // Stops events from bubbling to color option event handler
+ mxEvent.addListener(styleSelect, 'click', function(evt)
+ {
+ mxEvent.consume(evt);
+ });
+
+ var strokeKey = (ss.style.shape == 'image') ? mxConstants.STYLE_IMAGE_BORDER : mxConstants.STYLE_STROKECOLOR;
+ var label = (ss.style.shape == 'image') ? mxResources.get('border') : mxResources.get('line');
+
+ var lineColor = this.createCellColorOption(label, strokeKey, 'default', null, mxUtils.bind(this, function(color)
+ {
+ graph.setCellStyles(strokeKey, color, ss.cells);
+
+ // Sets strokeColor to inherit for rows and cells in tables
+ if (color == null || color == mxConstants.NONE)
+ {
+ var tableCells = [];
+
+ for (var i = 0; i < ss.cells.length; i++)
+ {
+ if (graph.isTableCell(ss.cells[i]) ||
+ graph.isTableRow(ss.cells[i]))
+ {
+ tableCells.push(ss.cells[i]);
+ }
+ }
+
+ if (tableCells.length > 0)
+ {
+ graph.setCellStyles(strokeKey, 'inherit', tableCells);
+ }
+ }
+ }), graph.shapeForegroundColor);
+
+ lineColor.appendChild(styleSelect);
+ colorPanel.appendChild(lineColor);
+
+ // Used if only edges selected
+ var stylePanel = colorPanel.cloneNode(false);
+ stylePanel.style.display = 'inline-flex';
+ stylePanel.style.alignItems = 'top';
+ stylePanel.style.fontWeight = 'normal';
+ stylePanel.style.whiteSpace = 'nowrap';
+ stylePanel.style.position = 'relative';
+ stylePanel.style.paddingLeft = '5px';
+ stylePanel.style.overflow = 'hidden';
+ stylePanel.style.marginTop = '2px';
+ stylePanel.style.width = '220px';
+
+ var addItem = mxUtils.bind(this, function(menu, width, cssName, keys, values)
+ {
+ var item = this.editorUi.menus.styleChange(menu, '', keys, values, 'geIcon', null);
+
+ var pat = document.createElement('div');
+ pat.style.width = width + 'px';
+ pat.style.height = '1px';
+ pat.style.borderBottom = '1px ' + cssName + ' ' + this.defaultStrokeColor;
+ pat.style.paddingTop = '6px';
+
+ item.firstChild.firstChild.style.padding = '0px 4px 0px 4px';
+ item.firstChild.firstChild.style.width = width + 'px';
+ item.firstChild.firstChild.appendChild(pat);
+
+ return item;
+ });
+
+ var pattern = this.editorUi.toolbar.addMenuFunctionInContainer(stylePanel, 'geSprite-orthogonal', mxResources.get('pattern'), false, mxUtils.bind(this, function(menu)
+ {
+ addItem(menu, 75, 'solid', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], [null, null]).setAttribute('title', mxResources.get('solid'));
+ addItem(menu, 75, 'dashed', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', null]).setAttribute('title', mxResources.get('dashed') + ' (1)');
+ addItem(menu, 75, 'dashed', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '8 8']).setAttribute('title', mxResources.get('dashed') + ' (2)');
+ addItem(menu, 75, 'dashed', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '12 12']).setAttribute('title', mxResources.get('dashed') + ' (3)');
+ addItem(menu, 75, 'dotted', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '1 1']).setAttribute('title', mxResources.get('dotted') + ' (1)');
+ addItem(menu, 75, 'dotted', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '1 2']).setAttribute('title', mxResources.get('dotted') + ' (2)');
+ addItem(menu, 75, 'dotted', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '1 4']).setAttribute('title', mxResources.get('dotted') + ' (3)');
+ }));
+
+ // Used for mixed selection (vertices and edges)
+ var altStylePanel = stylePanel.cloneNode(false);
+
+ var edgeShape = this.editorUi.toolbar.addMenuFunctionInContainer(altStylePanel, 'geSprite-connection', mxResources.get('connection'), false, mxUtils.bind(this, function(menu)
+ {
+ this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
+ [null, null, null, null], 'geIcon geSprite geSprite-connection', null, null, null, true).setAttribute('title', mxResources.get('line'));
+ this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
+ ['link', null, null, null], 'geIcon geSprite geSprite-linkedge', null, null, null, true).setAttribute('title', mxResources.get('link'));
+ this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
+ ['flexArrow', null, null, null], 'geIcon geSprite geSprite-arrow', null, null, null, true).setAttribute('title', mxResources.get('arrow'));
+ this.editorUi.menus.styleChange(menu, '', [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
+ ['arrow', null, null, null], 'geIcon geSprite geSprite-simplearrow', null, null, null, true).setAttribute('title', mxResources.get('simpleArrow'));
+ }));
+
+ var altPattern = this.editorUi.toolbar.addMenuFunctionInContainer(altStylePanel, 'geSprite-orthogonal', mxResources.get('pattern'), false, mxUtils.bind(this, function(menu)
+ {
+ addItem(menu, 33, 'solid', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], [null, null]).setAttribute('title', mxResources.get('solid'));
+ addItem(menu, 33, 'dashed', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', null]).setAttribute('title', mxResources.get('dashed') + ' (1)');
+ addItem(menu, 33, 'dashed', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '8 8']).setAttribute('title', mxResources.get('dashed') + ' (2)');
+ addItem(menu, 33, 'dashed', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '12 12']).setAttribute('title', mxResources.get('dashed') + ' (3)');
+ addItem(menu, 33, 'dotted', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '1 1']).setAttribute('title', mxResources.get('dotted') + ' (1)');
+ addItem(menu, 33, 'dotted', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '1 2']).setAttribute('title', mxResources.get('dotted') + ' (2)');
+ addItem(menu, 33, 'dotted', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', '1 4']).setAttribute('title', mxResources.get('dotted') + ' (3)');
+ }));
+
+ var stylePanel2 = stylePanel.cloneNode(false);
+
+ // Stroke width
+ var input = document.createElement('input');
+ input.style.position = 'absolute';
+ input.style.textAlign = 'right';
+ input.style.marginTop = '2px';
+ input.style.width = '52px';
+ input.style.height = '22px';
+ input.style.left = '146px';
+ input.style.borderWidth = '1px';
+ input.style.borderStyle = 'solid';
+ input.style.boxSizing = 'border-box';
+ input.setAttribute('title', mxResources.get('linewidth'));
+
+ stylePanel.appendChild(input);
+
+ var altInput = input.cloneNode(true);
+ altStylePanel.appendChild(altInput);
+
+ function update(evt)
+ {
+ // Maximum stroke width is 999
+ var value = parseFloat(input.value);
+ value = Math.min(999, Math.max(0, (isNaN(value)) ? 1 : value));
+
+ if (value != mxUtils.getValue(ss.style, mxConstants.STYLE_STROKEWIDTH, 1))
+ {
+ graph.setCellStyles(mxConstants.STYLE_STROKEWIDTH, value, ss.cells);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_STROKEWIDTH],
+ 'values', [value], 'cells', ss.cells));
+ }
+
+ input.value = value + ' pt';
+ mxEvent.consume(evt);
+ };
+
+ function altUpdate(evt)
+ {
+ // Maximum stroke width is 999
+ var value = parseFloat(altInput.value);
+ value = Math.min(999, Math.max(0, (isNaN(value)) ? 1 : value));
+
+ if (value != mxUtils.getValue(ss.style, mxConstants.STYLE_STROKEWIDTH, 1))
+ {
+ graph.setCellStyles(mxConstants.STYLE_STROKEWIDTH, value, ss.cells);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_STROKEWIDTH],
+ 'values', [value], 'cells', ss.cells));
+ }
+
+ altInput.value = value + ' pt';
+ mxEvent.consume(evt);
+ };
+
+ var stepper = this.createStepper(input, update, 1, 9);
+ stepper.style.display = input.style.display;
+ stepper.style.top = '2px';
+ stepper.style.left = '198px';
+ stylePanel.appendChild(stepper);
+
+ var altStepper = this.createStepper(altInput, altUpdate, 1, 9);
+ altStepper.style.display = altInput.style.display;
+ altInput.style.position = 'absolute';
+ altStepper.style.top = '2px';
+ altStepper.style.left = '198px';
+ altStylePanel.appendChild(altStepper);
+
+ mxEvent.addListener(input, 'blur', update);
+ mxEvent.addListener(input, 'change', update);
+
+ mxEvent.addListener(altInput, 'blur', altUpdate);
+ mxEvent.addListener(altInput, 'change', altUpdate);
+
+ var edgeStyle = this.editorUi.toolbar.addMenuFunctionInContainer(stylePanel2, 'geSprite-orthogonal', mxResources.get('waypoints'), false, mxUtils.bind(this, function(menu)
+ {
+ if (ss.style.shape != 'arrow')
+ {
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], [null, null, null], 'geIcon geSprite geSprite-straight', null, true).setAttribute('title', mxResources.get('straight'));
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['orthogonalEdgeStyle', null, null], 'geIcon geSprite geSprite-orthogonal', null, true).setAttribute('title', mxResources.get('orthogonal'));
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['elbowEdgeStyle', null, null, null], 'geIcon geSprite geSprite-horizontalelbow', null, true).setAttribute('title', mxResources.get('vertical'));
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['elbowEdgeStyle', 'vertical', null, null], 'geIcon geSprite geSprite-verticalelbow', null, true).setAttribute('title', mxResources.get('horizontal'));
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['isometricEdgeStyle', null, null, null], 'geIcon geSprite geSprite-horizontalisometric', null, true).setAttribute('title', mxResources.get('isometric'));
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['isometricEdgeStyle', 'vertical', null, null], 'geIcon geSprite geSprite-verticalisometric', null, true).setAttribute('title', mxResources.get('isometric'));
+
+ if (ss.style.shape == 'connector')
+ {
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['orthogonalEdgeStyle', '1', null], 'geIcon geSprite geSprite-curved', null, true).setAttribute('title', mxResources.get('curved'));
+ }
+
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE], ['entityRelationEdgeStyle', null, null], 'geIcon geSprite geSprite-entity', null, true).setAttribute('title', mxResources.get('entityRelation'));
+ }
+ }));
+
+ var lineStart = this.editorUi.toolbar.addMenuFunctionInContainer(stylePanel2, 'geSprite-startclassic', mxResources.get('linestart'), false, mxUtils.bind(this, function(menu)
+ {
+ if (ss.style.shape == 'connector' || ss.style.shape == 'flexArrow' || ss.style.shape == 'filledEdge' || ss.style.shape == 'wire')
+ {
+ var item = this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.NONE, 0], 'geIcon', null, false);
+ item.setAttribute('title', mxResources.get('none'));
+
+ var font = document.createElement('span');
+ font.style.fontSize = '11px';
+ mxUtils.write(font, mxResources.get('none'));
+ item.firstChild.firstChild.appendChild(font);
+
+ if (ss.style.shape == 'connector' || ss.style.shape == 'filledEdge' || ss.style.shape == 'wire')
+ {
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_CLASSIC, 1], null, null, false, Format.classicFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_CLASSIC_THIN, 1], null, null, false, Format.classicThinFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OPEN, 0], null, null, false, Format.openFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OPEN_THIN, 0], null, null, false, Format.openThinFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['openAsync', 0], null, null, false, Format.openAsyncFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_BLOCK, 1], null, null, false, Format.blockFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_BLOCK_THIN, 1], null, null, false, Format.blockThinFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['async', 1], null, null, false, Format.asyncFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OVAL, 1], null, null, false, Format.ovalFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND, 1], null, null, false, Format.diamondFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND_THIN, 1], null, null, false, Format.diamondThinFilledMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_CLASSIC, 0], null, null, false, Format.classicMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_CLASSIC_THIN, 0], null, null, false, Format.classicThinMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_BLOCK, 0], null, null, false, Format.blockMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_BLOCK_THIN, 0], null, null, false, Format.blockThinMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['async', 0], null, null, false, Format.asyncMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_OVAL, 0], null, null, false, Format.ovalMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND, 0], null, null, false, Format.diamondMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], [mxConstants.ARROW_DIAMOND_THIN, 0], null, null, false, Format.diamondThinMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['box', 0], null, null, false, Format.boxMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['halfCircle', 0], null, null, false, Format.halfCircleMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['dash', 0], null, null, false, Format.dashMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['cross', 0], null, null, false, Format.crossMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['circlePlus', 0], null, null, false, Format.circlePlusMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['circle', 1], null, null, false, Format.circleMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['baseDash', 0], null, null, false, Format.baseDashMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['ERone', 0], null, null, false, Format.EROneMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['ERmandOne', 0], null, null, false, Format.ERmandOneMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['ERmany', 0], null, null, false, Format.ERmanyMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['ERoneToMany', 0], null, null, false, Format.ERoneToManyMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['ERzeroToOne', 0], null, null, false, Format.ERzeroToOneMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['ERzeroToMany', 0], null, null, false, Format.ERzeroToManyMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['doubleBlock', 0], null, null, false, Format.doubleBlockMarkerImage.src));
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW, 'startFill'], ['doubleBlock', 1], null, null, false, Format.doubleBlockFilledMarkerImage.src));
+ }
+ else
+ {
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_STARTARROW], [mxConstants.ARROW_BLOCK], 'geIcon geSprite geSprite-startblocktrans', null, false).setAttribute('title', mxResources.get('block'));
+ }
+
+ menu.div.style.width = '40px';
+
+ window.setTimeout(mxUtils.bind(this, function()
+ {
+ if (menu.div != null)
+ {
+ mxUtils.fit(menu.div);
+ }
+ }), 0);
+ }
+ }));
+
+ var lineEnd = this.editorUi.toolbar.addMenuFunctionInContainer(stylePanel2, 'geSprite-endclassic', mxResources.get('lineend'), false, mxUtils.bind(this, function(menu)
+ {
+ if (ss.style.shape == 'connector' || ss.style.shape == 'flexArrow' || ss.style.shape == 'filledEdge' || ss.style.shape == 'wire')
+ {
+ var item = this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.NONE, 0], 'geIcon', null, false);
+ item.setAttribute('title', mxResources.get('none'));
+
+ var font = document.createElement('span');
+ font.style.fontSize = '11px';
+ mxUtils.write(font, mxResources.get('none'));
+ item.firstChild.firstChild.appendChild(font);
+
+ if (ss.style.shape == 'connector' || ss.style.shape == 'filledEdge' || ss.style.shape == 'wire')
+ {
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_CLASSIC, 1], null, null, false, Format.classicFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_CLASSIC_THIN, 1], null, null, false, Format.classicThinFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OPEN, 0], null, null, false, Format.openFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OPEN_THIN, 0], null, null, false, Format.openThinFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['openAsync', 0], null, null, false, Format.openAsyncFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_BLOCK, 1], null, null, false, Format.blockFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_BLOCK_THIN, 1], null, null, false, Format.blockThinFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['async', 1], null, null, false, Format.asyncFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OVAL, 1], null, null, false, Format.ovalFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND, 1], null, null, false, Format.diamondFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND_THIN, 1], null, null, false, Format.diamondThinFilledMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_CLASSIC, 0], null, null, false, Format.classicMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_CLASSIC_THIN, 0], null, null, false, Format.classicThinMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_BLOCK, 0], null, null, false, Format.blockMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_BLOCK_THIN, 0], null, null, false, Format.blockThinMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['async', 0], null, null, false, Format.asyncMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_OVAL, 0], null, null, false, Format.ovalMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND, 0], null, null, false, Format.diamondMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], [mxConstants.ARROW_DIAMOND_THIN, 0], null, null, false, Format.diamondThinMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['box', 0], null, null, false, Format.boxMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['halfCircle', 0], null, null, false, Format.halfCircleMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['dash', 0], null, null, false, Format.dashMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['cross', 0], null, null, false, Format.crossMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['circlePlus', 0], null, null, false, Format.circlePlusMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['circle', 0], null, null, false, Format.circleMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['baseDash', 0], null, null, false, Format.baseDashMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['ERone', 0], null, null, false, Format.EROneMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['ERmandOne', 0], null, null, false, Format.ERmandOneMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['ERmany', 0], null, null, false, Format.ERmanyMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['ERoneToMany', 0], null, null, false, Format.ERoneToManyMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['ERzeroToOne', 0], null, null, false, Format.ERzeroToOneMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['ERzeroToMany', 0], null, null, false, Format.ERzeroToManyMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['doubleBlock', 0], null, null, false, Format.doubleBlockMarkerImage.src), 'scaleX(-1)');
+ Format.processMenuIcon(this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW, 'endFill'], ['doubleBlock', 1], null, null, false, Format.doubleBlockFilledMarkerImage.src), 'scaleX(-1)');
+ }
+ else
+ {
+ this.editorUi.menus.edgeStyleChange(menu, '', [mxConstants.STYLE_ENDARROW], [mxConstants.ARROW_BLOCK], 'geIcon geSprite geSprite-endblocktrans', null, false).setAttribute('title', mxResources.get('block'));
+ }
+
+ menu.div.style.width = '40px';
+
+ window.setTimeout(mxUtils.bind(this, function()
+ {
+ if (menu.div != null)
+ {
+ mxUtils.fit(menu.div);
+ }
+ }), 0);
+ }
+ }));
+
+ this.addArrow(edgeShape);
+ this.addArrow(edgeStyle).style.marginTop = '-1px';
+ this.addArrow(lineStart);
+ this.addArrow(lineEnd);
+
+ var symbol = this.addArrow(pattern);
+ symbol.className = 'geIcon';
+ pattern.style.width = '134px';
+
+ var altSymbol = this.addArrow(altPattern);
+ altSymbol.className = 'geIcon';
+ altSymbol.style.width = '22px';
+
+ var solid = document.createElement('div');
+ solid.style.width = '102px';
+ solid.style.height = '10px';
+ solid.style.borderBottom = '1px solid ' + this.defaultStrokeColor;
+ solid.style.marginLeft = '10px';
+ symbol.appendChild(solid);
+
+ var altSolid = document.createElement('div');
+ altSolid.style.width = '30px';
+ altSolid.style.height = '10px';
+ altSolid.style.borderBottom = '1px solid ' + this.defaultStrokeColor;
+ altSolid.style.marginLeft = '10px';
+ altSymbol.appendChild(altSolid);
+
+ container.appendChild(colorPanel);
+ container.appendChild(altStylePanel);
+ container.appendChild(stylePanel);
+
+ var arrowPanel = stylePanel.cloneNode(false);
+ arrowPanel.style.display = 'block';
+ arrowPanel.style.padding = '5px 4px 6px 0px';
+ arrowPanel.style.fontWeight = 'normal';
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.maxWidth = '78px';
+ span.style.overflow = 'hidden';
+ span.style.textOverflow = 'ellipsis';
+ span.style.marginLeft = '0px';
+ span.style.marginBottom = '12px';
+ span.style.marginTop = '2px';
+ span.style.fontWeight = 'normal';
+
+ mxUtils.write(span, mxResources.get('lineend'));
+ arrowPanel.appendChild(span);
+
+ var endSpacingUpdate, endSizeUpdate;
+ var endSpacing = this.addUnitInput(arrowPanel, 'pt', 98, 52, function()
+ {
+ endSpacingUpdate.apply(this, arguments);
+ });
+ var endSize = this.addUnitInput(arrowPanel, 'pt', 30, 52, function()
+ {
+ endSizeUpdate.apply(this, arguments);
+ });
+
+ mxUtils.br(arrowPanel);
+
+ var spacer = document.createElement('div');
+ spacer.style.height = '8px';
+ arrowPanel.appendChild(spacer);
+
+ span = span.cloneNode(false);
+ mxUtils.write(span, mxResources.get('linestart'));
+ arrowPanel.appendChild(span);
+
+ var startSpacingUpdate, startSizeUpdate;
+ var startSpacing = this.addUnitInput(arrowPanel, 'pt', 98, 52, function()
+ {
+ startSpacingUpdate.apply(this, arguments);
+ });
+ var startSize = this.addUnitInput(arrowPanel, 'pt', 30, 52, function()
+ {
+ startSizeUpdate.apply(this, arguments);
+ });
+
+ mxUtils.br(arrowPanel);
+ this.addLabel(arrowPanel, mxResources.get('spacing'),
+ 98, 64).style.fontSize = '11px';
+ this.addLabel(arrowPanel, mxResources.get('size'),
+ 30, 64).style.fontSize = '11px';
+ mxUtils.br(arrowPanel);
+
+ var perimeterPanel = colorPanel.cloneNode(false);
+ perimeterPanel.style.fontWeight = 'normal';
+ perimeterPanel.style.position = 'relative';
+ perimeterPanel.style.paddingLeft = '16px'
+ perimeterPanel.style.marginBottom = '2px';
+ perimeterPanel.style.marginTop = '6px';
+ perimeterPanel.style.borderWidth = '0px';
+ perimeterPanel.style.paddingBottom = '18px';
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.marginLeft = '3px';
+ span.style.marginBottom = '12px';
+ span.style.marginTop = '1px';
+ span.style.fontWeight = 'normal';
+ span.style.width = '120px';
+ mxUtils.write(span, mxResources.get('perimeter'));
+ perimeterPanel.appendChild(span);
+
+ var perimeterUpdate;
+ var perimeterSpacing = this.addUnitInput(perimeterPanel, 'pt', 30, 52, function()
+ {
+ perimeterUpdate.apply(this, arguments);
+ });
+
+ if (ss.edges.length == ss.cells.length)
+ {
+ container.appendChild(stylePanel2);
+ container.appendChild(arrowPanel);
+ }
+ else if (ss.vertices.length == ss.cells.length)
+ {
+ container.appendChild(perimeterPanel);
+ }
+
+ var listener = mxUtils.bind(this, function(sender, evt, force)
+ {
+ ss = ui.getSelectionState();
+
+ if (force || document.activeElement != input)
+ {
+ var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_STROKEWIDTH, 1));
+ input.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ if (force || document.activeElement != altInput)
+ {
+ var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_STROKEWIDTH, 1));
+ altInput.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ styleSelect.style.visibility = (ss.style.shape == 'connector' ||
+ ss.style.shape == 'filledEdge' || ss.style.shape == 'wire') ?
+ '' : 'hidden';
+
+ if (mxUtils.getValue(ss.style, mxConstants.STYLE_CURVED, null) == '1')
+ {
+ styleSelect.value = 'curved';
+ }
+ else if (mxUtils.getValue(ss.style, mxConstants.STYLE_ROUNDED, null) == '1')
+ {
+ styleSelect.value = 'rounded';
+ }
+
+ if (mxUtils.getValue(ss.style, mxConstants.STYLE_DASHED, null) == '1')
+ {
+ if (mxUtils.getValue(ss.style, mxConstants.STYLE_DASH_PATTERN, null) == null ||
+ String(mxUtils.getValue(ss.style, mxConstants.STYLE_DASH_PATTERN, '')).substring(0, 2) != '1 ')
+ {
+ solid.style.borderBottom = '1px dashed ' + this.defaultStrokeColor;
+ }
+ else
+ {
+ solid.style.borderBottom = '1px dotted ' + this.defaultStrokeColor;
+ }
+ }
+ else
+ {
+ solid.style.borderBottom = '1px solid ' + this.defaultStrokeColor;
+ }
+
+ altSolid.style.borderBottom = solid.style.borderBottom;
+
+ // Updates toolbar icon for edge style
+ var edgeStyleDiv = edgeStyle.getElementsByTagName('div')[0];
+
+ if (edgeStyleDiv != null)
+ {
+ var es = mxUtils.getValue(ss.style, mxConstants.STYLE_EDGE, null);
+
+ if (mxUtils.getValue(ss.style, mxConstants.STYLE_NOEDGESTYLE, null) == '1')
+ {
+ es = null;
+ }
+
+ if (es == 'orthogonalEdgeStyle' && mxUtils.getValue(ss.style, mxConstants.STYLE_CURVED, null) == '1')
+ {
+ edgeStyleDiv.className = 'geSprite geSprite-curved';
+ }
+ else if (es == 'straight' || es == 'none' || es == null)
+ {
+ edgeStyleDiv.className = 'geSprite geSprite-straight';
+ }
+ else if (es == 'entityRelationEdgeStyle')
+ {
+ edgeStyleDiv.className = 'geSprite geSprite-entity';
+ }
+ else if (es == 'elbowEdgeStyle')
+ {
+ edgeStyleDiv.className = 'geSprite ' + ((mxUtils.getValue(ss.style,
+ mxConstants.STYLE_ELBOW, null) == 'vertical') ?
+ 'geSprite-verticalelbow' : 'geSprite-horizontalelbow');
+ }
+ else if (es == 'isometricEdgeStyle')
+ {
+ edgeStyleDiv.className = 'geSprite ' + ((mxUtils.getValue(ss.style,
+ mxConstants.STYLE_ELBOW, null) == 'vertical') ?
+ 'geSprite-verticalisometric' : 'geSprite-horizontalisometric');
+ }
+ else
+ {
+ edgeStyleDiv.className = 'geSprite geSprite-orthogonal';
+ }
+ }
+
+ // Updates icon for edge shape
+ var edgeShapeDiv = edgeShape.getElementsByTagName('div')[0];
+
+ if (edgeShapeDiv != null)
+ {
+ if (ss.style.shape == 'link')
+ {
+ edgeShapeDiv.className = 'geSprite geSprite-linkedge';
+ }
+ else if (ss.style.shape == 'flexArrow')
+ {
+ edgeShapeDiv.className = 'geSprite geSprite-arrow';
+ }
+ else if (ss.style.shape == 'arrow')
+ {
+ edgeShapeDiv.className = 'geSprite geSprite-simplearrow';
+ }
+ else
+ {
+ edgeShapeDiv.className = 'geSprite geSprite-connection';
+ }
+ }
+
+ if (ss.edges.length == ss.cells.length)
+ {
+ altStylePanel.style.display = '';
+ stylePanel.style.display = 'none';
+ }
+ else
+ {
+ altStylePanel.style.display = 'none';
+ stylePanel.style.display = '';
+ }
+
+ if (Graph.lineJumpsEnabled && ss.edges.length > 0 &&
+ ss.vertices.length == 0 && ss.lineJumps)
+ {
+ container.style.borderBottomStyle = 'none';
+ }
+
+ function updateArrow(marker, fill, elt, prefix)
+ {
+ var markerDiv = elt.getElementsByTagName('div')[0];
+
+ if (markerDiv != null)
+ {
+ ui.updateCssForMarker(markerDiv, prefix, ss.style.shape, marker, fill);
+ }
+
+ return markerDiv;
+ };
+
+ var sourceDiv = updateArrow(mxUtils.getValue(ss.style, mxConstants.STYLE_STARTARROW, null),
+ mxUtils.getValue(ss.style, 'startFill', '1'), lineStart, 'start');
+ var targetDiv = updateArrow(mxUtils.getValue(ss.style, mxConstants.STYLE_ENDARROW, null),
+ mxUtils.getValue(ss.style, 'endFill', '1'), lineEnd, 'end');
+
+ // Special cases for markers
+ if (sourceDiv != null && targetDiv != null)
+ {
+ if (ss.style.shape == 'arrow')
+ {
+ sourceDiv.className = 'geSprite geSprite-noarrow';
+ targetDiv.className = 'geSprite geSprite-endblocktrans';
+ }
+ else if (ss.style.shape == 'link')
+ {
+ sourceDiv.className = 'geSprite geSprite-noarrow';
+ targetDiv.className = 'geSprite geSprite-noarrow';
+ }
+ }
+
+ mxUtils.setOpacity(edgeStyle, (ss.style.shape == 'arrow') ? 30 : 100);
+
+ if (ss.style.shape != 'connector' && ss.style.shape != 'flexArrow' &&
+ ss.style.shape != 'filledEdge' && ss.style.shape != 'wire')
+ {
+ mxUtils.setOpacity(lineStart, 30);
+ mxUtils.setOpacity(lineEnd, 30);
+ }
+ else
+ {
+ mxUtils.setOpacity(lineStart, 100);
+ mxUtils.setOpacity(lineEnd, 100);
+ }
+
+ if (force || document.activeElement != startSize)
+ {
+ var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE));
+ startSize.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ if (force || document.activeElement != startSpacing)
+ {
+ var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_SOURCE_PERIMETER_SPACING, 0));
+ startSpacing.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ if (force || document.activeElement != endSize)
+ {
+ var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE));
+ endSize.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ if (force || document.activeElement != startSpacing)
+ {
+ var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_TARGET_PERIMETER_SPACING, 0));
+ endSpacing.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+
+ if (force || document.activeElement != perimeterSpacing)
+ {
+ var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_PERIMETER_SPACING, 0));
+ perimeterSpacing.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+ });
+
+ startSizeUpdate = this.installInputHandler(startSize, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE, 0, 999, ' pt');
+ startSpacingUpdate = this.installInputHandler(startSpacing, mxConstants.STYLE_SOURCE_PERIMETER_SPACING, 0, -999, 999, ' pt');
+ endSizeUpdate = this.installInputHandler(endSize, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE, 0, 999, ' pt');
+ endSpacingUpdate = this.installInputHandler(endSpacing, mxConstants.STYLE_TARGET_PERIMETER_SPACING, 0, -999, 999, ' pt');
+ perimeterUpdate = this.installInputHandler(perimeterSpacing, mxConstants.STYLE_PERIMETER_SPACING, 0, 0, 999, ' pt');
+
+ this.addKeyHandler(input, listener);
+ this.addKeyHandler(startSize, listener);
+ this.addKeyHandler(startSpacing, listener);
+ this.addKeyHandler(endSize, listener);
+ this.addKeyHandler(endSpacing, listener);
+ this.addKeyHandler(perimeterSpacing, listener);
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+ listener();
+
+ return container;
+};
+
+/**
+ * Adds UI for configuring line jumps.
+ */
+StyleFormatPanel.prototype.addLineJumps = function(container)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+ var ss = ui.getSelectionState();
+
+ if (Graph.lineJumpsEnabled && ss.edges.length > 0 &&
+ ss.vertices.length == 0 && ss.lineJumps)
+ {
+ container.style.padding = '2px 0px 24px 14px';
+
+ var span = document.createElement('div');
+ span.style.position = 'absolute';
+ span.style.maxWidth = '78px';
+ span.style.overflow = 'hidden';
+ span.style.textOverflow = 'ellipsis';
+
+ mxUtils.write(span, mxResources.get('lineJumps'));
+ container.appendChild(span);
+
+ var styleSelect = document.createElement('select');
+ styleSelect.style.position = 'absolute';
+ styleSelect.style.height = '21px';
+ styleSelect.style.padding = '0px';
+ styleSelect.style.marginTop = '-2px';
+ styleSelect.style.boxSizing = 'border-box';
+ styleSelect.style.textAlign = 'center';
+ styleSelect.style.right = '84px';
+ styleSelect.style.width = '64px';
+ styleSelect.style.borderWidth = '1px';
+ styleSelect.style.borderStyle = 'solid';
+
+ var styles = ['none', 'arc', 'gap', 'sharp', 'line'];
+
+ for (var i = 0; i < styles.length; i++)
+ {
+ var styleOption = document.createElement('option');
+ styleOption.setAttribute('value', styles[i]);
+ mxUtils.write(styleOption, mxResources.get(styles[i]));
+ styleSelect.appendChild(styleOption);
+ }
+
+ mxEvent.addListener(styleSelect, 'change', function(evt)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ graph.setCellStyles('jumpStyle', styleSelect.value, ss.cells);
+ ui.fireEvent(new mxEventObject('styleChanged', 'keys', ['jumpStyle'],
+ 'values', [styleSelect.value], 'cells', ss.cells));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ // Stops events from bubbling to color option event handler
+ mxEvent.addListener(styleSelect, 'click', function(evt)
+ {
+ mxEvent.consume(evt);
+ });
+
+ container.appendChild(styleSelect);
+
+ var jumpSizeUpdate;
+
+ var jumpSize = this.addUnitInput(container, 'pt', 16, 52, function()
+ {
+ jumpSizeUpdate.apply(this, arguments);
+ });
+
+ jumpSizeUpdate = this.installInputHandler(jumpSize, 'jumpSize',
+ Graph.defaultJumpSize, 0, 999, ' pt');
+
+ var listener = mxUtils.bind(this, function(sender, evt, force)
+ {
+ ss = ui.getSelectionState();
+ styleSelect.value = mxUtils.getValue(ss.style, 'jumpStyle', 'none');
+
+ if (force || document.activeElement != jumpSize)
+ {
+ var tmp = parseInt(mxUtils.getValue(ss.style, 'jumpSize', Graph.defaultJumpSize));
+ jumpSize.value = (isNaN(tmp)) ? '' : tmp + ' pt';
+ }
+ });
+
+ this.addKeyHandler(jumpSize, listener);
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+ listener();
+ }
+ else
+ {
+ container.style.display = 'none';
+ }
+
+ return container;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel.prototype.addEffects = function(div)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+ var ss = ui.getSelectionState();
+
+ div.style.paddingTop = '4px';
+ div.style.paddingBottom = '4px';
+
+ var table = document.createElement('table');
+
+ table.style.width = '210px';
+ table.style.fontWeight = 'bold';
+ table.style.tableLayout = 'fixed';
+ var tbody = document.createElement('tbody');
+ var row = document.createElement('tr');
+ row.style.padding = '0px';
+ var left = document.createElement('td');
+ left.style.padding = '0px';
+ left.style.width = '50%';
+ left.setAttribute('valign', 'top');
+
+ var right = left.cloneNode(true);
+ right.style.paddingLeft = '8px';
+ row.appendChild(left);
+ row.appendChild(right);
+ tbody.appendChild(row);
+ table.appendChild(tbody);
+ div.appendChild(table);
+
+ var current = left;
+
+ var addOption = mxUtils.bind(this, function(label, key, defaultValue, fn)
+ {
+ var opt = this.createCellOption(label, key, defaultValue, null, null, fn);
+ opt.style.width = '100%';
+ current.appendChild(opt);
+ current = (current == left) ? right : left;
+ });
+
+ var listener = mxUtils.bind(this, function(sender, evt, force)
+ {
+ ss = ui.getSelectionState();
+
+ left.innerText = '';
+ right.innerText = '';
+ current = left;
+
+ if (ss.rounded)
+ {
+ addOption(mxResources.get('rounded'), mxConstants.STYLE_ROUNDED, 0);
+ }
+
+ if (ss.swimlane)
+ {
+ addOption(mxResources.get('divider'), 'swimlaneLine', 1);
+ }
+
+ addOption(mxResources.get('sketch'), 'sketch', 0, function(cells, enabled)
+ {
+ graph.updateCellStyles({'sketch': (enabled) ? '1' : null,
+ 'curveFitting': (enabled) ? Editor.sketchDefaultCurveFitting : null,
+ 'jiggle': (enabled) ? Editor.sketchDefaultJiggle : null}, cells);
+ });
+
+ if (ss.glass)
+ {
+ addOption(mxResources.get('glass'), mxConstants.STYLE_GLASS, 0);
+ }
+
+ if (!ss.containsImage)
+ {
+ addOption(mxResources.get('shadow'), mxConstants.STYLE_SHADOW, 0);
+ }
+ });
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+ listener();
+
+ return div;
+}
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+StyleFormatPanel.prototype.addStyleOps = function(div)
+{
+ var ss = this.editorUi.getSelectionState();
+
+ if (ss.cells.length == 1)
+ {
+ this.addActions(div, ['setAsDefaultStyle']);
+ }
+
+ return div;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramStylePanel = function(format, editorUi, container)
+{
+ BaseFormatPanel.call(this, format, editorUi, container);
+ this.init();
+};
+
+mxUtils.extend(DiagramStylePanel, BaseFormatPanel);
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramStylePanel.prototype.init = function()
+{
+ var ui = this.editorUi;
+
+ this.darkModeChangedListener = mxUtils.bind(this, function()
+ {
+ this.format.cachedStyleEntries = [];
+ });
+
+ ui.addListener('darkModeChanged', this.darkModeChangedListener);
+ this.container.appendChild(this.addView(this.createPanel()));
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramStylePanel.prototype.getGlobalStyleButtons = function()
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+
+ var buttons = [mxUtils.button(mxResources.get('sketch'), mxUtils.bind(this, function(evt)
+ {
+ var value = !Editor.sketchMode;
+ graph.updateCellStyles({'sketch': (value) ? '1' : null,
+ 'curveFitting': (value) ? Editor.sketchDefaultCurveFitting : null,
+ 'jiggle': (value) ? Editor.sketchDefaultJiggle : null},
+ graph.getVerticesAndEdges());
+ ui.setSketchMode(value);
+ mxEvent.consume(evt);
+ })), mxUtils.button(mxResources.get('rounded'), mxUtils.bind(this, function(evt)
+ {
+ // Checks if all cells are rounded
+ var cells = graph.getVerticesAndEdges();
+ var rounded = true;
+
+ if (cells.length > 0)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var style = graph.getCellStyle(cells[i]);
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_ROUNDED, 0) == 0)
+ {
+ rounded = false;
+ break;
+ }
+ }
+ }
+
+ rounded = !rounded;
+ graph.updateCellStyles({'rounded': (rounded) ? '1' : '0'}, cells);
+
+ if (rounded)
+ {
+ graph.currentEdgeStyle['rounded'] = '1';
+ graph.currentVertexStyle['rounded'] = '1';
+ }
+ else
+ {
+ delete graph.currentEdgeStyle['rounded'];
+ delete graph.currentVertexStyle['rounded'];
+ }
+
+ mxEvent.consume(evt);
+ }))];
+
+ return buttons;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramStylePanel.prototype.addView = function(div)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+ var model = graph.getModel();
+ var gridColor = graph.view.gridColor;
+
+ div.style.paddingTop = '2px';
+ div.style.whiteSpace = 'normal';
+
+ var opts = document.createElement('div');
+ opts.style.marginRight = '16px';
+ opts.style.paddingBottom = '2px';
+
+ var table = document.createElement('table');
+ table.style.width = '204px';
+
+ var tbody = document.createElement('tbody');
+ var row = document.createElement('tr');
+ row.style.padding = '0px';
+
+ var left = document.createElement('td');
+ left.style.textAlign = 'center';
+ left.style.padding = '2px';
+ left.style.width = '50%';
+
+ var right = left.cloneNode(true);
+ var buttons = this.getGlobalStyleButtons();
+
+ for (var i = 0; i < buttons.length; i += 2)
+ {
+ var btn = buttons[i];
+ btn.style.height = '22px';
+ btn.style.width = '92px';
+
+ left.appendChild(btn);
+ row.appendChild(left);
+
+ btn = buttons[i + 1];
+
+ if (btn != null)
+ {
+ btn.style.height = '22px';
+ btn.style.width = '92px';
+ right.appendChild(btn);
+ }
+
+ row.appendChild(right);
+ tbody.appendChild(row);
+
+ left = left.cloneNode(false);
+ right = right.cloneNode(false);
+ row = row.cloneNode(false);
+ }
+
+ table.appendChild(tbody);
+ opts.appendChild(table);
+ div.appendChild(opts);
+
+ var defaultStyles = ['fillColor', 'strokeColor', 'fontColor', 'gradientColor'];
+
+ div.style.whiteSpace = 'normal';
+ div.style.paddingLeft = '18px';
+ div.style.paddingTop = '6px';
+
+ var updateCells = mxUtils.bind(this, function(styles, graphStyle)
+ {
+ var cells = graph.getVerticesAndEdges();
+
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var style = graph.getCellStyle(cells[i]);
+
+ // Handles special label background color
+ if (!ignoreGraphStyle && style['labelBackgroundColor'] != null)
+ {
+ graph.updateCellStyles({'labelBackgroundColor': (graphStyle != null) ?
+ graphStyle.background : null}, [cells[i]]);
+ }
+ else if (ignoreGraphStyle)
+ {
+ graph.updateCellStyles({'labelBackgroundColor': mxConstants.NONE}, [cells[i]]);
+ }
+
+ var edge = model.isEdge(cells[i]);
+ var newStyle = model.getStyle(cells[i]);
+ var current = (edge) ? graph.currentEdgeStyle : graph.currentVertexStyle;
+
+ for (var j = 0; j < styles.length; j++)
+ {
+ if ((style[styles[j]] != null && style[styles[j]] != mxConstants.NONE) ||
+ (styles[j] != mxConstants.STYLE_FILLCOLOR &&
+ styles[j] != mxConstants.STYLE_STROKECOLOR))
+ {
+ if (ignoreGraphStyle && edge && styles[j] == mxConstants.STYLE_FONTCOLOR)
+ {
+ newStyle = mxUtils.setStyle(newStyle, styles[j], 'default');
+ }
+ else
+ {
+ newStyle = mxUtils.setStyle(newStyle, styles[j], current[styles[j]]);
+ }
+ }
+ }
+
+ model.setStyle(cells[i], newStyle);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ });
+
+ var removeStyles = mxUtils.bind(this, function(style, styles, defaultStyle)
+ {
+ if (style != null)
+ {
+ for (var j = 0; j < styles.length; j++)
+ {
+ if (((style[styles[j]] != null &&
+ style[styles[j]] != mxConstants.NONE) ||
+ (styles[j] != mxConstants.STYLE_FILLCOLOR &&
+ styles[j] != mxConstants.STYLE_STROKECOLOR)))
+ {
+ style[styles[j]] = defaultStyle[styles[j]];
+ }
+ }
+ }
+ });
+
+ var ignoreGraphStyle = true;
+
+ var applyStyle = mxUtils.bind(this, function(style, result, cell, graphStyle, theGraph)
+ {
+ if (style != null)
+ {
+ if (cell != null)
+ {
+ // Handles special label background color
+ if (!ignoreGraphStyle && result['labelBackgroundColor'] != null)
+ {
+ var bg = (graphStyle != null) ? graphStyle.background : null;
+ theGraph = (theGraph != null) ? theGraph : graph;
+
+ if (bg == null)
+ {
+ bg = theGraph.background;
+ }
+
+ if (bg == null)
+ {
+ bg = theGraph.defaultPageBackgroundColor;
+ }
+
+ result['labelBackgroundColor'] = bg;
+ }
+ else if (ignoreGraphStyle)
+ {
+ result['labelBackgroundColor'] = mxConstants.NONE;
+ }
+ }
+
+ for (var key in style)
+ {
+ if (cell == null || ((result[key] != null &&
+ result[key] != mxConstants.NONE) ||
+ (key != mxConstants.STYLE_FILLCOLOR &&
+ key != mxConstants.STYLE_STROKECOLOR)))
+ {
+ if (ignoreGraphStyle && model.isEdge(cell) &&
+ key == mxConstants.STYLE_FONTCOLOR)
+ {
+ result[key] = 'default';
+ }
+ else
+ {
+ result[key] = style[key];
+ }
+ }
+ }
+ }
+ });
+
+ var createPreview = mxUtils.bind(this, function(commonStyle, vertexStyle, edgeStyle, graphStyle, container)
+ {
+ // Wrapper needed to catch events
+ var div = document.createElement('div');
+ div.style.background = (Editor.isDarkMode() ? '#2a252f' : '#f1f3f4');
+ div.style.position = 'absolute';
+ div.style.display = 'inline-block';
+ div.style.overflow = 'hidden';
+ div.style.pointerEvents = 'none';
+ div.style.width = '100%';
+ div.style.height = '100%';
+ container.appendChild(div);
+
+ var graph2 = new Graph(div, null, null, graph.getStylesheet());
+ graph2.shapeBackgroundColor = div.style.background;
+ graph2.resetViewOnRootChange = false;
+ graph2.foldingEnabled = false;
+ graph2.gridEnabled = false;
+ graph2.autoScroll = false;
+ graph2.setTooltips(false);
+ graph2.setConnectable(false);
+ graph2.setPanning(false);
+ graph2.setEnabled(false);
+
+ graph2.getCellStyle = function(cell, resolve)
+ {
+ resolve = (resolve != null) ? resolve : true;
+ var result = mxUtils.clone(graph.getCellStyle.apply(this, arguments));
+ var defaultStyle = graph.stylesheet.getDefaultVertexStyle();
+ var appliedStyle = vertexStyle;
+
+ if (model.isEdge(cell))
+ {
+ defaultStyle = graph.stylesheet.getDefaultEdgeStyle();
+ appliedStyle = edgeStyle;
+ }
+
+ removeStyles(result, defaultStyles, defaultStyle);
+ applyStyle(commonStyle, result, cell, graphStyle, graph2);
+ applyStyle(appliedStyle, result, cell, graphStyle, graph2);
+
+ if (resolve)
+ {
+ result = graph.postProcessCellStyle(cell, result);
+ }
+
+ return result;
+ };
+
+ // Avoid HTML labels to capture events in bubble phase
+ graph2.model.beginUpdate();
+ try
+ {
+ var v1 = graph2.insertVertex(graph2.getDefaultParent(), null, 'Shape', 14, 8, 70, 36, 'strokeWidth=2;');
+ var e1 = graph2.insertEdge(graph2.getDefaultParent(), null, 'Connector', v1, v1,
+ 'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;endSize=5;strokeWidth=2;')
+ e1.geometry.points = [new mxPoint(32, 66)];
+ e1.geometry.offset = new mxPoint(0, 8);
+ }
+ finally
+ {
+ graph2.model.endUpdate();
+ }
+ });
+
+ // Entries
+ var entries = document.createElement('div');
+ entries.style.position = 'relative';
+ entries.style.width = '210px';
+ div.appendChild(entries);
+
+ // Cached entries
+ if (this.format.cachedStyleEntries == null)
+ {
+ this.format.cachedStyleEntries = [];
+ }
+
+ function addKeys(style, result)
+ {
+ for (var key in style)
+ {
+ result.push(key);
+ }
+
+ return result;
+ };
+
+ var addEntry = mxUtils.bind(this, function(commonStyle, vertexStyle, edgeStyle, graphStyle, index)
+ {
+ var panel = this.format.cachedStyleEntries[index];
+
+ if (panel == null)
+ {
+ panel = document.createElement('div');
+ panel.style.display = 'inline-block';
+ panel.style.position = 'relative';
+ panel.style.width = '96px';
+ panel.style.height = '86px';
+ panel.style.cursor = 'pointer';
+ panel.style.border = '1px solid gray';
+ panel.style.borderRadius = '8px';
+ panel.style.margin = '1px 2px';
+ panel.style.overflow = 'hidden';
+
+ if (!ignoreGraphStyle && graphStyle != null && graphStyle.background != null)
+ {
+ panel.style.backgroundColor = graphStyle.background;
+ }
+
+ createPreview(commonStyle, vertexStyle, edgeStyle, graphStyle, panel);
+
+ mxEvent.addGestureListeners(panel, mxUtils.bind(this, function(evt)
+ {
+ panel.style.opacity = 0.5;
+ }), null, mxUtils.bind(this, function(evt)
+ {
+ panel.style.opacity = 1;
+ graph.currentVertexStyle = mxUtils.clone(graph.defaultVertexStyle);
+ graph.currentEdgeStyle = mxUtils.clone(graph.defaultEdgeStyle);
+
+ applyStyle(commonStyle, graph.currentVertexStyle);
+ applyStyle(commonStyle, graph.currentEdgeStyle);
+ applyStyle(vertexStyle, graph.currentVertexStyle);
+ applyStyle(edgeStyle, graph.currentEdgeStyle);
+
+ model.beginUpdate();
+ try
+ {
+ updateCells(addKeys(commonStyle, defaultStyles.slice()), graphStyle);
+
+ if (!ignoreGraphStyle)
+ {
+ var change = new ChangePageSetup(ui, (graphStyle != null) ? graphStyle.background : null);
+ change.ignoreImage = true;
+ model.execute(change);
+
+ model.execute(new ChangeGridColor(ui,
+ (graphStyle != null && graphStyle.gridColor != null) ?
+ graphStyle.gridColor : gridColor));
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }));
+
+ mxEvent.addListener(panel, 'mouseenter', mxUtils.bind(this, function(evt)
+ {
+ var prev = graph.getCellStyle;
+ var prevBg = graph.background;
+ var prevGrid = graph.view.gridColor;
+
+ if (!ignoreGraphStyle)
+ {
+ graph.background = (graphStyle != null) ? graphStyle.background : null;
+ graph.view.gridColor = (graphStyle != null && graphStyle.gridColor != null) ?
+ graphStyle.gridColor : gridColor;
+ }
+
+ graph.getCellStyle = function(cell, resolve)
+ {
+ resolve = (resolve != null) ? resolve : true;
+ var result = mxUtils.clone(prev.apply(this, arguments));
+
+ var defaultStyle = graph.stylesheet.getDefaultVertexStyle();
+ var appliedStyle = vertexStyle;
+
+ if (model.isEdge(cell))
+ {
+ defaultStyle = graph.stylesheet.getDefaultEdgeStyle();
+ appliedStyle = edgeStyle;
+ }
+
+ removeStyles(result, defaultStyles, defaultStyle);
+ applyStyle(commonStyle, result, cell, graphStyle);
+ applyStyle(appliedStyle, result, cell, graphStyle);
+
+ if (resolve)
+ {
+ result = this.postProcessCellStyle(cell, result);
+ }
+
+ return result;
+ };
+
+ graph.refresh();
+ graph.getCellStyle = prev;
+ graph.background = prevBg;
+ graph.view.gridColor = prevGrid;
+ }));
+
+ mxEvent.addListener(panel, 'mouseleave', mxUtils.bind(this, function(evt)
+ {
+ graph.refresh();
+ }));
+
+ // Workaround for broken cache in IE11
+ if (!mxClient.IS_IE && !mxClient.IS_IE11)
+ {
+ this.format.cachedStyleEntries[index] = panel;
+ }
+ }
+
+ entries.appendChild(panel);
+ });
+
+ // Maximum palettes to switch the switcher
+ var maxEntries = 10;
+ var pageCount = Math.ceil(Editor.styles.length / maxEntries);
+ this.format.currentStylePage = (this.format.currentStylePage != null) ? this.format.currentStylePage : 0;
+ var dots = [];
+
+ var addEntries = mxUtils.bind(this, function()
+ {
+ if (dots.length > 0)
+ {
+ dots[this.format.currentStylePage].style.background = '#84d7ff';
+ }
+
+ for (var i = this.format.currentStylePage * maxEntries;
+ i < Math.min((this.format.currentStylePage + 1) * maxEntries,
+ Editor.styles.length); i++)
+ {
+ var s = Editor.styles[i];
+ addEntry(s.commonStyle, s.vertexStyle, s.edgeStyle, s.graph, i);
+ }
+ });
+
+ var selectPage = mxUtils.bind(this, function(index)
+ {
+ if (index >= 0 && index < pageCount)
+ {
+ dots[this.format.currentStylePage].style.background = 'transparent';
+ entries.innerText = '';
+ this.format.currentStylePage = index;
+ addEntries();
+ }
+ });
+
+ if (pageCount > 1)
+ {
+ // Selector
+ var switcher = document.createElement('div');
+ switcher.style.whiteSpace = 'nowrap';
+ switcher.style.position = 'relative';
+ switcher.style.textAlign = 'center';
+ switcher.style.paddingTop = '4px';
+ switcher.style.width = '210px';
+
+ for (var i = 0; i < pageCount; i++)
+ {
+ var dot = document.createElement('div');
+ dot.style.display = 'inline-block';
+ dot.style.width = '6px';
+ dot.style.height = '6px';
+ dot.style.marginLeft = '4px';
+ dot.style.marginRight = '3px';
+ dot.style.borderRadius = '3px';
+ dot.style.cursor = 'pointer';
+ dot.style.background = 'transparent';
+ dot.style.border = '1px solid #b5b6b7';
+
+ (mxUtils.bind(this, function(index, elt)
+ {
+ mxEvent.addListener(dot, 'click', mxUtils.bind(this, function()
+ {
+ selectPage(index);
+ }));
+ }))(i, dot);
+
+ switcher.appendChild(dot);
+ dots.push(dot);
+ }
+
+ div.appendChild(switcher);
+ addEntries();
+
+ if (pageCount < 15)
+ {
+ var left = document.createElement('div');
+ left.className = 'geAdaptiveAsset';
+ left.style.position = 'absolute';
+ left.style.left = '0px';
+ left.style.top = '0px';
+ left.style.bottom = '0px';
+ left.style.width = '24px';
+ left.style.height = '24px';
+ left.style.margin = '0px';
+ left.style.cursor = 'pointer';
+ left.style.opacity = '0.5';
+ left.style.backgroundRepeat = 'no-repeat';
+ left.style.backgroundPosition = 'center center';
+ left.style.backgroundSize = '24px 24px';
+ left.style.backgroundImage = 'url(' + Editor.previousImage + ')';
+
+ var right = left.cloneNode(false);
+ right.style.backgroundImage = 'url(' + Editor.nextImage + ')';
+ right.style.left = '';
+ right.style.right = '2px';
+
+ switcher.appendChild(left);
+ switcher.appendChild(right);
+
+ mxEvent.addListener(left, 'click', mxUtils.bind(this, function()
+ {
+ selectPage(mxUtils.mod(this.format.currentStylePage - 1, pageCount));
+ }));
+
+ mxEvent.addListener(right, 'click', mxUtils.bind(this, function()
+ {
+ selectPage(mxUtils.mod(this.format.currentStylePage + 1, pageCount));
+ }));
+
+ // Hover state
+ function addHoverState(elt)
+ {
+ mxEvent.addListener(elt, 'mouseenter', function()
+ {
+ elt.style.opacity = '1';
+ });
+ mxEvent.addListener(elt, 'mouseleave', function()
+ {
+ elt.style.opacity = '0.5';
+ });
+ };
+
+ addHoverState(left);
+ addHoverState(right);
+ }
+ }
+ else
+ {
+ addEntries();
+ }
+
+ return div;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramStylePanel.prototype.destroy = function()
+{
+ BaseFormatPanel.prototype.destroy.apply(this, arguments);
+
+ if (this.darkModeChangedListener)
+ {
+ this.editorUi.removeListener(this.darkModeChangedListener);
+ this.darkModeChangedListener = null;
+ }
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramFormatPanel = function(format, editorUi, container)
+{
+ BaseFormatPanel.call(this, format, editorUi, container);
+ this.init();
+};
+
+mxUtils.extend(DiagramFormatPanel, BaseFormatPanel);
+
+/**
+ * Switch to disable page view.
+ */
+DiagramFormatPanel.showPageView = true;
+
+/**
+ * Specifies if the background image option should be shown. Default is true.
+ */
+DiagramFormatPanel.prototype.showBackgroundImageOption = true;
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramFormatPanel.prototype.init = function()
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+
+ this.container.appendChild(this.addView(this.createPanel()));
+
+ if (graph.isEnabled())
+ {
+ this.container.appendChild(this.addOptions(this.createPanel()));
+ this.container.appendChild(this.addPaperSize(this.createPanel()));
+ this.container.appendChild(this.addStyleOps(this.createPanel()));
+ }
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramFormatPanel.prototype.addView = function(div)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+
+ div.appendChild(this.createTitle(mxResources.get('view')));
+
+ // Grid
+ this.addGridOption(div);
+
+ // Page View
+ if (DiagramFormatPanel.showPageView)
+ {
+ div.appendChild(this.createOption(mxResources.get('pageView'), function()
+ {
+ return graph.pageVisible;
+ }, function(checked)
+ {
+ ui.actions.get('pageView').funct();
+ },
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ apply(graph.pageVisible);
+ };
+
+ ui.addListener('pageViewChanged', this.listener);
+ },
+ destroy: function()
+ {
+ ui.removeListener(this.listener);
+ }
+ }));
+ }
+
+ if (graph.isEnabled())
+ {
+ if (this.showBackgroundImageOption)
+ {
+ var bg = this.createOption(mxResources.get('background'), function()
+ {
+ return graph.backgroundImage != null;
+ }, function(checked)
+ {
+ if (!checked)
+ {
+ var change = new ChangePageSetup(ui, null, null);
+ change.ignoreColor = true;
+
+ graph.model.execute(change);
+ }
+ },
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ apply(graph.backgroundImage != null);
+ };
+
+ ui.addListener('backgroundImageChanged', this.listener);
+ },
+ destroy: function()
+ {
+ ui.removeListener(this.listener);
+ }
+ });
+
+ var input = bg.getElementsByTagName('input')[0];
+
+ if (input != null)
+ {
+ input.style.visibility = graph.backgroundImage != null ? 'visible' : 'hidden';
+ }
+
+ var label = bg.getElementsByTagName('div')[0];
+
+ if (label != null)
+ {
+ label.style.display = 'inline-block';
+ label.style.textOverflow = 'ellipsis';
+ label.style.overflow = 'hidden';
+ label.style.maxWidth = '80px';
+ }
+
+ if (mxClient.IS_FF)
+ {
+ label.style.marginTop = '1px';
+ }
+
+ var btn = mxUtils.button(mxResources.get('change') + '...', function(evt)
+ {
+ ui.showBackgroundImageDialog(null,
+ ui.editor.graph.backgroundImage,
+ ui.editor.graph.background);
+ mxEvent.consume(evt);
+ })
+
+ btn.style.position = 'absolute';
+ btn.style.height = '22px';
+ btn.style.left = '47%';
+ btn.style.marginLeft = '1px';
+ btn.style.width = '110px';
+ btn.style.maxWidth = '110px';
+
+ bg.appendChild(btn);
+ div.appendChild(bg);
+ }
+
+ var bgColor = this.createColorOption(mxResources.get('backgroundColor'), function()
+ {
+ return graph.background;
+ }, function(color)
+ {
+ var change = new ChangePageSetup(ui, color);
+ change.ignoreImage = true;
+
+ graph.model.execute(change);
+ }, '#ffffff');
+
+ bgColor.style.padding = '5px 0 1px 0';
+ div.appendChild(bgColor);
+
+ var option = this.createOption(mxResources.get('shadow'), function()
+ {
+ return graph.shadowVisible;
+ }, function(checked)
+ {
+ var change = new ChangePageSetup(ui);
+ change.ignoreColor = true;
+ change.ignoreImage = true;
+ change.shadowVisible = checked;
+
+ graph.model.execute(change);
+ },
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ apply(graph.shadowVisible);
+ };
+
+ ui.addListener('shadowVisibleChanged', this.listener);
+ },
+ destroy: function()
+ {
+ ui.removeListener(this.listener);
+ }
+ });
+
+ if (!Editor.enableShadowOption)
+ {
+ option.getElementsByTagName('input')[0].setAttribute('disabled', 'disabled');
+ mxUtils.setOpacity(option, 60);
+ }
+
+ option.style.display = 'inline-flex';
+ option.style.width = '100px';
+ option.style.maxWidth = '100px';
+ option.style.marginRight = '4px';
+ div.appendChild(option);
+
+ var sketchOption = this.createOption(mxResources.get('sketch'), function()
+ {
+ return Editor.sketchMode;
+ }, function(checked)
+ {
+ ui.setSketchMode(checked);
+ },
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ apply(Editor.sketchMode);
+ };
+
+ ui.addListener('sketchModeChanged', this.listener);
+ },
+ destroy: function()
+ {
+ ui.removeListener(this.listener);
+ }
+ });
+
+ sketchOption.style.display = 'inline-flex';
+ sketchOption.style.width = '104px';
+ sketchOption.style.maxWidth = '104px';
+ div.appendChild(sketchOption);
+ }
+
+ return div;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramFormatPanel.prototype.addOptions = function(div)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+
+ div.appendChild(this.createTitle(mxResources.get('options')));
+
+ if (graph.isEnabled())
+ {
+ // Connection arrows
+ div.appendChild(this.createOption(mxResources.get('connectionArrows'), function()
+ {
+ return graph.connectionArrowsEnabled;
+ }, function(checked)
+ {
+ ui.actions.get('connectionArrows').funct();
+ },
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ apply(graph.connectionArrowsEnabled);
+ };
+
+ ui.addListener('connectionArrowsChanged', this.listener);
+ },
+ destroy: function()
+ {
+ ui.removeListener(this.listener);
+ }
+ }));
+
+ // Connection points
+ div.appendChild(this.createOption(mxResources.get('connectionPoints'), function()
+ {
+ return graph.connectionHandler.isEnabled();
+ }, function(checked)
+ {
+ ui.actions.get('connectionPoints').funct();
+ },
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ apply(graph.connectionHandler.isEnabled());
+ };
+
+ ui.addListener('connectionPointsChanged', this.listener);
+ },
+ destroy: function()
+ {
+ ui.removeListener(this.listener);
+ }
+ }));
+
+ // Guides
+ div.appendChild(this.createOption(mxResources.get('guides'), function()
+ {
+ return graph.graphHandler.guidesEnabled;
+ }, function(checked)
+ {
+ ui.actions.get('guides').funct();
+ },
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ apply(graph.graphHandler.guidesEnabled);
+ };
+
+ ui.addListener('guidesEnabledChanged', this.listener);
+ },
+ destroy: function()
+ {
+ ui.removeListener(this.listener);
+ }
+ }));
+ }
+
+ return div;
+};
+
+/**
+ *
+ */
+DiagramFormatPanel.prototype.addGridOption = function(container)
+{
+ var fPanel = this;
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+
+ var input = document.createElement('input');
+ input.style.position = 'absolute';
+ input.style.textAlign = 'right';
+ input.style.width = '48px';
+ input.style.marginTop = '-2px';
+ input.style.height = '21px';
+ input.style.borderWidth = '1px';
+ input.style.borderStyle = 'solid';
+ input.style.boxSizing = 'border-box';
+ input.value = this.inUnit(graph.getGridSize()) + ' ' + this.getUnit();
+
+ var stepper = this.createStepper(input, update, this.getUnitStep(), null, null, null, this.isFloatUnit());
+ input.style.display = (graph.isGridEnabled()) ? '' : 'none';
+ stepper.style.display = input.style.display;
+
+ mxEvent.addListener(input, 'keydown', function(e)
+ {
+ if (e.keyCode == 13)
+ {
+ graph.container.focus();
+ mxEvent.consume(e);
+ }
+ else if (e.keyCode == 27)
+ {
+ input.value = graph.getGridSize();
+ graph.container.focus();
+ mxEvent.consume(e);
+ }
+ });
+
+ function update(evt)
+ {
+ var value = fPanel.isFloatUnit()? parseFloat(input.value) : parseInt(input.value);
+ value = fPanel.fromUnit(Math.max(fPanel.inUnit(1), (isNaN(value)) ? fPanel.inUnit(10) : value));
+
+ if (value != graph.getGridSize())
+ {
+ mxGraph.prototype.gridSize = value;
+ graph.setGridSize(value)
+ }
+
+ input.value = fPanel.inUnit(value) + ' ' + fPanel.getUnit();
+ mxEvent.consume(evt);
+ };
+
+ mxEvent.addListener(input, 'blur', update);
+ mxEvent.addListener(input, 'change', update);
+
+ input.style.right = '78px';
+ stepper.style.marginTop = (mxClient.IS_MAC && mxClient.IS_GC) ?
+ '-16px' : ((mxClient.IS_WIN) ? '-18px' : '-17px');
+ stepper.style.right = '66px';
+
+ var panel = this.createColorOption(mxResources.get('grid'), function()
+ {
+ var color = graph.view.gridColor;
+
+ return (graph.isGridEnabled()) ? color : null;
+ }, function(color)
+ {
+ var enabled = graph.isGridEnabled();
+
+ if (color == mxConstants.NONE)
+ {
+ graph.setGridEnabled(false);
+ }
+ else
+ {
+ graph.setGridEnabled(true);
+ ui.setGridColor(color);
+ }
+
+ input.style.display = (graph.isGridEnabled()) ? '' : 'none';
+ stepper.style.display = input.style.display;
+
+ if (enabled != graph.isGridEnabled())
+ {
+ graph.defaultGridEnabled = graph.isGridEnabled();
+ ui.fireEvent(new mxEventObject('gridEnabledChanged'));
+ }
+ }, Editor.isDarkMode() ? graph.view.defaultDarkGridColor : graph.view.defaultGridColor,
+ {
+ install: function(apply)
+ {
+ this.listener = function()
+ {
+ apply((graph.isGridEnabled()) ? graph.view.gridColor : null);
+ };
+
+ ui.addListener('gridColorChanged', this.listener);
+ ui.addListener('gridEnabledChanged', this.listener);
+ },
+ destroy: function()
+ {
+ ui.removeListener(this.listener);
+ }
+ });
+
+ panel.style.padding = '6px 0 0 0';
+ panel.appendChild(input);
+ panel.appendChild(stepper);
+ container.appendChild(panel);
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramFormatPanel.prototype.addDocumentProperties = function(div)
+{
+ // Hook for subclassers
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+
+ div.appendChild(this.createTitle(mxResources.get('options')));
+
+ return div;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramFormatPanel.prototype.addPaperSize = function(div)
+{
+ var ui = this.editorUi;
+ var editor = ui.editor;
+ var graph = editor.graph;
+
+ div.appendChild(this.createTitle(mxResources.get('paperSize')));
+
+ var accessor = PageSetupDialog.addPageFormatPanel(div, 'formatpanel', graph.pageFormat, function(pageFormat)
+ {
+ if (graph.pageFormat == null || graph.pageFormat.width != pageFormat.width ||
+ graph.pageFormat.height != pageFormat.height)
+ {
+ var change = new ChangePageSetup(ui, null, null, pageFormat);
+ change.ignoreColor = true;
+ change.ignoreImage = true;
+
+ graph.model.execute(change);
+ }
+ });
+
+ this.addKeyHandler(accessor.widthInput, function()
+ {
+ accessor.set(graph.pageFormat);
+ });
+
+ this.addKeyHandler(accessor.heightInput, function()
+ {
+ accessor.set(graph.pageFormat);
+ });
+
+ var listener = function()
+ {
+ accessor.set(graph.pageFormat);
+ };
+
+ ui.addListener('pageFormatChanged', listener);
+ this.listeners.push({destroy: function() { ui.removeListener(listener); }});
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+ this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
+
+ return div;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramFormatPanel.prototype.addStyleOps = function(div)
+{
+ this.addActions(div, ['editData']);
+ this.addActions(div, ['clearDefaultStyle']);
+
+ return div;
+};
+
+/**
+ * Adds the label menu items to the given menu and parent.
+ */
+DiagramFormatPanel.prototype.destroy = function()
+{
+ BaseFormatPanel.prototype.destroy.apply(this, arguments);
+
+ if (this.gridEnabledListener)
+ {
+ this.editorUi.removeListener(this.gridEnabledListener);
+ this.gridEnabledListener = null;
+ }
+};
+
+
+/**
+ * Configures global color schemes.
+ */
+StyleFormatPanel.prototype.defaultColorSchemes = [[{fill: '', stroke: ''}, {fill: '#f5f5f5', stroke: '#666666', font: '#333333'},
+{fill: '#dae8fc', stroke: '#6c8ebf'}, {fill: '#d5e8d4', stroke: '#82b366'},
+{fill: '#ffe6cc', stroke: '#d79b00'}, {fill: '#fff2cc', stroke: '#d6b656'},
+{fill: '#f8cecc', stroke: '#b85450'}, {fill: '#e1d5e7', stroke: '#9673a6'}],
+[{fill: '', stroke: ''}, {fill: '#60a917', stroke: '#2D7600', font: '#ffffff'},
+{fill: '#008a00', stroke: '#005700', font: '#ffffff'}, {fill: '#1ba1e2', stroke: '#006EAF', font: '#ffffff'},
+{fill: '#0050ef', stroke: '#001DBC', font: '#ffffff'}, {fill: '#6a00ff', stroke: '#3700CC', font: '#ffffff'},
+//{fill: '#aa00ff', stroke: '#7700CC', font: '#ffffff'},
+{fill: '#d80073', stroke: '#A50040', font: '#ffffff'}, {fill: '#a20025', stroke: '#6F0000', font: '#ffffff'}],
+[{fill: '#e51400', stroke: '#B20000', font: '#ffffff'}, {fill: '#fa6800', stroke: '#C73500', font: '#000000'},
+{fill: '#f0a30a', stroke: '#BD7000', font: '#000000'}, {fill: '#e3c800', stroke: '#B09500', font: '#000000'},
+{fill: '#6d8764', stroke: '#3A5431', font: '#ffffff'}, {fill: '#647687', stroke: '#314354', font: '#ffffff'},
+{fill: '#76608a', stroke: '#432D57', font: '#ffffff'}, {fill: '#a0522d', stroke: '#6D1F00', font: '#ffffff'}],
+[{fill: '', stroke: ''}, {fill: mxConstants.NONE, stroke: ''},
+{fill: '#fad7ac', stroke: '#b46504'}, {fill: '#fad9d5', stroke: '#ae4132'},
+{fill: '#b0e3e6', stroke: '#0e8088'}, {fill: '#b1ddf0', stroke: '#10739e'},
+{fill: '#d0cee2', stroke: '#56517e'}, {fill: '#bac8d3', stroke: '#23445d'}],
+[{fill: '', stroke: ''},
+{fill: '#f5f5f5', stroke: '#666666', gradient: '#b3b3b3'},
+{fill: '#dae8fc', stroke: '#6c8ebf', gradient: '#7ea6e0'},
+{fill: '#d5e8d4', stroke: '#82b366', gradient: '#97d077'},
+{fill: '#ffcd28', stroke: '#d79b00', gradient: '#ffa500'},
+{fill: '#fff2cc', stroke: '#d6b656', gradient: '#ffd966'},
+{fill: '#f8cecc', stroke: '#b85450', gradient: '#ea6b66'},
+{fill: '#e6d0de', stroke: '#996185', gradient: '#d5739d'}],
+[{fill: '', stroke: ''}, {fill: '#eeeeee', stroke: '#36393d'},
+{fill: '#f9f7ed', stroke: '#36393d'}, {fill: '#ffcc99', stroke: '#36393d'},
+{fill: '#cce5ff', stroke: '#36393d'}, {fill: '#ffff88', stroke: '#36393d'},
+{fill: '#cdeb8b', stroke: '#36393d'}, {fill: '#ffcccc', stroke: '#36393d'}]];
+
+/**
+* Configures custom color schemes.
+*/
+StyleFormatPanel.prototype.customColorSchemes = null;
+
+StyleFormatPanel.prototype.findCommonProperties = function(cell, properties, addAll)
+{
+if (properties == null) return;
+
+var handleCustomProp = function(custProperties)
+{
+ if (custProperties != null)
+ {
+ if (addAll)
+ {
+ for (var i = 0; i < custProperties.length; i++)
+ {
+ properties[custProperties[i].name] = custProperties[i];
+ }
+ }
+ else
+ {
+ for (var key in properties)
+ {
+ var found = false;
+
+ for (var i = 0; i < custProperties.length; i++)
+ {
+ if (custProperties[i].name == key && custProperties[i].type == properties[key].type)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ delete properties[key];
+ }
+ }
+ }
+ }
+};
+
+var view = this.editorUi.editor.graph.view;
+var state = view.getState(cell);
+
+if (state != null && state.shape != null)
+{
+ //Add common properties to all shapes
+ if (!state.shape.commonCustomPropAdded)
+ {
+ state.shape.commonCustomPropAdded = true;
+ state.shape.customProperties = state.shape.customProperties || [];
+
+ if (state.cell.vertex)
+ {
+ Array.prototype.push.apply(state.shape.customProperties, Editor.commonVertexProperties);
+ }
+ else
+ {
+ Array.prototype.push.apply(state.shape.customProperties, Editor.commonEdgeProperties);
+ }
+ }
+
+ handleCustomProp(state.shape.customProperties);
+}
+
+//This currently is not needed but let's keep it in case we needed in the future
+var userCustomProp = cell.getAttribute('customProperties');
+
+if (userCustomProp != null)
+{
+ try
+ {
+ handleCustomProp(JSON.parse(userCustomProp));
+ }
+ catch(e){}
+}
+};
+
+/**
+* Adds predefiend styles.
+*/
+var styleFormatPanelInit = StyleFormatPanel.prototype.init;
+
+StyleFormatPanel.prototype.init = function()
+{
+var sstate = this.editorUi.getSelectionState();
+
+if (this.defaultColorSchemes != null && this.defaultColorSchemes.length > 0 &&
+ sstate.style.shape != 'image' && !sstate.containsLabel &&
+ sstate.cells.length > 0)
+{
+ this.container.appendChild(this.addStyles(this.createPanel()));
+}
+
+styleFormatPanelInit.apply(this, arguments);
+
+if (Editor.enableCustomProperties)
+{
+ var properties = {};
+ var vertices = sstate.vertices;
+ var edges = sstate.edges;
+
+ for (var i = 0; i < vertices.length; i++)
+ {
+ this.findCommonProperties(vertices[i], properties, i == 0);
+ }
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ this.findCommonProperties(edges[i], properties, vertices.length == 0 && i == 0);
+ }
+
+ if (Object.getOwnPropertyNames != null && Object.getOwnPropertyNames(properties).length > 0)
+ {
+ this.container.appendChild(this.addProperties(this.createPanel(), properties, sstate));
+ }
+}
+};
+
+/**
+* Overridden to add copy and paste style.
+*/
+var styleFormatPanelAddStyleOps = StyleFormatPanel.prototype.addStyleOps;
+
+StyleFormatPanel.prototype.addStyleOps = function(div)
+{
+var ss = this.editorUi.getSelectionState();
+
+if (ss.cells.length == 1)
+{
+ this.addActions(div, ['copyStyle', 'pasteStyle']);
+}
+else if (ss.cells.length >= 1)
+{
+ this.addActions(div, ['pasteStyle', 'pasteData']);
+}
+
+return styleFormatPanelAddStyleOps.apply(this, arguments);
+};
+
+/**
+* Initial collapsed state of the properties panel.
+*/
+EditorUi.prototype.propertiesCollapsed = true;
+
+/**
+* Create Properties Panel
+*/
+StyleFormatPanel.prototype.addProperties = function(div, properties, state)
+{
+var that = this;
+var graph = this.editorUi.editor.graph;
+var secondLevel = [];
+
+function insertAfter(newElem, curElem)
+{
+ curElem.parentNode.insertBefore(newElem, curElem.nextSibling);
+};
+
+function applyStyleVal(pName, newVal, prop, delIndex)
+{
+ graph.getModel().beginUpdate();
+ try
+ {
+ var changedProps = [];
+ var changedVals = [];
+
+ if (prop.index != null)
+ {
+ var allVals = [];
+ var curVal = prop.parentRow.nextSibling;
+
+ while(curVal && curVal.getAttribute('data-pName') == pName)
+ {
+ allVals.push(curVal.getAttribute('data-pValue'));
+ curVal = curVal.nextSibling;
+ }
+
+ if (prop.index < allVals.length)
+ {
+ if (delIndex != null)
+ {
+ allVals.splice(delIndex, 1);
+ }
+ else
+ {
+ allVals[prop.index] = newVal;
+ }
+ }
+ else
+ {
+ allVals.push(newVal);
+ }
+
+ if (prop.size != null && allVals.length > prop.size) //trim the array to the specifies size
+ {
+ allVals = allVals.slice(0, prop.size);
+ }
+
+ newVal = allVals.join(',');
+
+ if (prop.countProperty != null)
+ {
+ graph.setCellStyles(prop.countProperty, allVals.length, graph.getSelectionCells());
+
+ changedProps.push(prop.countProperty);
+ changedVals.push(allVals.length);
+ }
+ }
+
+ graph.setCellStyles(pName, newVal, graph.getSelectionCells());
+ changedProps.push(pName);
+ changedVals.push(newVal);
+
+ if (prop.dependentProps != null)
+ {
+ for (var i = 0; i < prop.dependentProps.length; i++)
+ {
+ var defVal = prop.dependentPropsDefVal[i];
+ var vals = prop.dependentPropsVals[i];
+
+ if (vals.length > newVal)
+ {
+ vals = vals.slice(0, newVal);
+ }
+ else
+ {
+ for (var j = vals.length; j < newVal; j++)
+ {
+ vals.push(defVal);
+ }
+ }
+
+ vals = vals.join(',');
+ graph.setCellStyles(prop.dependentProps[i], vals, graph.getSelectionCells());
+ changedProps.push(prop.dependentProps[i]);
+ changedVals.push(vals);
+ }
+ }
+
+ if (typeof(prop.onChange) == 'function')
+ {
+ prop.onChange(graph, newVal);
+ }
+
+ that.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', changedProps,
+ 'values', changedVals, 'cells', graph.getSelectionCells()));
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+}
+
+function setElementPos(td, elem, adjustHeight)
+{
+ var divPos = mxUtils.getOffset(div, true);
+ var pos = mxUtils.getOffset(td, true);
+ elem.style.position = 'absolute';
+ elem.style.left = (pos.x - divPos.x) + 'px';
+ elem.style.top = (pos.y - divPos.y) + 'px';
+ elem.style.width = td.offsetWidth + 'px';
+ elem.style.height = (td.offsetHeight - (adjustHeight? 4 : 0)) + 'px';
+ elem.style.zIndex = 5;
+};
+
+function createColorBtn(pName, pValue, prop)
+{
+ var clrDiv = document.createElement('div');
+ clrDiv.style.width = '32px';
+ clrDiv.style.height = '4px';
+ clrDiv.style.margin = '2px';
+ clrDiv.style.border = '1px solid black';
+ clrDiv.style.background = !pValue || pValue == 'none'? 'url(\'' + Dialog.prototype.noColorImage + '\')' : pValue;
+
+ btn = mxUtils.button('', mxUtils.bind(that, function(evt)
+ {
+ this.editorUi.pickColor(pValue, function(color)
+ {
+ clrDiv.style.background = color == 'none'? 'url(\'' + Dialog.prototype.noColorImage + '\')' : color;
+ applyStyleVal(pName, color, prop);
+ });
+ mxEvent.consume(evt);
+ }));
+
+ btn.style.height = '12px';
+ btn.style.width = '40px';
+ btn.className = 'geColorBtn';
+
+ btn.appendChild(clrDiv);
+ return btn;
+};
+
+function createDynArrList(pName, pValue, subType, defVal, countProperty, myRow, flipBkg)
+{
+ if (pValue != null)
+ {
+ var vals = pValue.split(',');
+ secondLevel.push({name: pName, values: vals, type: subType, defVal: defVal, countProperty: countProperty, parentRow: myRow, isDeletable: true, flipBkg: flipBkg});
+ }
+
+ btn = mxUtils.button('+', mxUtils.bind(that, function(evt)
+ {
+ var beforeElem = myRow;
+ var index = 0;
+
+ while (beforeElem.nextSibling != null)
+ {
+ var cur = beforeElem.nextSibling;
+ var elemPName = cur.getAttribute('data-pName');
+
+ if (elemPName == pName)
+ {
+ beforeElem = beforeElem.nextSibling;
+ index++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ var newProp = {type: subType, parentRow: myRow, index: index, isDeletable: true, defVal: defVal, countProperty: countProperty};
+ var arrItem = createPropertyRow(pName, '', newProp, index % 2 == 0, flipBkg);
+ applyStyleVal(pName, defVal, newProp);
+ insertAfter(arrItem, beforeElem);
+
+ mxEvent.consume(evt);
+ }));
+
+ btn.style.height = '16px';
+ btn.style.width = '25px';
+ btn.className = 'geColorBtn';
+
+ return btn;
+};
+
+function createStaticArrList(pName, pValue, subType, defVal, size, myRow, flipBkg)
+{
+ if (size > 0)
+ {
+ var vals = new Array(size);
+
+ var curVals = pValue != null? pValue.split(',') : [];
+
+ for (var i = 0; i < size; i++)
+ {
+ vals[i] = curVals[i] != null? curVals[i] : (defVal != null? defVal : '');
+ }
+
+ secondLevel.push({name: pName, values: vals, type: subType, defVal: defVal, parentRow: myRow, flipBkg: flipBkg, size: size});
+ }
+
+ return document.createElement('div'); //empty cell
+};
+
+function createCheckbox(pName, pValue, prop)
+{
+ var input = document.createElement('input');
+ input.type = 'checkbox';
+ input.checked = pValue == '1';
+
+ mxEvent.addListener(input, 'change', function()
+ {
+ applyStyleVal(pName, input.checked? '1' : '0', prop);
+ });
+ return input;
+};
+
+function createPropertyRow(pName, pValue, prop, isOdd, flipBkg)
+{
+ var pDiplayName = prop.dispName;
+ var pType = prop.type;
+ var row = document.createElement('tr');
+ row.className = 'gePropRow' + (flipBkg? 'Dark' : '') + (isOdd? 'Alt' : '') + ' gePropNonHeaderRow';
+ row.setAttribute('data-pName', pName);
+ row.setAttribute('data-pValue', pValue);
+ var rightAlig = false;
+
+ if (prop.index != null)
+ {
+ row.setAttribute('data-index', prop.index);
+ pDiplayName = (pDiplayName != null? pDiplayName : '') + '[' + prop.index + ']';
+ rightAlig = true;
+ }
+
+ var td = document.createElement('td');
+ td.className = 'gePropRowCell';
+ var label = mxResources.get(pDiplayName, null, pDiplayName);
+ mxUtils.write(td, label);
+ td.setAttribute('title', label);
+
+ if (rightAlig)
+ {
+ td.style.textAlign = 'right';
+ }
+
+ row.appendChild(td);
+ td = document.createElement('td');
+ td.className = 'gePropRowCell';
+
+ if (pType == 'color')
+ {
+ td.appendChild(createColorBtn(pName, pValue, prop));
+ }
+ else if (pType == 'bool' || pType == 'boolean')
+ {
+ td.appendChild(createCheckbox(pName, pValue, prop));
+ }
+ else if (pType == 'enum')
+ {
+ var pEnumList = prop.enumList;
+
+ for (var i = 0; i < pEnumList.length; i++)
+ {
+ var op = pEnumList[i];
+
+ if (op.val == pValue)
+ {
+ mxUtils.write(td, mxResources.get(op.dispName, null, op.dispName));
+ break;
+ }
+ }
+
+ mxEvent.addListener(td, 'click', mxUtils.bind(that, function()
+ {
+ var select = document.createElement('select');
+ setElementPos(td, select);
+
+ for (var i = 0; i < pEnumList.length; i++)
+ {
+ var op = pEnumList[i];
+ var opElem = document.createElement('option');
+ opElem.value = mxUtils.htmlEntities(op.val);
+ mxUtils.write(opElem, mxResources.get(op.dispName, null, op.dispName));
+ select.appendChild(opElem);
+ }
+
+ select.value = pValue;
+
+ div.appendChild(select);
+
+ mxEvent.addListener(select, 'change', function()
+ {
+ var newVal = mxUtils.htmlEntities(select.value);
+ applyStyleVal(pName, newVal, prop);
+ //set value triggers a redraw of the panel which removes the select and updates the row
+ });
+
+ select.focus();
+
+ //FF calls blur on focus! so set the event after focusing (not with selects but to be safe)
+ mxEvent.addListener(select, 'blur', function()
+ {
+ div.removeChild(select);
+ });
+ }));
+ }
+ else if (pType == 'dynamicArr')
+ {
+ td.appendChild(createDynArrList(pName, pValue, prop.subType, prop.subDefVal, prop.countProperty, row, flipBkg));
+ }
+ else if (pType == 'staticArr')
+ {
+ td.appendChild(createStaticArrList(pName, pValue, prop.subType, prop.subDefVal, prop.size, row, flipBkg));
+ }
+ else if (pType == 'readOnly')
+ {
+ var inp = document.createElement('input');
+ inp.setAttribute('readonly', '');
+ inp.value = pValue;
+ inp.style.width = '96px';
+ inp.style.borderWidth = '0px';
+ td.appendChild(inp);
+ }
+ else
+ {
+ td.innerHTML = mxUtils.htmlEntities(decodeURIComponent(pValue));
+
+ mxEvent.addListener(td, 'click', mxUtils.bind(that, function()
+ {
+ var input = document.createElement('input');
+ setElementPos(td, input, true);
+ input.value = decodeURIComponent(pValue);
+ input.className = 'gePropEditor';
+
+ if ((pType == 'int' || pType == 'float') && !prop.allowAuto)
+ {
+ input.type = 'number';
+ input.step = pType == 'int'? '1' : 'any';
+
+ if (prop.min != null)
+ {
+ input.min = parseFloat(prop.min);
+ }
+
+ if (prop.max != null)
+ {
+ input.max = parseFloat(prop.max);
+ }
+ }
+
+ div.appendChild(input);
+
+ function setInputVal()
+ {
+ var inputVal = input.value;
+ inputVal = inputVal.length == 0 && pType != 'string'? 0 : inputVal;
+
+ if (prop.allowAuto)
+ {
+ if (inputVal.trim != null && inputVal.trim().toLowerCase() == 'auto')
+ {
+ inputVal = 'auto';
+ pType = 'string';
+ }
+ else
+ {
+ inputVal = parseFloat(inputVal);
+ inputVal = isNaN(inputVal)? 0 : inputVal;
+ }
+ }
+
+ if (prop.min != null && inputVal < prop.min)
+ {
+ inputVal = prop.min;
+ }
+ else if (prop.max != null && inputVal > prop.max)
+ {
+ inputVal = prop.max;
+ }
+
+ var newVal = encodeURIComponent((pType == 'int'? parseInt(inputVal) : inputVal) + '');
+
+ applyStyleVal(pName, newVal, prop);
+ }
+
+ mxEvent.addListener(input, 'keypress', function(e)
+ {
+ if (e.keyCode == 13)
+ {
+ setInputVal();
+ //set value triggers a redraw of the panel which removes the input
+ }
+ });
+
+ input.focus();
+
+ //FF calls blur on focus! so set the event after focusing
+ mxEvent.addListener(input, 'blur', function()
+ {
+ setInputVal();
+ });
+ }));
+ }
+
+ if (prop.isDeletable)
+ {
+ var delBtn = mxUtils.button('-', mxUtils.bind(that, function(evt)
+ {
+ //delete the node by refreshing the properties
+ applyStyleVal(pName, '', prop, prop.index);
+
+ mxEvent.consume(evt);
+ }));
+
+ delBtn.style.height = '16px';
+ delBtn.style.width = '25px';
+ delBtn.style.float = 'right';
+ delBtn.className = 'geColorBtn';
+ td.appendChild(delBtn);
+ }
+
+ row.appendChild(td);
+ return row;
+};
+
+div.style.position = 'relative';
+div.style.padding = '0';
+var grid = document.createElement('table');
+grid.className = 'geProperties';
+grid.style.whiteSpace = 'nowrap';
+grid.style.width = '100%';
+//create header row
+var hrow = document.createElement('tr');
+hrow.className = 'gePropHeader';
+var th = document.createElement('th');
+th.className = 'gePropHeaderCell';
+var collapseImg = document.createElement('img');
+collapseImg.src = Sidebar.prototype.expandedImage;
+collapseImg.style.verticalAlign = 'middle';
+th.appendChild(collapseImg);
+mxUtils.write(th, mxResources.get('property'));
+hrow.style.cursor = 'pointer';
+
+var onFold = function()
+{
+ var rows = grid.querySelectorAll('.gePropNonHeaderRow');
+ var display;
+
+ if (!that.editorUi.propertiesCollapsed)
+ {
+ collapseImg.src = Sidebar.prototype.expandedImage;
+ display = '';
+ }
+ else
+ {
+ collapseImg.src = Sidebar.prototype.collapsedImage;
+ display = 'none';
+
+ for (var e = div.childNodes.length - 1; e >= 0 ; e--)
+ {
+ //Blur can be executed concurrently with this method and the element is removed before removing it here
+ try
+ {
+ var child = div.childNodes[e];
+ var nodeName = child.nodeName.toUpperCase();
+
+ if (nodeName == 'INPUT' || nodeName == 'SELECT')
+ {
+ div.removeChild(child);
+ }
+ }
+ catch(ex){}
+ }
+ }
+
+ for (var r = 0; r < rows.length; r++)
+ {
+ rows[r].style.display = display;
+ }
+};
+
+mxEvent.addListener(hrow, 'click', function()
+{
+ that.editorUi.propertiesCollapsed = !that.editorUi.propertiesCollapsed;
+ onFold();
+});
+hrow.appendChild(th);
+th = document.createElement('th');
+th.className = 'gePropHeaderCell';
+th.innerHTML = mxResources.get('value');
+hrow.appendChild(th);
+grid.appendChild(hrow);
+
+var isOdd = false;
+var flipBkg = false;
+
+var cellId = null;
+
+if (state.vertices.length == 1 && state.edges.length == 0)
+{
+ cellId = state.vertices[0].id;
+}
+else if (state.vertices.length == 0 && state.edges.length == 1)
+{
+ cellId = state.edges[0].id;
+}
+
+//Add it to top (always)
+if (cellId != null)
+{
+ grid.appendChild(createPropertyRow('id', mxUtils.htmlEntities(cellId), {dispName: 'ID', type: 'readOnly'}, true, false));
+}
+
+for (var key in properties)
+{
+ var prop = properties[key];
+
+ if (typeof(prop.isVisible) == 'function')
+ {
+ if (!prop.isVisible(state, this)) continue;
+ }
+
+ var pValue = state.style[key] != null? mxUtils.htmlEntities(state.style[key] + '') :
+ ((prop.getDefaultValue != null) ? prop.getDefaultValue(state, this) : prop.defVal); //or undefined if defVal is undefined
+
+ if (prop.type == 'separator')
+ {
+ flipBkg = !flipBkg;
+ continue;
+ }
+ else if (prop.type == 'staticArr') //if dynamic values are needed, a more elegant technique is needed to replace such values
+ {
+ prop.size = parseInt(state.style[prop.sizeProperty] || properties[prop.sizeProperty].defVal) || 0;
+ }
+ else if (prop.dependentProps != null)
+ {
+ var dependentProps = prop.dependentProps;
+ var dependentPropsVals = [];
+ var dependentPropsDefVal = [];
+
+ for (var i = 0; i < dependentProps.length; i++)
+ {
+ var propVal = state.style[dependentProps[i]];
+ dependentPropsDefVal.push(properties[dependentProps[i]].subDefVal);
+ dependentPropsVals.push(propVal != null? propVal.split(',') : []);
+ }
+
+ prop.dependentPropsDefVal = dependentPropsDefVal;
+ prop.dependentPropsVals = dependentPropsVals;
+ }
+
+ grid.appendChild(createPropertyRow(key, pValue, prop, isOdd, flipBkg));
+
+ isOdd = !isOdd;
+}
+
+for (var i = 0; i < secondLevel.length; i++)
+{
+ var prop = secondLevel[i];
+ var insertElem = prop.parentRow;
+
+ for (var j = 0; j < prop.values.length; j++)
+ {
+ //mxUtils.clone failed because of the HTM element, so manual cloning is used
+ var iProp = {type: prop.type, parentRow: prop.parentRow, isDeletable: prop.isDeletable, index: j, defVal: prop.defVal, countProperty: prop.countProperty, size: prop.size};
+ var arrItem = createPropertyRow(prop.name, prop.values[j], iProp, j % 2 == 0, prop.flipBkg);
+ insertAfter(arrItem, insertElem);
+ insertElem = arrItem;
+ }
+}
+
+div.appendChild(grid);
+onFold();
+
+return div;
+};
+
+/**
+* Creates the buttons for the predefined styles.
+*/
+StyleFormatPanel.prototype.addStyles = function(div)
+{
+if (this.defaultColorSchemes != null)
+{
+ var ui = this.editorUi;
+ var graph = ui.editor.graph;
+ var picker = document.createElement('div');
+ picker.style.whiteSpace = 'nowrap';
+ picker.style.paddingLeft = '24px';
+ picker.style.paddingRight = '20px';
+ div.style.paddingLeft = '16px';
+ div.style.paddingBottom = '6px';
+ div.style.position = 'relative';
+ div.appendChild(picker);
+
+ var stylenames = ['plain-gray', 'plain-blue', 'plain-green', 'plain-turquoise',
+ 'plain-orange', 'plain-yellow', 'plain-red', 'plain-pink', 'plain-purple', 'gray',
+ 'blue', 'green', 'turquoise', 'orange', 'yellow', 'red', 'pink', 'purple'];
+
+ // Maximum palettes to switch the switcher
+ var maxEntries = 10;
+
+ // Selector
+ var switcher = document.createElement('div');
+ switcher.style.whiteSpace = 'nowrap';
+ switcher.style.position = 'relative';
+ switcher.style.textAlign = 'center';
+ switcher.style.width = '210px';
+
+ var dots = [];
+
+ for (var i = 0; i < this.defaultColorSchemes.length; i++)
+ {
+ var dot = document.createElement('div');
+ dot.style.display = 'inline-block';
+ dot.style.width = '6px';
+ dot.style.height = '6px';
+ dot.style.marginLeft = '4px';
+ dot.style.marginRight = '3px';
+ dot.style.borderRadius = '3px';
+ dot.style.cursor = 'pointer';
+ dot.style.background = 'transparent';
+ dot.style.border = '1px solid #b5b6b7';
+
+ (mxUtils.bind(this, function(index)
+ {
+ mxEvent.addListener(dot, 'click', mxUtils.bind(this, function()
+ {
+ setScheme(index);
+ }));
+ }))(i);
+
+ dots.push(dot);
+ switcher.appendChild(dot);
+ }
+
+ var setScheme = mxUtils.bind(this, function(index)
+ {
+ if (dots[index] != null)
+ {
+ if (this.format.currentScheme != null && dots[this.format.currentScheme] != null)
+ {
+ dots[this.format.currentScheme].style.background = 'transparent';
+ }
+
+ this.format.currentScheme = index;
+ updateScheme(this.defaultColorSchemes[this.format.currentScheme]);
+ dots[this.format.currentScheme].style.background = '#84d7ff';
+ }
+ });
+
+ var updateScheme = mxUtils.bind(this, function(colorsets)
+ {
+ var addButton = mxUtils.bind(this, function(colorset)
+ {
+ var btn = mxUtils.button('', mxUtils.bind(this, function(evt)
+ {
+ graph.getModel().beginUpdate();
+ try
+ {
+ var cells = ui.getSelectionState().cells;
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var style = graph.getModel().getStyle(cells[i]);
+
+ for (var j = 0; j < stylenames.length; j++)
+ {
+ style = mxUtils.removeStylename(style, stylenames[j]);
+ }
+
+ var defaults = (graph.getModel().isVertex(cells[i])) ? graph.defaultVertexStyle : graph.defaultEdgeStyle;
+
+ if (colorset != null)
+ {
+ if (!mxEvent.isShiftDown(evt))
+ {
+ if (colorset['fill'] == '')
+ {
+ style = mxUtils.setStyle(style, mxConstants.STYLE_FILLCOLOR, null);
+ }
+ else
+ {
+ style = mxUtils.setStyle(style, mxConstants.STYLE_FILLCOLOR, colorset['fill'] ||
+ mxUtils.getValue(defaults, mxConstants.STYLE_FILLCOLOR, null));
+ }
+
+ style = mxUtils.setStyle(style, mxConstants.STYLE_GRADIENTCOLOR, colorset['gradient'] ||
+ mxUtils.getValue(defaults, mxConstants.STYLE_GRADIENTCOLOR, null));
+
+ if (!mxEvent.isControlDown(evt) && (!mxClient.IS_MAC || !mxEvent.isMetaDown(evt)) &&
+ graph.getModel().isVertex(cells[i]))
+ {
+ style = mxUtils.setStyle(style, mxConstants.STYLE_FONTCOLOR, colorset['font'] ||
+ mxUtils.getValue(defaults, mxConstants.STYLE_FONTCOLOR, null));
+ }
+ }
+
+ if (!mxEvent.isAltDown(evt))
+ {
+ if (colorset['stroke'] == '')
+ {
+ style = mxUtils.setStyle(style, mxConstants.STYLE_STROKECOLOR, null);
+ }
+ else
+ {
+ style = mxUtils.setStyle(style, mxConstants.STYLE_STROKECOLOR, colorset['stroke'] ||
+ mxUtils.getValue(defaults, mxConstants.STYLE_STROKECOLOR, null));
+ }
+ }
+ }
+ else
+ {
+ style = mxUtils.setStyle(style, mxConstants.STYLE_FILLCOLOR,
+ mxUtils.getValue(defaults, mxConstants.STYLE_FILLCOLOR, '#ffffff'));
+ style = mxUtils.setStyle(style, mxConstants.STYLE_STROKECOLOR,
+ mxUtils.getValue(defaults, mxConstants.STYLE_STROKECOLOR, '#000000'));
+ style = mxUtils.setStyle(style, mxConstants.STYLE_GRADIENTCOLOR,
+ mxUtils.getValue(defaults, mxConstants.STYLE_GRADIENTCOLOR, null));
+
+ if (graph.getModel().isVertex(cells[i]))
+ {
+ style = mxUtils.setStyle(style, mxConstants.STYLE_FONTCOLOR,
+ mxUtils.getValue(defaults, mxConstants.STYLE_FONTCOLOR, null));
+ }
+ }
+
+ graph.getModel().setStyle(cells[i], style);
+ }
+ }
+ finally
+ {
+ graph.getModel().endUpdate();
+ }
+ }));
+
+ btn.className = 'geStyleButton';
+ btn.style.width = '36px';
+ btn.style.height = (this.defaultColorSchemes.length <= maxEntries) ? '24px' : '30px';
+ btn.style.margin = '0px 6px 6px 0px';
+
+ if (colorset != null)
+ {
+ var b = (Editor.isDarkMode()) ? '2px solid' : '1px solid';
+
+ if (colorset['border'] != null)
+ {
+ b = colorset['border'];
+ }
+
+ if (colorset['gradient'] != null)
+ {
+ if (mxClient.IS_IE && (document.documentMode < 10))
+ {
+ btn.style.filter = 'progid:DXImageTransform.Microsoft.Gradient('+
+ 'StartColorStr=\'' + colorset['fill'] +
+ '\', EndColorStr=\'' + colorset['gradient'] + '\', GradientType=0)';
+ }
+ else
+ {
+ btn.style.backgroundImage = 'linear-gradient(' + colorset['fill'] + ' 0px,' +
+ colorset['gradient'] + ' 100%)';
+ }
+ }
+ else if (colorset['fill'] == mxConstants.NONE)
+ {
+ btn.style.background = 'url(\'' + Dialog.prototype.noColorImage + '\')';
+ }
+ else if (colorset['fill'] == '')
+ {
+ btn.style.backgroundColor = mxUtils.getValue(graph.defaultVertexStyle,
+ mxConstants.STYLE_FILLCOLOR, (Editor.isDarkMode()) ? Editor.darkColor : '#ffffff');
+ }
+ else
+ {
+ btn.style.backgroundColor = colorset['fill'] || mxUtils.getValue(graph.defaultVertexStyle,
+ mxConstants.STYLE_FILLCOLOR, (Editor.isDarkMode()) ? Editor.darkColor : '#ffffff');
+ }
+
+ if (colorset['stroke'] == mxConstants.NONE)
+ {
+ btn.style.border = b + ' transparent';
+ }
+ else if (colorset['stroke'] == '')
+ {
+ btn.style.border = b + ' ' + mxUtils.getValue(graph.defaultVertexStyle,
+ mxConstants.STYLE_STROKECOLOR, (!Editor.isDarkMode()) ? Editor.darkColor : '#ffffff');
+ }
+ else
+ {
+ btn.style.border = b + ' ' + (colorset['stroke'] || mxUtils.getValue(graph.defaultVertexStyle,
+ mxConstants.STYLE_STROKECOLOR, (!Editor.isDarkMode()) ? Editor.darkColor : '#ffffff'));
+ }
+
+ if (colorset['title'] != null)
+ {
+ btn.setAttribute('title', colorset['title']);
+ }
+ }
+ else
+ {
+ var bg = mxUtils.getValue(graph.defaultVertexStyle, mxConstants.STYLE_FILLCOLOR, '#ffffff');
+ var bd = mxUtils.getValue(graph.defaultVertexStyle, mxConstants.STYLE_STROKECOLOR, '#000000');
+
+ btn.style.backgroundColor = bg;
+ btn.style.border = '1px solid ' + bd;
+ }
+
+ btn.style.borderRadius = '0';
+
+ picker.appendChild(btn);
+ });
+
+ picker.innerText = '';
+
+ for (var i = 0; i < colorsets.length; i++)
+ {
+ if (i > 0 && mxUtils.mod(i, 4) == 0)
+ {
+ mxUtils.br(picker);
+ }
+
+ addButton(colorsets[i]);
+ }
+ });
+
+ if (this.format.currentScheme == null)
+ {
+ setScheme(Math.min(dots.length - 1, Editor.isDarkMode()
+ ? 1 : (urlParams['sketch'] == '1' ? 5 : 0)));
+ }
+ else
+ {
+ setScheme(this.format.currentScheme);
+ }
+
+ var bottom = (this.defaultColorSchemes.length <= maxEntries) ? 28 : 8;
+
+ var left = document.createElement('div');
+ left.style.cssText = 'position:absolute;left:10px;top:8px;bottom:' + bottom + 'px;width:20px;margin:4px;opacity:0.5;' +
+ 'background-repeat:no-repeat;background-position:center center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAAB2dnZ4eHh3d3d1dXVxcXF2dnZ2dnZ2dnZxcXF2dnYmb3w1AAAACnRSTlMAfCTkhhvb7cQSPH2JPgAAADRJREFUCNdjwACMAmBKaiGYs2oJmLPKAZ3DabU8AMRTXpUKopislqFyVzCAuUZgikkBZjoAcMYLnp53P/UAAAAASUVORK5CYII=);';
+
+ mxEvent.addListener(left, 'click', mxUtils.bind(this, function()
+ {
+ setScheme(mxUtils.mod(this.format.currentScheme - 1, this.defaultColorSchemes.length));
+ }));
+
+ var right = document.createElement('div');
+ right.style.cssText = 'position:absolute;left:202px;top:8px;bottom:' + bottom + 'px;width:20px;margin:4px;opacity:0.5;' +
+ 'background-repeat:no-repeat;background-position:center center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAAB2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnYBuwCcAAAACnRSTlMAfCTkhhvb7cQSPH2JPgAAADZJREFUCNdjQAOMAmBKaiGY8loF5rKswsZlrVo8AUiFrTICcbIWK8A5DF1gDoMymMPApIAwHwCS0Qx/U7qCBQAAAABJRU5ErkJggg==);';
+
+ if (this.defaultColorSchemes.length > 1)
+ {
+ div.appendChild(left);
+ div.appendChild(right);
+ }
+
+ mxEvent.addListener(right, 'click', mxUtils.bind(this, function()
+ {
+ setScheme(mxUtils.mod(this.format.currentScheme + 1, this.defaultColorSchemes.length));
+ }));
+
+ // Hover state
+ function addHoverState(elt)
+ {
+ mxEvent.addListener(elt, 'mouseenter', function()
+ {
+ elt.style.opacity = '1';
+ });
+ mxEvent.addListener(elt, 'mouseleave', function()
+ {
+ elt.style.opacity = '0.5';
+ });
+ };
+
+ addHoverState(left);
+ addHoverState(right);
+
+ updateScheme(this.defaultColorSchemes[this.format.currentScheme]);
+
+ if (this.defaultColorSchemes.length <= maxEntries)
+ {
+ div.appendChild(switcher);
+ }
+}
+
+return div;
+};
diff --git a/oaweb/public/cherry/drawio/Graph.js b/oaweb/public/cherry/drawio/Graph.js
new file mode 100644
index 0000000..552f163
--- /dev/null
+++ b/oaweb/public/cherry/drawio/Graph.js
@@ -0,0 +1,15007 @@
+/**
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+// Workaround for handling named HTML entities in mxUtils.parseXml
+// LATER: How to configure DOMParser to just ignore all entities?
+(function()
+{
+ var entities = [
+ ['nbsp', '160'],
+ ['shy', '173']
+ ];
+
+ var parseXml = mxUtils.parseXml;
+
+ mxUtils.parseXml = function(text)
+ {
+ for (var i = 0; i < entities.length; i++)
+ {
+ text = text.replace(new RegExp(
+ '&' + entities[i][0] + ';', 'g'),
+ '' + entities[i][1] + ';');
+ }
+
+ return parseXml(text);
+ };
+})();
+
+// Shim for missing toISOString in older versions of IE
+// See https://stackoverflow.com/questions/12907862
+if (!Date.prototype.toISOString)
+{
+ (function()
+ {
+ function pad(number)
+ {
+ var r = String(number);
+
+ if (r.length === 1)
+ {
+ r = '0' + r;
+ }
+
+ return r;
+ };
+
+ Date.prototype.toISOString = function()
+ {
+ return this.getUTCFullYear()
+ + '-' + pad( this.getUTCMonth() + 1 )
+ + '-' + pad( this.getUTCDate() )
+ + 'T' + pad( this.getUTCHours() )
+ + ':' + pad( this.getUTCMinutes() )
+ + ':' + pad( this.getUTCSeconds() )
+ + '.' + String( (this.getUTCMilliseconds()/1000).toFixed(3) ).slice( 2, 5 )
+ + 'Z';
+ };
+ }());
+}
+
+// Shim for Date.now()
+if (!Date.now)
+{
+ Date.now = function()
+ {
+ return new Date().getTime();
+ };
+}
+
+// Polyfill for Uint8Array.from in IE11 used in Graph.decompress
+// See https://stackoverflow.com/questions/36810940/alternative-or-polyfill-for-array-from-on-the-internet-explorer
+if (!Uint8Array.from) {
+ Uint8Array.from = (function () {
+ var toStr = Object.prototype.toString;
+ var isCallable = function (fn) {
+ return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
+ };
+ var toInteger = function (value) {
+ var number = Number(value);
+ if (isNaN(number)) { return 0; }
+ if (number === 0 || !isFinite(number)) { return number; }
+ return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
+ };
+ var maxSafeInteger = Math.pow(2, 53) - 1;
+ var toLength = function (value) {
+ var len = toInteger(value);
+ return Math.min(Math.max(len, 0), maxSafeInteger);
+ };
+
+ // The length property of the from method is 1.
+ return function from(arrayLike/*, mapFn, thisArg */) {
+ // 1. Let C be the this value.
+ var C = this;
+
+ // 2. Let items be ToObject(arrayLike).
+ var items = Object(arrayLike);
+
+ // 3. ReturnIfAbrupt(items).
+ if (arrayLike == null) {
+ throw new TypeError("Array.from requires an array-like object - not null or undefined");
+ }
+
+ // 4. If mapfn is undefined, then let mapping be false.
+ var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
+ var T;
+ if (typeof mapFn !== 'undefined') {
+ // 5. else
+ // 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
+ if (!isCallable(mapFn)) {
+ throw new TypeError('Array.from: when provided, the second argument must be a function');
+ }
+
+ // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
+ if (arguments.length > 2) {
+ T = arguments[2];
+ }
+ }
+
+ // 10. Let lenValue be Get(items, "length").
+ // 11. Let len be ToLength(lenValue).
+ var len = toLength(items.length);
+
+ // 13. If IsConstructor(C) is true, then
+ // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
+ // 14. a. Else, Let A be ArrayCreate(len).
+ var A = isCallable(C) ? Object(new C(len)) : new Array(len);
+
+ // 16. Let k be 0.
+ var k = 0;
+ // 17. Repeat, while k < len… (also steps a - h)
+ var kValue;
+ while (k < len) {
+ kValue = items[k];
+ if (mapFn) {
+ A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
+ } else {
+ A[k] = kValue;
+ }
+ k += 1;
+ }
+ // 18. Let putStatus be Put(A, "length", len, true).
+ A.length = len;
+ // 20. Return A.
+ return A;
+ };
+ }());
+}
+
+/**
+ * Measurements Units
+ */
+mxConstants.POINTS = 1;
+mxConstants.MILLIMETERS = 2;
+mxConstants.INCHES = 3;
+mxConstants.METERS = 4;
+
+/**
+ * This ratio is with page scale 1
+ */
+mxConstants.PIXELS_PER_MM = 3.937;
+mxConstants.PIXELS_PER_INCH = 100;
+mxConstants.SHADOW_OPACITY = 0.25;
+mxConstants.SHADOWCOLOR = '#000000';
+mxConstants.VML_SHADOWCOLOR = '#d0d0d0';
+
+mxCodec.allowlist = ['mxStylesheet', 'Array', 'mxGraphModel', 'html',
+ 'mxCell', 'mxGeometry', 'mxRectangle', 'mxPoint',
+ 'mxChildChange', 'mxRootChange', 'mxTerminalChange',
+ 'mxValueChange', 'mxStyleChange', 'mxGeometryChange',
+ 'mxCollapseChange', 'mxVisibleChange', 'mxCellAttributeChange'];
+mxGraph.prototype.pageBreakColor = '#c0c0c0';
+mxGraph.prototype.pageScale = 1;
+
+// Letter page format is default in US, Canada and Mexico
+(function()
+{
+ try
+ {
+ if (navigator != null && navigator.language != null)
+ {
+ var lang = navigator.language.toLowerCase();
+ mxGraph.prototype.pageFormat = (lang === 'en-us' || lang === 'en-ca' || lang === 'es-mx') ?
+ mxConstants.PAGE_FORMAT_LETTER_PORTRAIT : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+})();
+
+// Matches label positions of mxGraph 1.x
+mxText.prototype.baseSpacingTop = 5;
+mxText.prototype.baseSpacingBottom = 1;
+
+// Keeps edges between relative child cells inside parent
+mxGraphModel.prototype.ignoreRelativeEdgeParent = false;
+
+// Defines grid properties
+mxGraphView.prototype.gridImage = (mxClient.IS_SVG) ? 'data:image/gif;base64,R0lGODlhCgAKAJEAAAAAAP///8zMzP///yH5BAEAAAMALAAAAAAKAAoAAAIJ1I6py+0Po2wFADs=' :
+ IMAGE_PATH + '/grid.gif';
+mxGraphView.prototype.gridSteps = 4;
+mxGraphView.prototype.minGridSize = 4;
+
+// UrlParams is null in embed mode
+mxGraphView.prototype.defaultGridColor = '#d0d0d0';
+mxGraphView.prototype.defaultDarkGridColor = '#424242';
+mxGraphView.prototype.gridColor = mxGraphView.prototype.defaultGridColor;
+
+// Units
+mxGraphView.prototype.unit = mxConstants.POINTS;
+
+mxGraphView.prototype.setUnit = function(unit)
+{
+ if (this.unit != unit)
+ {
+ this.unit = unit;
+
+ this.fireEvent(new mxEventObject('unitChanged', 'unit', unit));
+ }
+};
+
+// Alternative text for unsupported foreignObjects
+mxSvgCanvas2D.prototype.foAltText = '[Not supported by viewer]';
+
+// Hook for custom constraints
+mxShape.prototype.getConstraints = function(style, w, h)
+{
+ return null;
+};
+
+// Override for clipSvg style
+mxImageShape.prototype.getImageDataUri = function()
+{
+ var src = this.image;
+
+ if (src.substring(0, 26) == 'data:image/svg+xml;base64,' && this.style != null &&
+ mxUtils.getValue(this.style, 'clipSvg', '0') == '1')
+ {
+ if (this.clippedSvg == null || this.clippedImage != src)
+ {
+ this.clippedSvg = Graph.clipSvgDataUri(src, true);
+ this.clippedImage = src;
+ }
+
+ src = this.clippedSvg;
+ }
+
+ return src;
+};
+
+// Override to use key as fallback
+(function()
+{
+ var mxResourcesGet = mxResources.get;
+
+ mxResources.get = function(key, params, defaultValue)
+ {
+ if (defaultValue == null)
+ {
+ defaultValue = key;
+ }
+
+ return mxResourcesGet.apply(this, [key, params, defaultValue]);
+ };
+
+})();
+
+/**
+ * Constructs a new graph instance. Note that the constructor does not take a
+ * container because the graph instance is needed for creating the UI, which
+ * in turn will create the container for the graph. Hence, the container is
+ * assigned later in EditorUi.
+ */
+/**
+ * Defines graph class.
+ */
+Graph = function(container, model, renderHint, stylesheet, themes, standalone)
+{
+ mxGraph.call(this, container, model, renderHint, stylesheet);
+
+ this.themes = themes || this.defaultThemes;
+ this.currentEdgeStyle = mxUtils.clone(this.defaultEdgeStyle);
+ this.currentVertexStyle = mxUtils.clone(this.defaultVertexStyle);
+ this.standalone = (standalone != null) ? standalone : false;
+
+ // Sets the base domain URL and domain path URL for relative links.
+ var b = this.baseUrl;
+ var p = b.indexOf('//');
+ this.domainUrl = '';
+ this.domainPathUrl = '';
+
+ if (p > 0)
+ {
+ var d = b.indexOf('/', p + 2);
+
+ if (d > 0)
+ {
+ this.domainUrl = b.substring(0, d);
+ }
+
+ d = b.lastIndexOf('/');
+
+ if (d > 0)
+ {
+ this.domainPathUrl = b.substring(0, d + 1);
+ }
+ }
+
+ // Adds support for HTML labels via style. Note: Currently, only the Java
+ // backend supports HTML labels but CSS support is limited to the following:
+ // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
+ // TODO: Wrap should not affect isHtmlLabel output (should be handled later)
+ this.isHtmlLabel = function(cell)
+ {
+ var style = this.getCurrentCellStyle(cell);
+
+ return (style != null) ? (style['html'] == '1' || style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') : false;
+ };
+
+ // Implements a listener for hover and click handling on edges and tables
+ if (this.immediateHandling)
+ {
+ var start = {
+ point: null,
+ event: null,
+ state: null,
+ handle: null,
+ selected: false
+ };
+
+ var initialSelected = false;
+
+ // Uses this event to process mouseDown to check the selection state before it is changed
+ this.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt)
+ {
+ if (evt.getProperty('eventName') == 'mouseDown' && this.isEnabled())
+ {
+ var me = evt.getProperty('event');
+ var state = me.getState();
+ var s = this.view.scale;
+
+ if (!mxEvent.isAltDown(me.getEvent()) && state != null)
+ {
+ initialSelected = this.isCellSelected(state.cell);
+
+ if (!this.panningHandler.isActive() && !mxEvent.isControlDown(me.getEvent()))
+ {
+ var handler = this.selectionCellsHandler.getHandler(state.cell);
+
+ // Cell handles have precedence over row and col resize
+ if (handler == null || handler.getHandleForEvent(me) == null)
+ {
+ var box = new mxRectangle(me.getGraphX() - 1, me.getGraphY() - 1);
+ var tol = mxEvent.isTouchEvent(me.getEvent()) ?
+ mxShape.prototype.svgStrokeTolerance - 1 :
+ (mxShape.prototype.svgStrokeTolerance + 2) / 2;
+ var t1 = tol + 2;
+ box.grow(tol);
+
+ // Ignores clicks inside cell to avoid delayed selection on
+ // merged cells when clicking on invisible part of dividers
+ if (this.isTableCell(state.cell) && this.isCellMovable(state.cell) &&
+ !this.isCellSelected(state.cell) &&
+ (!mxUtils.contains(state, me.getGraphX() - t1, me.getGraphY() - t1) ||
+ !mxUtils.contains(state, me.getGraphX() - t1, me.getGraphY() + t1) ||
+ !mxUtils.contains(state, me.getGraphX() + t1, me.getGraphY() + t1) ||
+ !mxUtils.contains(state, me.getGraphX() + t1, me.getGraphY() - t1)))
+ {
+ var row = this.model.getParent(state.cell);
+ var table = this.model.getParent(row);
+
+ if (!this.isCellSelected(table))
+ {
+ var b = tol * s;
+ var b2 = 2 * b;
+
+ // Ignores events on top line of top row and left line of left column
+ if ((this.model.getChildAt(table, 0) != row) && mxUtils.intersects(box,
+ new mxRectangle(state.x, state.y - b, state.width, b2)) ||
+ (this.model.getChildAt(row, 0) != state.cell) && mxUtils.intersects(box,
+ new mxRectangle(state.x - b, state.y, b2, state.height)) ||
+ mxUtils.intersects(box, new mxRectangle(state.x, state.y + state.height - b, state.width, b2)) ||
+ mxUtils.intersects(box, new mxRectangle(state.x + state.width - b, state.y, b2, state.height)))
+ {
+ var wasSelected = this.selectionCellsHandler.isHandled(table);
+ this.selectCellForEvent(table, me.getEvent());
+ handler = this.selectionCellsHandler.getHandler(table);
+
+ if (handler != null)
+ {
+ var handle = handler.getHandleForEvent(me);
+
+ if (handle != null)
+ {
+ handler.start(me.getGraphX(), me.getGraphY(), handle);
+ handler.blockDelayedSelection = !wasSelected;
+ me.consume();
+ }
+ }
+ }
+ }
+ }
+
+ // Hover for swimlane start sizes inside tables
+ var current = state;
+
+ while (!me.isConsumed() && current != null && (this.isTableCell(current.cell) ||
+ this.isTableRow(current.cell) || this.isTable(current.cell)))
+ {
+ if (this.isSwimlane(current.cell) && this.isCellMovable(current.cell))
+ {
+ var offset = this.getActualStartSize(current.cell);
+
+ if (((offset.x > 0 || offset.width > 0) && mxUtils.intersects(box, new mxRectangle(
+ current.x + (offset.x - offset.width - 1) * s + ((offset.x == 0) ? current.width : 0),
+ current.y, 1, current.height))) || ((offset.y > 0 || offset.height > 0) &&
+ mxUtils.intersects(box, new mxRectangle(current.x, current.y + (offset.y -
+ offset.height - 1) * s + ((offset.y == 0) ? current.height : 0), current.width, 1))))
+ {
+ this.selectCellForEvent(current.cell, me.getEvent());
+ handler = this.selectionCellsHandler.getHandler(current.cell);
+
+ if (handler != null && handler.customHandles != null)
+ {
+ // Swimlane start size handle is last custom handle
+ var handle = mxEvent.CUSTOM_HANDLE - handler.customHandles.length + 1;
+ handler.start(me.getGraphX(), me.getGraphY(), handle);
+ me.consume();
+ }
+ }
+ }
+
+ current = this.view.getState(this.model.getParent(current.cell));
+ }
+ }
+ }
+ }
+ }
+ }));
+
+ // Uses this event to process mouseDown to check the selection state before it is changed
+ this.addListener(mxEvent.CONSUME_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt)
+ {
+ if (evt.getProperty('eventName') == 'mouseDown' && this.isEnabled())
+ {
+ var me = evt.getProperty('event');
+ var state = me.getState();
+
+ if (!mxEvent.isAltDown(me.getEvent()) && !mxEvent.isControlDown(evt) &&
+ !mxEvent.isShiftDown(evt) && !initialSelected &&
+ state != null && this.model.isEdge(state.cell))
+ {
+ start.point = new mxPoint(me.getGraphX(), me.getGraphY());
+ start.selected = this.isCellSelected(state.cell);
+ start.state = state;
+ start.event = me;
+
+ if (state.text != null && state.text.boundingBox != null &&
+ mxUtils.contains(state.text.boundingBox, me.getGraphX(), me.getGraphY()))
+ {
+ start.handle = mxEvent.LABEL_HANDLE;
+ }
+ else
+ {
+ var handler = this.selectionCellsHandler.getHandler(state.cell);
+
+ if (handler != null && handler.bends != null && handler.bends.length > 0)
+ {
+ start.handle = handler.getHandleForEvent(me);
+ }
+ }
+ }
+ }
+ }));
+
+ this.addMouseListener(
+ {
+ mouseDown: function(sender, me) {},
+ mouseMove: mxUtils.bind(this, function(sender, me)
+ {
+ // Checks if any other handler is active
+ var handlerMap = this.selectionCellsHandler.handlers.map;
+
+ for (var key in handlerMap)
+ {
+ if (handlerMap[key].index != null)
+ {
+ return;
+ }
+ }
+
+ if (this.isEnabled() && !this.panningHandler.isActive() && !mxEvent.isAltDown(me.getEvent()))
+ {
+ var tol = this.tolerance;
+
+ if (start.point != null && start.state != null && start.event != null)
+ {
+ var state = start.state;
+
+ if (start.handle != null || Math.abs(start.point.x - me.getGraphX()) > tol ||
+ Math.abs(start.point.y - me.getGraphY()) > tol)
+ {
+ var handler = null;
+
+ if (!mxEvent.isControlDown(me.getEvent()) &&
+ !mxEvent.isShiftDown(me.getEvent()))
+ {
+ handler = this.selectionCellsHandler.getHandler(state.cell);
+ }
+
+ if (handler != null && handler.bends != null && handler.bends.length > 0)
+ {
+ handler.redrawHandles();
+ var handle = (start.handle != null) ? start.handle :
+ handler.getHandleForEvent(start.event);
+ var edgeStyle = this.view.getEdgeStyle(state);
+ var entity = edgeStyle == mxEdgeStyle.EntityRelation;
+
+ // Handles special case where label was clicked on unselected edge in which
+ // case the label will be moved regardless of the handle that is returned
+ if (!start.selected && start.handle == mxEvent.LABEL_HANDLE)
+ {
+ handle = start.handle;
+ }
+
+ if (!entity || handle == 0 || handle == handler.bends.length - 1 || handle == mxEvent.LABEL_HANDLE)
+ {
+ // Source or target handle or connected for direct handle access or orthogonal line
+ // with just two points where the central handle is moved regardless of mouse position
+ if (handle == mxEvent.LABEL_HANDLE || handle == 0 || state.visibleSourceState != null ||
+ handle == handler.bends.length - 1 || state.visibleTargetState != null)
+ {
+ if (!entity && handle != mxEvent.LABEL_HANDLE)
+ {
+ var pts = state.absolutePoints;
+
+ // Default case where handles are at corner points handles
+ // drag of corner as drag of existing point
+ if (pts != null && ((edgeStyle == null && handle == null) ||
+ edgeStyle == mxEdgeStyle.SegmentConnector ||
+ edgeStyle == mxEdgeStyle.OrthConnector))
+ {
+ // Does not use handles if they were not initially visible
+ handle = start.handle;
+
+ if (handle == null)
+ {
+ var box = new mxRectangle(start.point.x, start.point.y);
+ box.grow(mxEdgeHandler.prototype.handleImage.width / 2);
+
+ if (mxUtils.contains(box, pts[0].x, pts[0].y))
+ {
+ // Moves source terminal handle
+ handle = 0;
+ }
+ else if (mxUtils.contains(box, pts[pts.length - 1].x, pts[pts.length - 1].y))
+ {
+ // Moves target terminal handle
+ handle = handler.bends.length - 1;
+ }
+ else
+ {
+ // Checks if edge has no bends
+ var nobends = edgeStyle != null && (pts.length == 2 || (pts.length == 3 &&
+ ((Math.round(pts[0].x - pts[1].x) == 0 && Math.round(pts[1].x - pts[2].x) == 0) ||
+ (Math.round(pts[0].y - pts[1].y) == 0 && Math.round(pts[1].y - pts[2].y) == 0))));
+
+ if (nobends)
+ {
+ // Moves central handle for straight orthogonal edges
+ handle = 2;
+ }
+ else
+ {
+ // Finds and moves vertical or horizontal segment
+ handle = mxUtils.findNearestSegment(state, start.point.x, start.point.y);
+
+ // Converts segment to virtual handle index
+ if (edgeStyle == null)
+ {
+ handle = mxEvent.VIRTUAL_HANDLE - handle;
+ }
+ // Maps segment to handle
+ else
+ {
+ handle += 1;
+ }
+ }
+ }
+ }
+ }
+
+ // Creates a new waypoint and starts moving it
+ if (handle == null)
+ {
+ handle = mxEvent.VIRTUAL_HANDLE;
+ }
+ }
+
+ handler.start(me.getGraphX(), me.getGraphX(), handle);
+ me.consume();
+
+ // Removes preview rectangle in graph handler
+ this.graphHandler.reset();
+ }
+ }
+ else if (entity && (state.visibleSourceState != null || state.visibleTargetState != null))
+ {
+ // Disables moves on entity to make it consistent
+ this.graphHandler.reset();
+ me.consume();
+ }
+ }
+
+ if (handler != null)
+ {
+ // Lazy selection for edges inside groups
+ if (this.selectionCellsHandler.isHandlerActive(handler))
+ {
+ if (!this.isCellSelected(state.cell))
+ {
+ this.selectionCellsHandler.handlers.put(state.cell, handler);
+ this.selectCellForEvent(state.cell, me.getEvent());
+ }
+ }
+ else if (!this.isCellSelected(state.cell))
+ {
+ // Destroy temporary handler
+ handler.destroy();
+ }
+ }
+
+ // Reset start state
+ start.selected = false;
+ start.handle = null;
+ start.state = null;
+ start.event = null;
+ start.point = null;
+ }
+ }
+ else
+ {
+ // Updates cursor for unselected edges under the mouse
+ var state = me.getState();
+
+ if (state != null && this.isCellEditable(state.cell))
+ {
+ var cursor = null;
+
+ // Checks if state was removed in call to stopEditing above
+ if (this.model.isEdge(state.cell) &&
+ !this.isCellSelected(state.cell) &&
+ !mxEvent.isAltDown(me.getEvent()) &&
+ !mxEvent.isControlDown(me.getEvent()) &&
+ !mxEvent.isShiftDown(me.getEvent()))
+ {
+ var box = new mxRectangle(me.getGraphX(), me.getGraphY());
+ box.grow(mxEdgeHandler.prototype.handleImage.width / 2);
+ var pts = state.absolutePoints;
+
+ if (pts != null)
+ {
+ if (state.text != null && state.text.boundingBox != null &&
+ mxUtils.contains(state.text.boundingBox, me.getGraphX(), me.getGraphY()))
+ {
+ cursor = 'move';
+ }
+ else if (mxUtils.contains(box, pts[0].x, pts[0].y) ||
+ mxUtils.contains(box, pts[pts.length - 1].x, pts[pts.length - 1].y))
+ {
+ cursor = 'pointer';
+ }
+ else if (state.visibleSourceState != null || state.visibleTargetState != null)
+ {
+ // Moving is not allowed for entity relation but still indicate hover state
+ var tmp = this.view.getEdgeStyle(state);
+ cursor = 'crosshair';
+
+ if (tmp != mxEdgeStyle.EntityRelation && this.isOrthogonal(state))
+ {
+ var idx = mxUtils.findNearestSegment(state, me.getGraphX(), me.getGraphY());
+
+ if (idx < pts.length - 1 && idx >= 0)
+ {
+ cursor = (Math.round(pts[idx].x - pts[idx + 1].x) == 0) ?
+ 'col-resize' : 'row-resize';
+ }
+ }
+ }
+ }
+ }
+ else if (!mxEvent.isControlDown(me.getEvent()))
+ {
+ var tol = mxShape.prototype.svgStrokeTolerance / 2;
+ var box = new mxRectangle(me.getGraphX(), me.getGraphY());
+ box.grow(tol);
+
+ if (this.isTableCell(state.cell) && this.isCellMovable(state.cell))
+ {
+ var row = this.model.getParent(state.cell);
+ var table = this.model.getParent(row);
+
+ if (!this.isCellSelected(table))
+ {
+ if ((mxUtils.intersects(box, new mxRectangle(state.x, state.y - 2, state.width, 4)) &&
+ this.model.getChildAt(table, 0) != row) || mxUtils.intersects(box,
+ new mxRectangle(state.x, state.y + state.height - 2, state.width, 4)))
+ {
+ cursor ='row-resize';
+ }
+ else if ((mxUtils.intersects(box, new mxRectangle(state.x - 2, state.y, 4, state.height)) &&
+ this.model.getChildAt(row, 0) != state.cell) || mxUtils.intersects(box,
+ new mxRectangle(state.x + state.width - 2, state.y, 4, state.height)))
+ {
+ cursor ='col-resize';
+ }
+ }
+ }
+
+ // Hover for swimlane start sizes inside tables
+ var current = state;
+
+ while (cursor == null && current != null && (this.isTableCell(current.cell) ||
+ this.isTableRow(current.cell) || this.isTable(current.cell)))
+ {
+ if (this.isSwimlane(current.cell) && this.isCellMovable(current.cell))
+ {
+ var offset = this.getActualStartSize(current.cell);
+ var s = this.view.scale;
+
+ if ((offset.x > 0 || offset.width > 0) && mxUtils.intersects(box, new mxRectangle(
+ current.x + (offset.x - offset.width - 1) * s + ((offset.x == 0) ? current.width * s : 0),
+ current.y, 1, current.height)))
+ {
+ cursor ='col-resize';
+ }
+ else if ((offset.y > 0 || offset.height > 0) && mxUtils.intersects(box, new mxRectangle(
+ current.x, current.y + (offset.y - offset.height - 1) * s + ((offset.y == 0) ? current.height : 0),
+ current.width, 1)))
+ {
+ cursor ='row-resize';
+ }
+ }
+
+ current = this.view.getState(this.model.getParent(current.cell));
+ }
+ }
+
+ if (cursor != null)
+ {
+ state.setCursor(cursor);
+ }
+ }
+ }
+ }
+ }),
+ mouseUp: mxUtils.bind(this, function(sender, me)
+ {
+ start.state = null;
+ start.event = null;
+ start.point = null;
+ start.handle = null;
+ })
+ });
+ }
+
+ this.cellRenderer.minSvgStrokeWidth = 0.1;
+
+ // HTML entities are displayed as plain text in wrapped plain text labels
+ this.cellRenderer.getLabelValue = function(state)
+ {
+ var result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments);
+
+ if (state.view.graph.isHtmlLabel(state.cell))
+ {
+ if (state.style['html'] != 1)
+ {
+ result = mxUtils.htmlEntities(result, false);
+ }
+ else
+ {
+ // Skips sanitizeHtml for unchanged labels
+ if (state.lastLabelValue != result)
+ {
+ state.lastLabelValue = result;
+ state.lastSanitizedLabelValue = Graph.sanitizeHtml(result);
+ }
+
+ result = state.lastSanitizedLabelValue;
+ }
+ }
+
+ return result;
+ };
+
+ // All code below not available and not needed in embed mode
+ if (typeof mxVertexHandler !== 'undefined')
+ {
+ this.setConnectable(true);
+ this.setDropEnabled(true);
+ this.setPanning(true);
+ this.setTooltips(true);
+ this.setAllowLoops(true);
+ this.allowAutoPanning = true;
+ this.resetEdgesOnConnect = false;
+ this.constrainChildren = false;
+ this.constrainRelativeChildren = true;
+
+ // Do not scroll after moving cells
+ this.graphHandler.scrollOnMove = false;
+ this.graphHandler.scaleGrid = true;
+
+ // Disables cloning of connection sources by default
+ this.connectionHandler.setCreateTarget(false);
+ this.connectionHandler.insertBeforeSource = true;
+
+ // Disables built-in connection starts
+ this.connectionHandler.isValidSource = function(cell, me)
+ {
+ return false;
+ };
+
+ // Sets the style to be used when an elbow edge is double clicked
+ this.alternateEdgeStyle = 'vertical';
+
+ if (stylesheet == null)
+ {
+ this.loadStylesheet();
+ }
+
+ // Adds page centers to the guides for moving cells
+ var graphHandlerGetGuideStates = this.graphHandler.getGuideStates;
+ this.graphHandler.getGuideStates = function()
+ {
+ var result = graphHandlerGetGuideStates.apply(this, arguments);
+
+ // Create virtual cell state for page centers
+ if (this.graph.pageVisible)
+ {
+ var guides = [];
+
+ var pf = this.graph.pageFormat;
+ var ps = this.graph.pageScale;
+ var pw = pf.width * ps;
+ var ph = pf.height * ps;
+ var t = this.graph.view.translate;
+ var s = this.graph.view.scale;
+
+ var layout = this.graph.getPageLayout();
+
+ for (var i = 0; i < layout.width; i++)
+ {
+ guides.push(new mxRectangle(((layout.x + i) * pw + t.x) * s,
+ (layout.y * ph + t.y) * s, pw * s, ph * s));
+ }
+
+ for (var j = 1; j < layout.height; j++)
+ {
+ guides.push(new mxRectangle((layout.x * pw + t.x) * s,
+ ((layout.y + j) * ph + t.y) * s, pw * s, ph * s));
+ }
+
+ // Page center guides have precedence over normal guides
+ result = guides.concat(result);
+ }
+
+ return result;
+ };
+
+ // Overrides zIndex for dragElement
+ mxDragSource.prototype.dragElementZIndex = mxPopupMenu.prototype.zIndex;
+
+ // Overrides color for virtual guides for page centers
+ mxGuide.prototype.getGuideColor = function(state, horizontal)
+ {
+ return (state.cell == null) ? '#ffa500' /* orange */ : mxConstants.GUIDE_COLOR;
+ };
+
+ // Changes color of move preview for black backgrounds
+ this.graphHandler.createPreviewShape = function(bounds)
+ {
+ this.previewColor = (this.graph.background == '#000000') ? '#ffffff' : mxGraphHandler.prototype.previewColor;
+
+ return mxGraphHandler.prototype.createPreviewShape.apply(this, arguments);
+ };
+
+ // Handles parts of cells by checking if part=1 is in the style and returning the parent
+ // if the parent is not already in the list of cells. container style is used to disable
+ // step into swimlanes and dropTarget style is used to disable acting as a drop target.
+ // LATER: Handle recursive parts
+ var graphHandlerGetCells = this.graphHandler.getCells;
+
+ this.graphHandler.getCells = function(initialCell)
+ {
+ var cells = graphHandlerGetCells.apply(this, arguments);
+ var lookup = new mxDictionary();
+ var newCells = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ // Propagates to composite parents or moves selected table rows
+ var cell = (this.graph.isTableCell(initialCell) &&
+ this.graph.isTableCell(cells[i]) &&
+ this.graph.isCellSelected(cells[i])) ?
+ this.graph.model.getParent(cells[i]) :
+ ((this.graph.isTableRow(initialCell) &&
+ this.graph.isTableRow(cells[i]) &&
+ this.graph.isCellSelected(cells[i])) ?
+ cells[i] : this.graph.getCompositeParent(cells[i]));
+
+ if (cell != null && !lookup.get(cell))
+ {
+ lookup.put(cell, true);
+ newCells.push(cell);
+ }
+ }
+
+ return newCells;
+ };
+
+ // Handles parts and selected rows in tables of cells for drag and drop
+ var graphHandlerStart = this.graphHandler.start;
+
+ this.graphHandler.start = function(cell, x, y, cells)
+ {
+ // Propagates to selected table row to start move
+ var ignoreParent = false;
+
+ if (this.graph.isTableCell(cell))
+ {
+ if (!this.graph.isCellSelected(cell))
+ {
+ cell = this.graph.model.getParent(cell);
+ }
+ else
+ {
+ ignoreParent = true;
+ }
+ }
+
+ if (!ignoreParent && (!this.graph.isTableRow(cell) || !this.graph.isCellSelected(cell)))
+ {
+ cell = this.graph.getCompositeParent(cell);
+ }
+
+ graphHandlerStart.apply(this, arguments);
+ };
+
+ // Handles parts of cells when cloning the source for new connections
+ this.connectionHandler.createTargetVertex = function(evt, source)
+ {
+ source = this.graph.getCompositeParent(source);
+
+ return mxConnectionHandler.prototype.createTargetVertex.apply(this, arguments);
+ };
+
+ // Applies newEdgeStyle
+ this.connectionHandler.insertEdge = function(parent, id, value, source, target, style)
+ {
+ var edge = mxConnectionHandler.prototype.insertEdge.apply(this, arguments);
+
+ if (source != null)
+ {
+ this.graph.applyNewEdgeStyle(source, [edge]);
+ }
+
+ return edge
+ };
+
+ // Creates rubberband selection and associates with graph instance
+ var rubberband = new mxRubberband(this);
+
+ this.getRubberband = function()
+ {
+ return rubberband;
+ };
+
+ // Timer-based activation of outline connect in connection handler
+ var startTime = new Date().getTime();
+ var timeOnTarget = 0;
+
+ var connectionHandlerMouseMove = this.connectionHandler.mouseMove;
+
+ this.connectionHandler.mouseMove = function()
+ {
+ var prev = this.currentState;
+ connectionHandlerMouseMove.apply(this, arguments);
+
+ if (prev != this.currentState)
+ {
+ startTime = new Date().getTime();
+ timeOnTarget = 0;
+ }
+ else
+ {
+ timeOnTarget = new Date().getTime() - startTime;
+ }
+ };
+
+ // Activates outline connect after 1500ms with touch event or if alt is pressed inside the shape
+ // outlineConnect=0 is a custom style that means do not connect to strokes inside the shape,
+ // or in other words, connect to the shape's perimeter if the highlight is under the mouse
+ // (the name is because the highlight, including all strokes, is called outline in the code)
+ var connectionHandleIsOutlineConnectEvent = this.connectionHandler.isOutlineConnectEvent;
+
+ this.connectionHandler.isOutlineConnectEvent = function(me)
+ {
+ if (mxEvent.isShiftDown(me.getEvent()) && mxEvent.isAltDown(me.getEvent()))
+ {
+ return false;
+ }
+ else
+ {
+ return (this.currentState != null && me.getState() == this.currentState && timeOnTarget > 2000) ||
+ ((this.currentState == null || mxUtils.getValue(this.currentState.style, 'outlineConnect', '1') != '0') &&
+ connectionHandleIsOutlineConnectEvent.apply(this, arguments));
+ }
+ };
+
+ // Adds shift+click to toggle selection state
+ var isToggleEvent = this.isToggleEvent;
+ this.isToggleEvent = function(evt)
+ {
+ return isToggleEvent.apply(this, arguments) || (!mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt));
+ };
+
+ // Workaround for Firefox where first mouse down is received
+ // after tap and hold if scrollbars are visible, which means
+ // start rubberband immediately if no cell is under mouse.
+ var isForceRubberBandEvent = rubberband.isForceRubberbandEvent;
+ rubberband.isForceRubberbandEvent = function(me)
+ {
+ return isForceRubberBandEvent.apply(this, arguments) ||
+ (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(me.getEvent())) ||
+ (mxUtils.hasScrollbars(this.graph.container) && mxClient.IS_FF &&
+ mxClient.IS_WIN && me.getState() == null && mxEvent.isTouchEvent(me.getEvent()));
+ };
+
+ // Shows hand cursor while panning
+ var prevCursor = null;
+
+ this.panningHandler.addListener(mxEvent.PAN_START, mxUtils.bind(this, function()
+ {
+ if (this.isEnabled())
+ {
+ prevCursor = this.container.style.cursor;
+ this.container.style.cursor = 'move';
+ }
+ }));
+
+ this.panningHandler.addListener(mxEvent.PAN_END, mxUtils.bind(this, function()
+ {
+ if (this.isEnabled())
+ {
+ this.container.style.cursor = prevCursor;
+ }
+ }));
+
+ this.popupMenuHandler.autoExpand = true;
+
+ this.popupMenuHandler.isSelectOnPopup = function(me)
+ {
+ return mxEvent.isMouseEvent(me.getEvent());
+ };
+
+ // Handles links in read-only graphs
+ // and cells in locked layers
+ var click = this.click;
+ this.click = function(me)
+ {
+ var locked = me.state == null && me.sourceState != null &&
+ this.isCellLocked(this.getLayerForCell(
+ me.sourceState.cell));
+
+ if ((!this.isEnabled() || locked) && !me.isConsumed())
+ {
+ var cell = (locked) ? me.sourceState.cell : me.getCell();
+
+ if (cell != null)
+ {
+ var link = this.getClickableLinkForCell(cell);
+
+ if (link != null)
+ {
+ if (this.isCustomLink(link))
+ {
+ this.customLinkClicked(link);
+ }
+ else
+ {
+ this.openLink(link);
+ }
+ }
+ }
+ }
+ else
+ {
+ return click.apply(this, arguments);
+ }
+ };
+
+ // Redirects tooltips for locked cells
+ this.tooltipHandler.getStateForEvent = function(me)
+ {
+ return me.sourceState;
+ };
+
+ // Opens links in tooltips in new windows
+ var tooltipHandlerShow = this.tooltipHandler.show;
+ this.tooltipHandler.show = function()
+ {
+ tooltipHandlerShow.apply(this, arguments);
+
+ if (this.div != null)
+ {
+ var links = this.div.getElementsByTagName('a');
+
+ for (var i = 0; i < links.length; i++)
+ {
+ if (links[i].getAttribute('href') != null &&
+ links[i].getAttribute('target') == null)
+ {
+ links[i].setAttribute('target', '_blank');
+ }
+ }
+ }
+ };
+
+ // Redirects tooltips for locked cells
+ this.tooltipHandler.getStateForEvent = function(me)
+ {
+ return me.sourceState;
+ };
+
+ // Redirects cursor for locked cells
+ var getCursorForMouseEvent = this.getCursorForMouseEvent;
+ this.getCursorForMouseEvent = function(me)
+ {
+ var locked = me.state == null && me.sourceState != null && this.isCellLocked(me.sourceState.cell);
+
+ return this.getCursorForCell((locked) ? me.sourceState.cell : me.getCell());
+ };
+
+ // Shows pointer cursor for clickable cells with links
+ // ie. if the graph is disabled and cells cannot be selected
+ var getCursorForCell = this.getCursorForCell;
+ this.getCursorForCell = function(cell)
+ {
+ if (!this.isEnabled() || this.isCellLocked(cell))
+ {
+ var link = this.getClickableLinkForCell(cell);
+
+ if (link != null)
+ {
+ return 'pointer';
+ }
+ else if (this.isCellLocked(cell))
+ {
+ return 'default';
+ }
+ }
+
+ return getCursorForCell.apply(this, arguments);
+ };
+
+ // Changes rubberband selection ignore locked cells
+ this.selectRegion = function(rect, evt)
+ {
+ var isect = (mxEvent.isAltDown(evt)) ? rect : null;
+ var cells = this.getCells(rect.x, rect.y,
+ rect.width, rect.height, null, null,
+ isect, null, true);
+
+ if (this.isToggleEvent(evt))
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.selectCellForEvent(cells[i], evt);
+ }
+ }
+ else
+ {
+ this.selectCellsForEvent(cells, evt);
+ }
+
+ return cells;
+ };
+
+ // Never removes cells from parents that are being moved
+ var graphHandlerShouldRemoveCellsFromParent = this.graphHandler.shouldRemoveCellsFromParent;
+ this.graphHandler.shouldRemoveCellsFromParent = function(parent, cells, evt)
+ {
+ if (this.graph.isCellSelected(parent))
+ {
+ return false;
+ }
+
+ return graphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
+ };
+
+ // Enables rubberband selection on cells in locked layers
+ var graphUpdateMouseEvent = this.updateMouseEvent;
+ this.updateMouseEvent = function(me)
+ {
+ me = graphUpdateMouseEvent.apply(this, arguments);
+
+ if (me.state != null && this.isCellLocked(this.getLayerForCell(me.getCell())))
+ {
+ me.state = null;
+ }
+
+ return me;
+ };
+
+ // Cells in locked layers are not selectable
+ var graphIsCellSelectable = this.isCellSelectable;
+ this.isCellSelectable = function(cell)
+ {
+ return graphIsCellSelectable.apply(this, arguments) &&
+ !this.isCellLocked(this.getLayerForCell(cell));
+ };
+
+ // Returns true if the given cell is locked
+ this.isCellLocked = function(cell)
+ {
+ while (cell != null)
+ {
+ if (mxUtils.getValue(this.getCurrentCellStyle(cell), 'locked', '0') == '1')
+ {
+ return true;
+ }
+
+ cell = this.model.getParent(cell);
+ }
+
+ return false;
+ };
+
+ var tapAndHoldSelection = null;
+
+ // Uses this event to process mouseDown to check the selection state before it is changed
+ this.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt)
+ {
+ if (evt.getProperty('eventName') == 'mouseDown')
+ {
+ var me = evt.getProperty('event');
+ var state = me.getState();
+
+ if (state != null && !this.isSelectionEmpty() && !this.isCellSelected(state.cell))
+ {
+ tapAndHoldSelection = this.getSelectionCells();
+ }
+ else
+ {
+ tapAndHoldSelection = null;
+ }
+ }
+ }));
+
+ // Tap and hold on background starts rubberband for multiple selected
+ // cells the cell associated with the event is deselected
+ this.addListener(mxEvent.TAP_AND_HOLD, mxUtils.bind(this, function(sender, evt)
+ {
+ if (!mxEvent.isMultiTouchEvent(evt))
+ {
+ var me = evt.getProperty('event');
+ var cell = evt.getProperty('cell');
+
+ if (cell == null)
+ {
+ var pt = mxUtils.convertPoint(this.container,
+ mxEvent.getClientX(me), mxEvent.getClientY(me));
+ rubberband.start(pt.x, pt.y);
+ }
+ else if (tapAndHoldSelection != null)
+ {
+ this.addSelectionCells(tapAndHoldSelection);
+ }
+ else if (this.getSelectionCount() > 1 && this.isCellSelected(cell))
+ {
+ this.removeSelectionCell(cell);
+ }
+
+ // Blocks further processing of the event
+ tapAndHoldSelection = null;
+ evt.consume();
+ }
+ }));
+
+ // On connect the target is selected and we clone the cell of the preview edge for insert
+ this.connectionHandler.selectCells = function(edge, target)
+ {
+ this.graph.setSelectionCell(target || edge);
+ };
+
+ // Shows connection points only if cell not selected and parent table not handled
+ this.connectionHandler.constraintHandler.isStateIgnored = function(state, source)
+ {
+ var graph = state.view.graph;
+
+ return source && (graph.isCellSelected(state.cell) || (graph.isTableRow(state.cell) &&
+ graph.selectionCellsHandler.isHandled(graph.model.getParent(state.cell))));
+ };
+
+ // Updates constraint handler if the selection changes
+ this.selectionModel.addListener(mxEvent.CHANGE, mxUtils.bind(this, function()
+ {
+ var ch = this.connectionHandler.constraintHandler;
+
+ if (ch.currentFocus != null && ch.isStateIgnored(ch.currentFocus, true))
+ {
+ ch.currentFocus = null;
+ ch.constraints = null;
+ ch.destroyIcons();
+ }
+
+ ch.destroyFocusHighlight();
+ }));
+
+ // Initializes touch interface
+ if (Graph.touchStyle)
+ {
+ this.initTouch();
+ }
+ }
+
+ //Create a unique offset object for each graph instance.
+ this.currentTranslate = new mxPoint(0, 0);
+};
+
+/**
+ * Specifies if the touch UI should be used (cannot detect touch in FF so always on for Windows/Linux)
+ */
+Graph.touchStyle = mxClient.IS_TOUCH || (mxClient.IS_FF && mxClient.IS_WIN) || navigator.maxTouchPoints > 0 ||
+ navigator.msMaxTouchPoints > 0 || window.urlParams == null || urlParams['touch'] == '1';
+
+/**
+ * Shortcut for capability check.
+ */
+Graph.fileSupport = window.File != null && window.FileReader != null && window.FileList != null &&
+ (window.urlParams == null || urlParams['filesupport'] != '0');
+
+/**
+ * Shortcut for capability check.
+ */
+Graph.translateDiagram = urlParams['translate-diagram'] == '1';
+
+/**
+ * Shortcut for capability check.
+ */
+Graph.diagramLanguage = (urlParams['diagram-language'] != null) ? urlParams['diagram-language'] : mxClient.language;
+
+/**
+ * Default size for line jumps.
+ */
+Graph.lineJumpsEnabled = true;
+
+/**
+ * Default size for line jumps.
+ */
+Graph.defaultJumpSize = 6;
+
+/**
+ * Specifies if the mouse wheel is used for zoom without any modifiers.
+ */
+Graph.zoomWheel = false;
+
+/**
+ * Minimum width for table columns.
+ */
+Graph.minTableColumnWidth = 20;
+
+/**
+ * Minimum height for table rows.
+ */
+Graph.minTableRowHeight = 20;
+
+/**
+ * Text for foreign object warning.
+ */
+Graph.foreignObjectWarningText = 'Text is not SVG - cannot display';
+
+/**
+ * Link for foreign object warning.
+ */
+Graph.foreignObjectWarningLink = 'https://www.drawio.com/doc/faq/svg-export-text-problems';
+
+/**
+ *
+ */
+Graph.xmlDeclaration = '';
+
+/**
+ *
+ */
+Graph.svgDoctype = '';
+
+/**
+ *
+ */
+Graph.svgFileComment = ''
+
+/**
+ * Minimum height for table rows.
+ */
+Graph.pasteStyles = ['rounded', 'shadow', 'dashed', 'dashPattern', 'fontFamily', 'fontSource', 'fontSize', 'fontColor', 'fontStyle',
+ 'align', 'verticalAlign', 'strokeColor', 'strokeWidth', 'fillColor', 'gradientColor', 'swimlaneFillColor',
+ 'textOpacity', 'gradientDirection', 'glass', 'labelBackgroundColor', 'labelBorderColor', 'opacity',
+ 'spacing', 'spacingTop', 'spacingLeft', 'spacingBottom', 'spacingRight', 'endFill', 'endArrow',
+ 'endSize', 'targetPerimeterSpacing', 'startFill', 'startArrow', 'startSize', 'sourcePerimeterSpacing',
+ 'arcSize', 'comic', 'sketch', 'fillWeight', 'hachureGap', 'hachureAngle', 'jiggle', 'disableMultiStroke',
+ 'disableMultiStrokeFill', 'fillStyle', 'curveFitting', 'simplification', 'comicStyle'];
+
+/**
+ * Whitelist for known layout names.
+ */
+Graph.layoutNames = ['mxHierarchicalLayout', 'mxCircleLayout', 'mxCompactTreeLayout',
+ 'mxEdgeLabelLayout', 'mxFastOrganicLayout', 'mxParallelEdgeLayout',
+ 'mxPartitionLayout', 'mxRadialTreeLayout', 'mxStackLayout'];
+
+/**
+ * Creates a temporary graph instance for rendering off-screen content.
+ */
+Graph.createOffscreenGraph = function(stylesheet)
+{
+ var graph = new Graph(document.createElement('div'));
+ graph.stylesheet.styles = mxUtils.clone(stylesheet.styles);
+ graph.resetViewOnRootChange = false;
+ graph.setConnectable(false);
+ graph.gridEnabled = false;
+ graph.autoScroll = false;
+ graph.setTooltips(false);
+ graph.setEnabled(false);
+
+ // Container must be in the DOM for correct HTML rendering
+ graph.container.style.visibility = 'hidden';
+ graph.container.style.position = 'absolute';
+ graph.container.style.overflow = 'hidden';
+ graph.container.style.height = '1px';
+ graph.container.style.width = '1px';
+
+ return graph;
+};
+
+/**
+ * Helper function for creating SVG data URI.
+ */
+Graph.createSvgImage = function(w, h, data, coordWidth, coordHeight)
+{
+ var tmp = unescape(encodeURIComponent(Graph.svgDoctype +
+ ''));
+
+ return new mxImage('data:image/svg+xml;base64,' + ((window.btoa) ? btoa(tmp) : Base64.encode(tmp, true)), w, h)
+};
+
+/**
+ * Helper function for creating an SVG node.
+ */
+Graph.createSvgNode = function(x, y, w, h, background)
+{
+ var svgDoc = mxUtils.createXmlDocument();
+ var root = (svgDoc.createElementNS != null) ?
+ svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') :
+ svgDoc.createElement('svg');
+
+ if (background != null)
+ {
+ if (root.style != null)
+ {
+ root.style.backgroundColor = background;
+ }
+ else
+ {
+ root.setAttribute('style', 'background-color:' + background);
+ }
+ }
+
+ if (svgDoc.createElementNS == null)
+ {
+ root.setAttribute('xmlns', mxConstants.NS_SVG);
+ root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
+ }
+ else
+ {
+ // KNOWN: Ignored in IE9-11, adds namespace for each image element instead. No workaround.
+ root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
+ }
+
+ root.setAttribute('version', '1.1');
+ root.setAttribute('width', w + 'px');
+ root.setAttribute('height', h + 'px');
+ root.setAttribute('viewBox', x + ' ' + y + ' ' + w + ' ' + h);
+ svgDoc.appendChild(root);
+
+ return root;
+};
+
+/**
+ * Helper function for creating an SVG node.
+ */
+Graph.htmlToPng = function(html, w, h, fn)
+{
+ var canvas = document.createElement('canvas');
+ canvas.width = w;
+ canvas.height = h;
+
+ var img = document.createElement('img');
+ img.onload = mxUtils.bind(this, function()
+ {
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(img, 0, 0)
+
+ fn(canvas.toDataURL());
+ });
+
+ img.src = 'data:image/svg+xml,' + encodeURIComponent('');
+};
+
+/**
+ * Removes all illegal control characters with ASCII code <32 except TAB, LF
+ * and CR.
+ */
+Graph.zapGremlins = function(text)
+{
+ var lastIndex = 0;
+ var checked = [];
+
+ for (var i = 0; i < text.length; i++)
+ {
+ var code = text.charCodeAt(i);
+
+ // Removes all control chars except TAB, LF and CR
+ if (!((code >= 32 || code == 9 || code == 10 || code == 13) &&
+ code != 0xFFFF && code != 0xFFFE))
+ {
+ checked.push(text.substring(lastIndex, i));
+ lastIndex = i + 1;
+ }
+ }
+
+ if (lastIndex > 0 && lastIndex < text.length)
+ {
+ checked.push(text.substring(lastIndex));
+ }
+
+ return (checked.length == 0) ? text : checked.join('');
+};
+
+/**
+ * Turns the given string into an array.
+ */
+Graph.stringToBytes = function(str)
+{
+ var arr = new Array(str.length);
+
+ for (var i = 0; i < str.length; i++)
+ {
+ arr[i] = str.charCodeAt(i);
+ }
+
+ return arr;
+};
+
+/**
+ * Turns the given array into a string.
+ */
+Graph.bytesToString = function(arr)
+{
+ var result = new Array(arr.length);
+
+ for (var i = 0; i < arr.length; i++)
+ {
+ result[i] = String.fromCharCode(arr[i]);
+ }
+
+ return result.join('');
+};
+
+/**
+ * Turns the given array into a string.
+ */
+Graph.base64EncodeUnicode = function(str)
+{
+ return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
+ return String.fromCharCode(parseInt(p1, 16))
+ }));
+};
+
+/**
+ * Turns the given array into a string.
+ */
+Graph.base64DecodeUnicode = function(str)
+{
+ return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
+ }).join(''));
+};
+
+/**
+ * Returns a base64 encoded version of the compressed outer XML of the given node.
+ */
+Graph.compressNode = function(node, checked)
+{
+ var xml = mxUtils.getXml(node);
+
+ return Graph.compress((checked) ? xml : Graph.zapGremlins(xml));
+};
+
+/**
+ * Returns a string for the given array buffer.
+ */
+Graph.arrayBufferToString = function(buffer)
+{
+ var binary = '';
+ var bytes = new Uint8Array(buffer);
+ var len = bytes.byteLength;
+
+ for (var i = 0; i < len; i++)
+ {
+ binary += String.fromCharCode(bytes[i]);
+ }
+
+ return binary;
+};
+
+/**
+ * Returns an array buffer for the given string.
+ */
+Graph.stringToArrayBuffer = function(data)
+{
+ return Uint8Array.from(data, function (c)
+ {
+ return c.charCodeAt(0);
+ });
+};
+
+/**
+ * Returns index of a string in an array buffer (UInt8Array)
+ */
+Graph.arrayBufferIndexOfString = function (uint8Array, str, start)
+{
+ var c0 = str.charCodeAt(0), j = 1, p = -1;
+
+ //Index of first char
+ for (var i = start || 0; i < uint8Array.byteLength; i++)
+ {
+ if (uint8Array[i] == c0)
+ {
+ p = i;
+ break;
+ }
+ }
+
+ for (var i = p + 1; p > -1 && i < uint8Array.byteLength && i < p + str.length - 1; i++)
+ {
+ if (uint8Array[i] != str.charCodeAt(j))
+ {
+ return Graph.arrayBufferIndexOfString(uint8Array, str, p + 1);
+ }
+
+ j++;
+ }
+
+ return j == str.length - 1? p : -1;
+};
+
+/**
+ * Returns a base64 encoded version of the compressed string.
+ */
+Graph.compress = function(data, deflate)
+{
+ if (data == null || data.length == 0 || typeof(pako) === 'undefined')
+ {
+ return data;
+ }
+ else
+ {
+ var tmp = (deflate) ? pako.deflate(encodeURIComponent(data)) :
+ pako.deflateRaw(encodeURIComponent(data));
+
+ return btoa(Graph.arrayBufferToString(new Uint8Array(tmp)));
+ }
+};
+
+/**
+ * Returns a decompressed version of the base64 encoded string.
+ */
+Graph.decompress = function(data, inflate, checked)
+{
+ if (data == null || data.length == 0 || typeof(pako) === 'undefined')
+ {
+ return data;
+ }
+ else
+ {
+ var tmp = Graph.stringToArrayBuffer(atob(data));
+ var inflated = decodeURIComponent((inflate) ?
+ pako.inflate(tmp, {to: 'string'}) :
+ pako.inflateRaw(tmp, {to: 'string'}));
+
+ return (checked) ? inflated : Graph.zapGremlins(inflated);
+ }
+};
+
+/**
+ * Fades the given nodes in or out.
+ */
+Graph.fadeNodes = function(nodes, start, end, done, delay)
+{
+ delay = (delay != null) ? delay : 1000;
+ Graph.setTransitionForNodes(nodes, null);
+ Graph.setOpacityForNodes(nodes, start);
+
+ window.setTimeout(function()
+ {
+ Graph.setTransitionForNodes(nodes,
+ 'all ' + delay + 'ms ease-in-out');
+ Graph.setOpacityForNodes(nodes, end);
+
+ window.setTimeout(function()
+ {
+ Graph.setTransitionForNodes(nodes, null);
+
+ if (done != null)
+ {
+ done();
+ }
+ }, delay);
+ }, 0);
+};
+
+/**
+ * Removes the elements from the map where the given function returns true.
+ */
+Graph.removeKeys = function(map, ignoreFn)
+{
+ for (var key in map)
+ {
+ if (ignoreFn(key))
+ {
+ delete map[key];
+ }
+ }
+};
+
+/**
+ * Sets the transition for the given nodes.
+ */
+Graph.setTransitionForNodes = function(nodes, transition)
+{
+ for (var i = 0; i < nodes.length; i++)
+ {
+ mxUtils.setPrefixedStyle(nodes[i].style, 'transition', transition);
+ }
+};
+
+/**
+ * Sets the opacity for the given nodes.
+ */
+Graph.setOpacityForNodes = function(nodes, opacity)
+{
+ for (var i = 0; i < nodes.length; i++)
+ {
+ nodes[i].style.opacity = opacity;
+ }
+};
+
+/**
+ * Removes formatting from pasted HTML.
+ */
+Graph.removePasteFormatting = function(elt, ignoreTabs)
+{
+ while (elt != null)
+ {
+ if (elt.firstChild != null)
+ {
+ Graph.removePasteFormatting(elt.firstChild, true);
+ }
+
+ var next = elt.nextSibling;
+
+ if (elt.nodeType == mxConstants.NODETYPE_ELEMENT && elt.style != null)
+ {
+ elt.style.whiteSpace = '';
+
+ if (elt.style.color == '#000000')
+ {
+ elt.style.color = '';
+ }
+
+ // Replaces tabs from macOS TextEdit
+ if (elt.nodeName == 'SPAN' && elt.className == 'Apple-tab-span')
+ {
+ var temp = Graph.createTabNode(4);
+ elt.parentNode.replaceChild(temp, elt);
+ elt = temp;
+ }
+
+ // Replaces paragraphs from macOS TextEdit
+ if (elt.nodeName == 'P' && elt.className == 'p1')
+ {
+ while (elt.firstChild != null)
+ {
+ elt.parentNode.insertBefore(elt.firstChild, elt);
+ }
+
+ if (next != null && next.nodeName == 'P' &&
+ next.className == 'p1')
+ {
+ elt.parentNode.insertBefore(elt.ownerDocument.
+ createElement('br'), elt);
+ }
+
+ elt.parentNode.removeChild(elt);
+ }
+
+ // Replaces tabs
+ if (!ignoreTabs && elt.innerHTML != null)
+ {
+ var tabNode = Graph.createTabNode(4);
+ elt.innerHTML = elt.innerHTML.replace(/\t/g,
+ tabNode.outerHTML);
+ }
+ }
+
+ elt = next;
+ }
+};
+
+/**
+ * Removes formatting from pasted HTML.
+ */
+Graph.createTabNode = function(spaces)
+{
+ var str = '\t';
+
+ if (spaces != null)
+ {
+ str = '';
+
+ while (spaces > 0)
+ {
+ str += '\xa0';
+ spaces--;
+ }
+ }
+
+ // LATER: Fix normalized tab after editing plain text labels
+ var tabNode = document.createElement('span');
+ tabNode.style.whiteSpace = 'pre';
+ tabNode.appendChild(document.createTextNode(str));
+
+ return tabNode;
+};
+
+/**
+ * Sanitizes the given HTML markup, allowing target attributes and
+ * data: protocol links to pages and custom actions.
+ */
+Graph.sanitizeHtml = function(value, editing)
+{
+ return Graph.domPurify(value, false);
+};
+
+/**
+ * Returns the size of the page format scaled with the page size.
+ */
+ Graph.sanitizeLink = function(href)
+ {
+ if (href == null)
+ {
+ return null;
+ }
+ else
+ {
+ var a = document.createElement('a');
+ a.setAttribute('href', href);
+ Graph.sanitizeNode(a);
+
+ return a.getAttribute('href');
+ }
+ };
+
+/**
+ * Sanitizes the given DOM node in-place.
+ */
+Graph.sanitizeNode = function(value)
+{
+ return Graph.domPurify(value, true);
+};
+
+// Allows use tag in SVG with local references only
+DOMPurify.addHook('afterSanitizeAttributes', function(node)
+{
+ if (node.nodeName == 'use' && ((node.getAttribute('xlink:href') != null &&
+ !node.getAttribute('xlink:href').startsWith('#')) ||
+ (node.getAttribute('href') != null && !node.getAttribute('href').startsWith('#'))))
+ {
+ node.remove();
+ }
+});
+
+// Workaround for removed content with empty nodes
+DOMPurify.addHook('uponSanitizeAttribute', function (node, evt)
+{
+ if (node.nodeName == 'svg' && evt.attrName == 'content')
+ {
+ evt.forceKeepAttr = true;
+ }
+
+ return node;
+});
+
+/**
+ * Sanitizes the given value.
+ */
+Graph.domPurify = function(value, inPlace)
+{
+ window.DOM_PURIFY_CONFIG.IN_PLACE = inPlace;
+
+ return DOMPurify.sanitize(value, window.DOM_PURIFY_CONFIG);
+};
+
+/**
+ * Updates the viewbox, width and height in the given SVG data URI
+ * and returns the updated data URI with all script tags and event
+ * handlers removed.
+ */
+Graph.clipSvgDataUri = function(dataUri, ignorePreserveAspect)
+{
+ // LATER Add workaround for non-default NS declarations with empty URI not allowed in IE11
+ if (!mxClient.IS_IE && !mxClient.IS_IE11 && dataUri != null &&
+ dataUri.substring(0, 26) == 'data:image/svg+xml;base64,')
+ {
+ try
+ {
+ var div = document.createElement('div');
+ div.style.position = 'absolute';
+ div.style.visibility = 'hidden';
+
+ // Adds the text and inserts into DOM for updating of size
+ var data = decodeURIComponent(escape(atob(dataUri.substring(26))));
+ var idx = data.indexOf('