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 + + '' + data + '')); + + 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('' + + '
I lick cheese
'); +}; + +/** + * 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('= 0) + { + // Strips leading XML declaration and doctypes + div.innerHTML = Graph.sanitizeHtml(data.substring(idx)); + + // Gets the size and removes from DOM + var svgs = div.getElementsByTagName('svg'); + + if (svgs.length > 0) + { + // Avoids getBBox as it ignores stroke option + if (ignorePreserveAspect || svgs[0].getAttribute('preserveAspectRatio') != null) + { + document.body.appendChild(div); + + try + { + var fx = 1; + var fy = 1; + var w = svgs[0].getAttribute('width'); + var h = svgs[0].getAttribute('height'); + + if (w != null && w.charAt(w.length - 1) != '%') + { + w = parseFloat(w); + } + else + { + w = NaN; + } + + if (h != null && h.charAt(h.length - 1) != '%') + { + h = parseFloat(h); + } + else + { + h = NaN; + } + + var vb = svgs[0].getAttribute('viewBox'); + + if (vb != null && !isNaN(w) && !isNaN(h)) + { + var tokens = vb.split(' '); + + if (vb.length >= 4) + { + fx = parseFloat(tokens[2]) / w; + fy = parseFloat(tokens[3]) / h; + } + } + + var size = svgs[0].getBBox(); + + if (size.width > 0 && size.height > 0) + { + div.getElementsByTagName('svg')[0].setAttribute('viewBox', size.x + + ' ' + size.y + ' ' + size.width + ' ' + size.height); + div.getElementsByTagName('svg')[0].setAttribute('width', size.width / fx); + div.getElementsByTagName('svg')[0].setAttribute('height', size.height / fy); + } + } + catch (e) + { + // ignore + } + finally + { + document.body.removeChild(div); + } + } + + dataUri = Editor.createSvgDataUri(mxUtils.getXml(svgs[0])); + } + } + } + catch (e) + { + // ignore + } + } + + return dataUri; +}; + +/** + * Returns the CSS font family from the given computed style. + */ +Graph.stripQuotes = function(text) +{ + if (text != null) + { + if (text.charAt(0) == '\'') + { + text = text.substring(1); + } + + if (text.charAt(text.length - 1) == '\'') + { + text = text.substring(0, text.length - 1); + } + + if (text.charAt(0) == '"') + { + text = text.substring(1); + } + + if (text.charAt(text.length - 1) == '"') + { + text = text.substring(0, text.length - 1); + } + } + + return text; +}; + +/** + * Create remove icon. + */ +Graph.createRemoveIcon = function(title, onclick) +{ + var removeLink = document.createElement('img'); + removeLink.setAttribute('src', Dialog.prototype.clearImage); + removeLink.setAttribute('title', title); + removeLink.setAttribute('width', '13'); + removeLink.setAttribute('height', '10'); + removeLink.style.marginLeft = '4px'; + removeLink.style.marginBottom = '-1px'; + removeLink.style.cursor = 'pointer'; + + mxEvent.addListener(removeLink, 'click', onclick); + + return removeLink; +}; + +/** + * Returns true if the given string is a page link. + */ +Graph.isPageLink = function(text) +{ + return text != null && text.substring(0, 13) == 'data:page/id,'; +}; + +/** + * Returns true if the given string is a page link. + */ +Graph.rewritePageLinks = function(doc) +{ + var links = doc.getElementsByTagName('a'); + + function rewriteLink(link, attrib) + { + var href = link.getAttribute(attrib); + + if (href != null && Graph.isPageLink(href)) + { + link.setAttribute(attrib, '#' + href.substring(href.indexOf(':') + 1)); + } + }; + + for (var i = 0; i < links.length; i++) + { + rewriteLink(links[i], 'href'); + rewriteLink(links[i], 'xlink:href'); + } +}; + +/** + * Returns true if the given string is a link. + * + * See https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url + */ +Graph.isLink = function(text) +{ + return text != null && Graph.linkPattern.test(text); +}; + +/** + * Regular expression for links. + */ +Graph.linkPattern = new RegExp('^(https?:\\/\\/)?'+ // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string + '(\\#[-a-z\\d_]*)?$','i'); // fragment locator + +/** + * Graph inherits from mxGraph. + */ +mxUtils.extend(Graph, mxGraph); + +/** + * Allows all values in fit. + */ +Graph.prototype.minFitScale = null; + +/** + * Allows all values in fit. + */ +Graph.prototype.maxFitScale = null; + +/** + * Sets the policy for links. Possible values are "self" to replace any framesets, + * "blank" to load the URL in and "auto" (default). + */ +Graph.prototype.linkPolicy = (urlParams['target'] == 'frame') ? 'blank' : (urlParams['target'] || 'auto'); + +/** + * Target for links that open in a new window. Default is _blank. + */ +Graph.prototype.linkTarget = (urlParams['target'] == 'frame') ? '_self' : '_blank'; + +/** + * Value to the rel attribute of links. Default is 'nofollow noopener noreferrer'. + * NOTE: There are security implications when this is changed and if noopener is removed, + * then must be overridden to allow for the opener to be set by default. + */ +Graph.prototype.linkRelation = 'nofollow noopener noreferrer'; + +/** + * Scrollbars setting is overriden in the editor, but not in the viewer. + */ +Graph.prototype.defaultScrollbars = true; + +/** + * Specifies if the page should be visible for new files. Default is true. + */ +Graph.prototype.defaultPageVisible = true; + +/** + * Specifies if the page should be visible for new files. Default is true. + */ +Graph.prototype.defaultGridEnabled = urlParams['grid'] != '0'; + +/** + * Specifies if the app should run in chromeless mode. Default is false. + * This default is only used if the contructor argument is null. + */ +Graph.prototype.lightbox = false; + +/** + * + */ +Graph.prototype.defaultPageBackgroundColor = '#ffffff'; + +/** + * + */ +Graph.prototype.diagramBackgroundColor = '#f0f0f0'; + +/** + * Whether to use diagramBackgroundColor for no page views. + */ +Graph.prototype.enableDiagramBackground = false; + +/** + * + */ +Graph.prototype.defaultPageBorderColor = '#ffffff'; + +/** + * + */ +Graph.prototype.shapeForegroundColor = '#000000'; + +/** + * + */ +Graph.prototype.shapeBackgroundColor = '#ffffff'; + +/** + * Specifies the size of the size for "tiles" to be used for a graph with + * scrollbars but no visible background page. A good value is large + * enough to reduce the number of repaints that is caused for auto- + * translation, which depends on this value, and small enough to give + * a small empty buffer around the graph. Default is 400x400. + */ +Graph.prototype.scrollTileSize = new mxRectangle(0, 0, 400, 400); + +/** + * Overrides the background color and paints a transparent background. + */ +Graph.prototype.transparentBackground = true; + +/** + * Sets global constants. + */ +Graph.prototype.selectParentAfterDelete = false; + +/** + * Sets the default target for all links in cells. + */ +Graph.prototype.defaultEdgeLength = 80; + +/** + * Enables activation of special handles on unselected cells. + */ +Graph.prototype.immediateHandling = true; + +/** + * Allows all values in fit. + */ +Graph.prototype.connectionArrowsEnabled = true; + +/** + * Specifies the regular expression for matching placeholders. + */ +Graph.prototype.placeholderPattern = new RegExp('%(date\{.*\}|[^%^\{^\}^ ^"^ \'^=^;]+)%', 'g'); + +/** + * Specifies the regular expression for matching placeholders. + */ +Graph.prototype.absoluteUrlPattern = new RegExp('^(?:[a-z]+:)?//', 'i'); + +/** + * Specifies the default name for the theme. Default is 'default'. + */ +Graph.prototype.defaultThemeName = 'default'; + +/** + * Specifies the default name for the theme. Default is 'default'. + */ +Graph.prototype.defaultThemes = {}; + +/** + * Base URL for relative links. + */ +Graph.prototype.baseUrl = (urlParams['base'] != null) ? + decodeURIComponent(urlParams['base']) : + (((window != window.top) ? document.referrer : + document.location.toString()).split('#')[0]); + +/** + * Specifies if the label should be edited after an insert. + */ +Graph.prototype.editAfterInsert = false; + +/** + * Defines the built-in properties to be ignored in tooltips. + */ +Graph.prototype.builtInProperties = ['label', 'tooltip', 'placeholders', 'placeholder']; + +/** + * Defines if the graph is part of an EditorUi. If this is false the graph can + * be used in an EditorUi instance but will not have a UI added, functions + * overridden or event handlers added. + */ +Graph.prototype.standalone = false; + +/** + * Enables move of bends/segments without selecting. + */ +Graph.prototype.enableFlowAnimation = false; + +/** + * Background color for inactive tabs. + */ +Graph.prototype.roundableShapes = ['label', 'rectangle', 'internalStorage', 'corner', + 'parallelogram', 'swimlane', 'triangle', 'trapezoid', 'ext', 'step', 'tee', 'process', + 'link', 'rhombus', 'offPageConnector', 'loopLimit', 'hexagon', 'manualInput', 'card', + 'curlyBracket', 'singleArrow', 'callout', 'doubleArrow', 'flexArrow', 'umlLifeline']; + +/** + * Installs child layout styles. + */ +Graph.prototype.init = function(container) +{ + mxGraph.prototype.init.apply(this, arguments); + + // Intercepts links with no target attribute and opens in new window + this.cellRenderer.initializeLabel = function(state, shape) + { + mxCellRenderer.prototype.initializeLabel.apply(this, arguments); + + // Checks tolerance for clicks on links + var tol = state.view.graph.tolerance; + var handleClick = true; + var first = null; + + var down = mxUtils.bind(this, function(evt) + { + handleClick = true; + first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt)); + }); + + var move = mxUtils.bind(this, function(evt) + { + handleClick = handleClick && first != null && + Math.abs(first.x - mxEvent.getClientX(evt)) < tol && + Math.abs(first.y - mxEvent.getClientY(evt)) < tol; + }); + + var up = mxUtils.bind(this, function(evt) + { + if (handleClick) + { + var elt = mxEvent.getSource(evt) + + while (elt != null && elt != shape.node) + { + if (elt.nodeName.toLowerCase() == 'a') + { + state.view.graph.labelLinkClicked(state, elt, evt); + break; + } + + elt = elt.parentNode; + } + } + }); + + mxEvent.addGestureListeners(shape.node, down, move, up); + mxEvent.addListener(shape.node, 'click', function(evt) + { + mxEvent.consume(evt); + }); + }; + + // Handles custom links in tooltips + if (this.tooltipHandler != null) + { + var tooltipHandlerInit = this.tooltipHandler.init; + + this.tooltipHandler.init = function() + { + tooltipHandlerInit.apply(this, arguments); + + if (this.div != null) + { + mxEvent.addListener(this.div, 'click', mxUtils.bind(this, function(evt) + { + var source = mxEvent.getSource(evt); + + if (source.nodeName == 'A') + { + var href = source.getAttribute('href'); + + if (href != null && this.graph.isCustomLink(href) && + (mxEvent.isTouchEvent(evt) || !mxEvent.isPopupTrigger(evt)) && + this.graph.customLinkClicked(href)) + { + mxEvent.consume(evt); + } + } + })); + } + }; + } + + + // Adds or updates CSS for flowAnimation style + this.addListener(mxEvent.SIZE, mxUtils.bind(this, function(sender, evt) + { + if (this.container != null && this.flowAnimationStyle) + { + var id = this.flowAnimationStyle.getAttribute('id'); + this.flowAnimationStyle.innerHTML = this.getFlowAnimationStyleCss(id); + } + })); + + this.initLayoutManager(); +}; + +/** + * Implements zoom and offset via CSS transforms. This is currently only used + * in read-only as there are fewer issues with the mxCellState not being scaled + * and translated. + * + * KNOWN ISSUES TO FIX: + * - Apply CSS transforms to HTML labels in IE11 + */ +(function() +{ + /** + * Uses CSS transforms for scale and translate. + */ + Graph.prototype.useCssTransforms = false; + + /** + * Contains the scale. + */ + Graph.prototype.currentScale = 1; + + /** + * Contains the offset. + */ + Graph.prototype.currentTranslate = new mxPoint(0, 0); + + /** + * Returns information about the current selection. + */ + Graph.prototype.isFillState = function(state) + { + return !this.isSpecialColor(state.style[mxConstants.STYLE_FILLCOLOR]) && + mxUtils.getValue(state.style, 'lineShape', null) != '1' && + (this.model.isVertex(state.cell) || + mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'arrow' || + mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'wire' || + mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'filledEdge' || + mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'flexArrow' || + mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'mxgraph.arrows2.wedgeArrow'); + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.isGradientState = function(state) + { + return this.isFillState(state) && mxUtils.getValue(state.style, + mxConstants.STYLE_SHAPE, null) != 'wire'; + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.isStrokeState = function(state) + { + return true; + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.isSpecialColor = function(color) + { + return mxUtils.indexOf([mxConstants.STYLE_STROKECOLOR, + mxConstants.STYLE_FILLCOLOR, 'inherit', 'swimlane', + 'indicated'], color) >= 0; + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.isGlassState = function(state) + { + var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null); + + return (shape == 'label' || shape == 'rectangle' || shape == 'internalStorage' || + shape == 'ext' || shape == 'umlLifeline' || shape == 'swimlane' || + shape == 'process'); + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.isRoundedState = function(state) + { + return (state.shape != null) ? state.shape.isRoundable() : + mxUtils.indexOf(this.roundableShapes, mxUtils.getValue(state.style, + mxConstants.STYLE_SHAPE, null)) >= 0; + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.isLineJumpState = function(state) + { + var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null); + var curved = mxUtils.getValue(state.style, mxConstants.STYLE_CURVED, false); + + return !curved && (shape == 'connector' || shape == 'filledEdge' || shape == 'wire'); + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.isAutoSizeState = function(state) + { + return mxUtils.getValue(state.style, mxConstants.STYLE_AUTOSIZE, null) == '1'; + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.isImageState = function(state) + { + return mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE, null) != null; + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.isShadowState = function(state) + { + var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null); + + return (shape != 'image'); + }; + + /** + * + */ + Graph.prototype.getVerticesAndEdges = function(vertices, edges) + { + vertices = (vertices != null) ? vertices : true; + edges = (edges != null) ? edges : true; + var model = this.model; + + return model.filterDescendants(function(cell) + { + return (vertices && model.isVertex(cell)) || (edges && model.isEdge(cell)); + }, model.getRoot()); + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.applyNewEdgeStyle = function(source, edges, dir) + { + var style = this.getCellStyle(source); + var temp = style['newEdgeStyle']; + + if (temp != null) + { + this.model.beginUpdate(); + try + { + var styles = JSON.parse(temp); + + for (var key in styles) + { + this.setCellStyles(key, styles[key], edges); + + // Sets elbow direction + if (key == 'edgeStyle' && styles[key] == 'elbowEdgeStyle' && dir != null) + { + this.setCellStyles('elbow', (dir == mxConstants.DIRECTION_SOUTH || + dir == mxConstants.DIRECTION_NOTH) ? 'vertical' : 'horizontal', + edges); + } + } + } + finally + { + this.model.endUpdate(); + } + } + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.getCommonStyle = function(cells) + { + var style = {}; + + for (var i = 0; i < cells.length; i++) + { + var state = this.view.getState(cells[i]); + + if (state != null) + { + this.mergeStyle(state.style, style, i == 0); + } + } + + return style; + }; + + /** + * Returns information about the current selection. + */ + Graph.prototype.mergeStyle = function(style, into, initial) + { + if (style != null) + { + var keys = {}; + + for (var key in style) + { + var value = style[key]; + + if (value != null) + { + keys[key] = true; + + if (into[key] == null && initial) + { + into[key] = value; + } + else if (into[key] != value) + { + delete into[key]; + } + } + } + + for (var key in into) + { + if (!keys[key]) + { + delete into[key]; + } + } + } + }; + + /** + * Returns the cell for editing the given cell. + */ + Graph.prototype.getStartEditingCell = function(cell, trigger) + { + // Redirect editing for tables + var style = this.getCellStyle(cell); + var size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, 0)); + + if (this.isTable(cell) && (!this.isSwimlane(cell) || + size == 0) && this.getLabel(cell) == '' && + this.model.getChildCount(cell) > 0) + { + cell = this.model.getChildAt(cell, 0); + + style = this.getCellStyle(cell); + size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, 0)); + } + + // Redirect editing for table rows + if (this.isTableRow(cell) && (!this.isSwimlane(cell) || + size == 0) && this.getLabel(cell) == '' && + this.model.getChildCount(cell) > 0) + { + for (var i = 0; i < this.model.getChildCount(cell); i++) + { + var temp = this.model.getChildAt(cell, i); + + if (this.isCellEditable(temp)) + { + cell = temp; + break; + } + } + } + + return cell; + }; + + /** + * Returns the style of the given cell as an object. + */ + Graph.prototype.copyStyle = function(cell) + { + return this.getCellStyle(cell, false); + }; + + /** + * Returns true if fast zoom preview should be used. + */ + Graph.prototype.pasteStyle = function(style, cells, keys) + { + keys = (keys != null) ? keys : Graph.pasteStyles; + + Graph.removeKeys(style, function(key) + { + return mxUtils.indexOf(keys, key) < 0; + }); + + this.updateCellStyles(style, cells); + }; + + /** + * Removes implicit styles from cell styles so that dark mode works using the + * default values from the stylesheet. + */ + Graph.prototype.updateCellStyles = function(style, cells) + { + this.model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + if (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i])) + { + var cellStyle = this.getCellStyle(cells[i], false); + var perimeter = cellStyle[mxConstants.STYLE_PERIMETER]; + var restorePerimeter = false; + + for (var key in style) + { + var value = style[key]; + + if (cellStyle[key] != value) + { + // Handles paste of shape to UML lifeline + if (key == mxConstants.STYLE_SHAPE && + cellStyle[key] == 'umlLifeline' && + value != 'umlLifeline') + { + restorePerimeter = true; + key = 'participant'; + } + + this.setCellStyles(key, value, [cells[i]]); + } + } + + if (restorePerimeter) + { + this.setCellStyles(mxConstants.STYLE_PERIMETER, perimeter, [cells[i]]); + } + } + } + } + finally + { + this.model.endUpdate(); + } + }; + + /** + * Returns true if fast zoom preview should be used. + */ + Graph.prototype.isFastZoomEnabled = function() + { + return urlParams['zoom'] != 'nocss' && !mxClient.NO_FO && !mxClient.IS_EDGE && + !this.useCssTransforms && (this.isCssTransformsSupported() || mxClient.IS_IOS); + }; + + /** + * Only foreignObject supported for now (no IE11). Safari disabled as it ignores + * overflow visible on foreignObject in negative space (lightbox and viewer). + * Check the following test case on page 1 before enabling this in production: + * https://devhost.jgraph.com/git/drawio/etc/embed/sf-math-fo-clipping.html?dev=1 + */ + Graph.prototype.isCssTransformsSupported = function() + { + return this.dialect == mxConstants.DIALECT_SVG && !mxClient.NO_FO && + (!this.lightbox || !mxClient.IS_SF); + }; + + /** + * Function: getCellAt + * + * Needs to modify original method for recursive call. + */ + Graph.prototype.getCellAt = function(x, y, parent, vertices, edges, ignoreFn) + { + if (this.useCssTransforms) + { + x = x / this.currentScale - this.currentTranslate.x; + y = y / this.currentScale - this.currentTranslate.y; + } + + return this.getScaledCellAt.apply(this, arguments); + }; + + /** + * Function: getScaledCellAt + * + * Overridden for recursion. + */ + Graph.prototype.getScaledCellAt = function(x, y, parent, vertices, edges, ignoreFn) + { + vertices = (vertices != null) ? vertices : true; + edges = (edges != null) ? edges : true; + + if (parent == null) + { + parent = this.getCurrentRoot(); + + if (parent == null) + { + parent = this.getModel().getRoot(); + } + } + + if (parent != null) + { + var childCount = this.model.getChildCount(parent); + + for (var i = childCount - 1; i >= 0; i--) + { + var cell = this.model.getChildAt(parent, i); + var result = this.getScaledCellAt(x, y, cell, vertices, edges, ignoreFn); + + if (result != null) + { + return result; + } + else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) || + vertices && this.model.isVertex(cell))) + { + var state = this.view.getState(cell); + + if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) && + this.intersects(state, x, y)) + { + return cell; + } + } + } + } + + return null; + }; + + /** + * Returns if the child cells of the given vertex cell state should be resized. + */ + Graph.prototype.isRecursiveVertexResize = function(state) + { + return !this.isSwimlane(state.cell) && this.model.getChildCount(state.cell) > 0 && + !this.isCellCollapsed(state.cell) && mxUtils.getValue(state.style, 'recursiveResize', '1') == '1' && + mxUtils.getValue(state.style, 'childLayout', null) == null; + } + + /** + * Returns the first parent with an absolute or no geometry. + */ + Graph.prototype.getAbsoluteParent = function(cell) + { + var result = cell; + var geo = this.getCellGeometry(result); + + while (geo != null && geo.relative) + { + result = this.getModel().getParent(result); + geo = this.getCellGeometry(result); + } + + return result; + }; + + /** + * Returns the first parent that is not a part. + */ + Graph.prototype.isPart = function(cell) + { + return mxUtils.getValue(this.getCurrentCellStyle(cell), 'part', '0') == '1' || + this.isTableCell(cell) || this.isTableRow(cell); + }; + + /** + * Returns the first parent that is not a part. + */ + Graph.prototype.getCompositeParents = function(cells) + { + var lookup = new mxDictionary(); + var newCells = []; + + for (var i = 0; i < cells.length; i++) + { + var cell = this.getCompositeParent(cells[i]); + + if (this.isTableCell(cell)) + { + cell = this.graph.model.getParent(cell); + } + + if (this.isTableRow(cell)) + { + cell = this.graph.model.getParent(cell); + } + + if (cell != null && !lookup.get(cell)) + { + lookup.put(cell, true); + newCells.push(cell); + } + } + + return newCells; + }; + + /** + * Returns the given terminal that is not relative, an edge or a part. + */ + Graph.prototype.getReferenceTerminal = function(terminal) + { + if (terminal != null) + { + var geo = this.getCellGeometry(terminal); + + if (geo != null && geo.relative) + { + terminal = this.model.getParent(terminal); + } + } + + if (terminal != null && this.model.isEdge(terminal)) + { + terminal = this.model.getParent(terminal); + } + + if (terminal != null) + { + terminal = this.getCompositeParent(terminal); + } + + return terminal; + }; + + /** + * Returns the first parent that is not a part. + */ + Graph.prototype.getCompositeParent = function(cell) + { + while (this.isPart(cell)) + { + var temp = this.model.getParent(cell); + + if (!this.model.isVertex(temp)) + { + break; + } + + cell = temp; + } + + return cell; + }; + + /** + * Returns the selection cells where the given function returns false. + */ + Graph.prototype.filterSelectionCells = function(ignoreFn) + { + var cells = this.getSelectionCells(); + + if (ignoreFn != null) + { + var temp = []; + + for (var i = 0; i < cells.length; i++) + { + if (!ignoreFn(cells[i])) + { + temp.push(cells[i]); + } + } + + cells = temp; + } + + return cells; + }; + + /** + * Overrides scrollRectToVisible to fix ignored transform. + */ + var graphScrollRectToVisible = mxGraph.prototype.scrollRectToVisible; + Graph.prototype.scrollRectToVisible = function(r) + { + if (this.useCssTransforms) + { + var s = this.currentScale; + var t = this.currentTranslate; + r = new mxRectangle((r.x + 2 * t.x) * s - t.x, + (r.y + 2 * t.y) * s - t.y, + r.width * s, r.height * s); + } + + graphScrollRectToVisible.apply(this, arguments); + }; + + /** + * Function: repaint + * + * Updates the highlight after a change of the model or view. + */ + mxCellHighlight.prototype.getStrokeWidth = function(state) + { + var s = this.strokeWidth; + + if (this.graph.useCssTransforms) + { + s /= this.graph.currentScale; + } + + return s; + }; + + /** + * Function: getGraphBounds + * + * Overrides getGraphBounds to use bounding box from SVG. + */ + mxGraphView.prototype.getGraphBounds = function() + { + var b = this.graphBounds; + + if (this.graph.useCssTransforms) + { + var t = this.graph.currentTranslate; + var s = this.graph.currentScale; + + b = new mxRectangle( + (b.x + t.x) * s, (b.y + t.y) * s, + b.width * s, b.height * s); + } + + return b; + }; + + /** + * Overrides to bypass full cell tree validation. + * TODO: Check if this improves performance + */ + mxGraphView.prototype.viewStateChanged = function() + { + if (this.graph.useCssTransforms) + { + this.validate(); + this.graph.sizeDidChange(); + } + else + { + this.revalidate(); + this.graph.sizeDidChange(); + } + }; + + /** + * Overrides validate to normalize validation view state and pass + * current state to CSS transform. + */ + var graphViewValidate = mxGraphView.prototype.validate; + mxGraphView.prototype.validate = function(cell) + { + if (this.graph.useCssTransforms) + { + this.graph.currentScale = this.scale; + this.graph.currentTranslate.x = this.translate.x; + this.graph.currentTranslate.y = this.translate.y; + + this.scale = 1; + this.translate.x = 0; + this.translate.y = 0; + } + + graphViewValidate.apply(this, arguments); + + if (this.graph.useCssTransforms) + { + this.graph.updateCssTransform(); + + this.scale = this.graph.currentScale; + this.translate.x = this.graph.currentTranslate.x; + this.translate.y = this.graph.currentTranslate.y; + } + }; + + /** + * Overrides function to exclude table cells and rows from groups. + */ + var graphGetCellsForGroup = mxGraph.prototype.getCellsForGroup; + Graph.prototype.getCellsForGroup = function(cells) + { + cells = graphGetCellsForGroup.apply(this, arguments); + var result = []; + + // Filters selection cells with the same parent + for (var i = 0; i < cells.length; i++) + { + if (!this.isTableRow(cells[i]) && + !this.isTableCell(cells[i])) + { + result.push(cells[i]); + } + } + + return result; + }; + + /** + * Overrides function to exclude tables, rows and cells from ungrouping. + */ + var graphGetCellsForUngroup = mxGraph.prototype.getCellsForUngroup; + Graph.prototype.getCellsForUngroup = function(cells) + { + cells = graphGetCellsForUngroup.apply(this, arguments); + var result = []; + + // Filters selection cells with the same parent + for (var i = 0; i < cells.length; i++) + { + if (!this.isTable(cells[i]) && + !this.isTableRow(cells[i]) && + !this.isTableCell(cells[i])) + { + result.push(cells[i]); + } + } + + return result; + }; + + /** + * Function: updateCssTransform + * + * Zooms out of the graph by . + */ + Graph.prototype.updateCssTransform = function() + { + var temp = this.view.getDrawPane(); + + if (temp != null) + { + var g = temp.parentNode; + + if (!this.useCssTransforms) + { + g.removeAttribute('transformOrigin'); + g.removeAttribute('transform'); + } + else + { + var prev = g.getAttribute('transform'); + g.setAttribute('transformOrigin', '0 0'); + var s = Math.round(this.currentScale * 100) / 100; + var dx = Math.round(this.currentTranslate.x * 100) / 100; + var dy = Math.round(this.currentTranslate.y * 100) / 100; + g.setAttribute('transform', 'scale(' + s + ',' + s + ')' + + 'translate(' + dx + ',' + dy + ')'); + + // Applies workarounds only if translate has changed + if (prev != g.getAttribute('transform')) + { + this.fireEvent(new mxEventObject('cssTransformChanged'), + 'transform', g.getAttribute('transform')); + } + } + } + }; + + var graphViewValidateBackgroundPage = mxGraphView.prototype.validateBackgroundPage; + mxGraphView.prototype.validateBackgroundPage = function() + { + var useCssTranforms = this.graph.useCssTransforms, scale = this.scale, + translate = this.translate; + + if (useCssTranforms) + { + this.scale = this.graph.currentScale; + this.translate = this.graph.currentTranslate; + } + + graphViewValidateBackgroundPage.apply(this, arguments); + + if (useCssTranforms) + { + this.scale = scale; + this.translate = translate; + } + }; + + var graphUpdatePageBreaks = mxGraph.prototype.updatePageBreaks; + mxGraph.prototype.updatePageBreaks = function(visible, width, height) + { + var useCssTranforms = this.useCssTransforms, scale = this.view.scale, + translate = this.view.translate; + + if (useCssTranforms) + { + this.view.scale = 1; + this.view.translate = new mxPoint(0, 0); + this.useCssTransforms = false; + } + + graphUpdatePageBreaks.apply(this, arguments); + + if (useCssTranforms) + { + this.view.scale = scale; + this.view.translate = translate; + this.useCssTransforms = true; + } + }; +})(); + +/** + * Sets the XML node for the current diagram. + */ +Graph.prototype.isLightboxView = function() +{ + return this.lightbox; +}; + +/** + * Sets the XML node for the current diagram. + */ +Graph.prototype.isViewer = function() +{ + return false; +}; + +/** + * Installs automatic layout via styles + */ +Graph.prototype.labelLinkClicked = function(state, elt, evt) +{ + var href = elt.getAttribute('href'); + + // Blocks and removes unsafe links in labels + if (href != Graph.sanitizeLink(href)) + { + Graph.sanitizeNode(elt); + } + + if (href != null && !this.isCustomLink(href) && ((mxEvent.isLeftMouseButton(evt) && + !mxEvent.isPopupTrigger(evt)) || mxEvent.isTouchEvent(evt))) + { + if (!this.isEnabled() || this.isCellLocked(state.cell)) + { + var target = this.isBlankLink(href) ? this.linkTarget : '_top'; + this.openLink(this.getAbsoluteUrl(href), target); + } + + mxEvent.consume(evt); + } +}; + +/** + * Returns the size of the page format scaled with the page size. + */ +Graph.prototype.openLink = function(href, target, allowOpener) +{ + var result = window; + + try + { + href = Graph.sanitizeLink(href); + + if (href != null) + { + // Workaround for blocking in same iframe + if (target == '_self' && window != window.top) + { + window.location.href = href; + } + else + { + // Avoids page reload for anchors (workaround for IE but used everywhere) + if (href.substring(0, this.baseUrl.length) == this.baseUrl && + href.charAt(this.baseUrl.length) == '#' && + target == '_top' && window == window.top) + { + var hash = href.split('#')[1]; + + // Forces navigation if on same hash + if (window.location.hash == '#' + hash) + { + window.location.hash = ''; + } + + window.location.hash = hash; + } + else + { + result = window.open(href, (target != null) ? + target : '_blank', (!allowOpener) ? + 'noopener,noreferrer' : null); + + if (result != null && !allowOpener) + { + result.opener = null; + } + } + } + } + } + catch (e) + { + // ignores permission denied + } + + return result; +}; + +/** + * Adds support for page links. + */ +Graph.prototype.getLinkTitle = function(href) +{ + return href.substring(href.lastIndexOf('/') + 1); +}; + +/** + * Adds support for page links. + */ +Graph.prototype.isCustomLink = function(href) +{ + return href.substring(0, 5) == 'data:'; +}; + +/** + * Adds support for page links. + */ +Graph.prototype.customLinkClicked = function(link) +{ + return false; +}; + +/** + * Returns true if the given href references an external protocol that + * should never open in a new window. Default returns true for mailto. + */ +Graph.prototype.isExternalProtocol = function(href) +{ + return href.substring(0, 7) === 'mailto:'; +}; + +/** + * Hook for links to open in same window. Default returns true for anchors, + * links to same domain or if target == 'self' in the config. + */ +Graph.prototype.isBlankLink = function(href) +{ + return !this.isExternalProtocol(href) && + (this.linkPolicy === 'blank' || + (this.linkPolicy !== 'self' && + !this.isRelativeUrl(href) && + href.substring(0, this.domainUrl.length) !== this.domainUrl)); +}; + +/** + * + */ +Graph.prototype.isRelativeUrl = function(url) +{ + return url != null && !this.absoluteUrlPattern.test(url) && + url.substring(0, 5) !== 'data:' && + !this.isExternalProtocol(url); +}; + +/** + * + */ +Graph.prototype.getAbsoluteUrl = function(url) +{ + if (url != null && this.isRelativeUrl(url)) + { + if (url.charAt(0) == '#') + { + url = this.baseUrl + url; + } + else if (url.charAt(0) == '/') + { + url = this.domainUrl + url; + } + else + { + url = this.domainPathUrl + url; + } + } + + return url; +}; + +/** + * Installs automatic layout via styles + */ +Graph.prototype.initLayoutManager = function() +{ + this.layoutManager = new mxLayoutManager(this); + + this.layoutManager.hasLayout = function(cell) + { + return this.graph.getCellStyle(cell)['childLayout'] != null; + }; + + this.layoutManager.getLayout = function(cell, eventName) + { + var parent = this.graph.model.getParent(cell); + + // Executes layouts from top to bottom except for nested layouts where + // child layouts are executed before and after the parent layout runs + // in case the layout changes the size of the child cell + if (!this.graph.isCellCollapsed(cell) && (eventName != mxEvent.BEGIN_UPDATE || + this.hasLayout(parent, eventName))) + { + var style = this.graph.getCellStyle(cell); + + if (style['childLayout'] == 'stackLayout') + { + var stackLayout = new mxStackLayout(this.graph, true); + stackLayout.resizeParentMax = mxUtils.getValue(style, 'resizeParentMax', '1') == '1'; + stackLayout.horizontal = mxUtils.getValue(style, 'horizontalStack', '1') == '1'; + stackLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1'; + stackLayout.resizeLast = mxUtils.getValue(style, 'resizeLast', '0') == '1'; + stackLayout.spacing = style['stackSpacing'] || stackLayout.spacing; + stackLayout.border = style['stackBorder'] || stackLayout.border; + stackLayout.marginLeft = style['marginLeft'] || 0; + stackLayout.marginRight = style['marginRight'] || 0; + stackLayout.marginTop = style['marginTop'] || 0; + stackLayout.marginBottom = style['marginBottom'] || 0; + stackLayout.allowGaps = style['allowGaps'] || 0; + stackLayout.fill = true; + + if (stackLayout.allowGaps) + { + stackLayout.gridSize = parseFloat(mxUtils.getValue(style, 'stackUnitSize', 20)); + } + + return stackLayout; + } + else if (style['childLayout'] == 'treeLayout') + { + var treeLayout = new mxCompactTreeLayout(this.graph); + treeLayout.horizontal = mxUtils.getValue(style, 'horizontalTree', '1') == '1'; + treeLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1'; + treeLayout.groupPadding = mxUtils.getValue(style, 'parentPadding', 20); + treeLayout.levelDistance = mxUtils.getValue(style, 'treeLevelDistance', 30); + treeLayout.maintainParentLocation = true; + treeLayout.edgeRouting = false; + treeLayout.resetEdges = false; + + return treeLayout; + } + else if (style['childLayout'] == 'flowLayout') + { + var flowLayout = new mxHierarchicalLayout(this.graph, mxUtils.getValue(style, + 'flowOrientation', mxConstants.DIRECTION_EAST)); + flowLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1'; + flowLayout.parentBorder = mxUtils.getValue(style, 'parentPadding', 20); + flowLayout.maintainParentLocation = true; + + // Special undocumented styles for changing the hierarchical + flowLayout.intraCellSpacing = mxUtils.getValue(style, 'intraCellSpacing', + mxHierarchicalLayout.prototype.intraCellSpacing); + flowLayout.interRankCellSpacing = mxUtils.getValue(style, 'interRankCellSpacing', + mxHierarchicalLayout.prototype.interRankCellSpacing); + flowLayout.interHierarchySpacing = mxUtils.getValue(style, 'interHierarchySpacing', + mxHierarchicalLayout.prototype.interHierarchySpacing); + flowLayout.parallelEdgeSpacing = mxUtils.getValue(style, 'parallelEdgeSpacing', + mxHierarchicalLayout.prototype.parallelEdgeSpacing); + + return flowLayout; + } + else if (style['childLayout'] == 'circleLayout') + { + return new mxCircleLayout(this.graph); + } + else if (style['childLayout'] == 'organicLayout') + { + return new mxFastOrganicLayout(this.graph); + } + else if (style['childLayout'] == 'tableLayout') + { + return new TableLayout(this.graph); + } + else if (style['childLayout'] != null && style['childLayout'].charAt(0) == '[') + { + try + { + return new mxCompositeLayout(this.graph, + this.graph.createLayouts(JSON.parse( + style['childLayout']))); + } + catch (e) + { + if (window.console != null) + { + console.error(e); + } + } + } + } + + return null; + }; +}; + +/** + * Creates an array of graph layouts from the given array of the form [{layout: name, config: obj}, ...] + * where name is the layout constructor name and config contains the properties of the layout instance. + */ +Graph.prototype.createLayouts = function(list) +{ + var layouts = []; + + for (var i = 0; i < list.length; i++) + { + if (mxUtils.indexOf(Graph.layoutNames, list[i].layout) >= 0) + { + // Handles special case of branch optimizer in orgchart + var layout = (list[i].layout == 'mxOrgChartLayout' && list[i].config != null) ? + new window[list[i].layout](this, list[i].config['branchOptimizer']) : + new window[list[i].layout](this); + + if (list[i].config != null) + { + for (var key in list[i].config) + { + // Ignores branch optimizer in orgchart (handled above) + if (list[i].layout != 'mxOrgChartLayout' || + key != 'branchOptimizer') + { + layout[key] = list[i].config[key]; + } + } + } + + layouts.push(layout); + } + else + { + throw Error(mxResources.get('invalidCallFnNotFound', [list[i].layout])); + } + } + + return layouts; +}; + +/** + * Returns the metadata of the given cells as a JSON object. + */ +Graph.prototype.getDataForCells = function(cells) +{ + var result = []; + + for (var i = 0; i < cells.length; i++) + { + var attrs = (cells[i].value != null) ? cells[i].value.attributes : null; + var row = {}; + row.id = cells[i].id; + + if (attrs != null) + { + for (var j = 0; j < attrs.length; j++) + { + row[attrs[j].nodeName] = attrs[j].nodeValue; + } + } + else + { + row.label = this.convertValueToString(cells[i]); + } + + result.push(row); + } + + return result; +}; + +/** + * Returns the DOM nodes for the given cells. + */ +Graph.prototype.getNodesForCells = function(cells) +{ + var nodes = []; + + for (var i = 0; i < cells.length; i++) + { + var state = this.view.getState(cells[i]); + + if (state != null) + { + var shapes = this.cellRenderer.getShapesForState(state); + + for (var j = 0; j < shapes.length; j++) + { + if (shapes[j] != null && shapes[j].node != null) + { + nodes.push(shapes[j].node); + } + } + + // Adds folding icon + if (state.control != null && state.control.node != null) + { + nodes.push(state.control.node); + } + } + } + + return nodes; +}; + +/** + * Creates animations for the given cells. + */ + Graph.prototype.createWipeAnimations = function(cells, wipeIn) + { + var animations = []; + + for (var i = 0; i < cells.length; i++) + { + var state = this.view.getState(cells[i]); + + if (state != null && state.shape != null) + { + // TODO: include descendants + if (this.model.isEdge(state.cell) && + state.absolutePoints != null && + state.absolutePoints.length > 1) + { + animations.push(this.createEdgeWipeAnimation(state, wipeIn)); + } + else if (this.model.isVertex(state.cell) && + state.shape.bounds != null) + { + animations.push(this.createVertexWipeAnimation(state, wipeIn)); + } + } + } + + return animations; +}; + +/** + * Creates an object to show the given edge cell state. + */ +Graph.prototype.createEdgeWipeAnimation = function(state, wipeIn) +{ + var pts = state.absolutePoints.slice(); + var segs = state.segments; + var total = state.length; + var n = pts.length; + + return { + execute: mxUtils.bind(this, function(step, steps) + { + if (state.shape != null) + { + var pts2 = [pts[0]]; + var f = step / steps; + + if (!wipeIn) + { + f = 1 - f; + } + + var dist = total * f; + + for (var i = 1; i < n; i++) + { + if (dist <= segs[i - 1]) + { + pts2.push(new mxPoint(pts[i - 1].x + (pts[i].x - pts[i - 1].x) * dist / segs[i - 1], + pts[i - 1].y + (pts[i].y - pts[i - 1].y) * dist / segs[i - 1])); + + break; + } + else + { + dist -= segs[i - 1]; + pts2.push(pts[i]); + } + } + + state.shape.points = pts2; + state.shape.redraw(); + + if (step == 0) + { + Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), 1); + } + + if (state.text != null && state.text.node != null) + { + state.text.node.style.opacity = f; + } + } + }), + stop: mxUtils.bind(this, function() + { + if (state.shape != null) + { + state.shape.points = pts; + state.shape.redraw(); + + if (state.text != null && state.text.node != null) + { + state.text.node.style.opacity = '' + } + + Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), (wipeIn) ? 1 : 0); + } + }) + }; +}; + + /** + * Creates an object to show the given vertex cell state. + */ +Graph.prototype.createVertexWipeAnimation = function(state, wipeIn) +{ + var bds = new mxRectangle.fromRectangle(state.shape.bounds); + + return { + execute: mxUtils.bind(this, function(step, steps) + { + if (state.shape != null) + { + var f = step / steps; + + if (!wipeIn) + { + f = 1 - f; + } + + state.shape.bounds = new mxRectangle(bds.x, bds.y, bds.width * f, bds.height); + state.shape.redraw(); + + if (step == 0) + { + Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), 1); + } + + if (state.text != null && state.text.node != null) + { + state.text.node.style.opacity = f; + } + } + }), + stop: mxUtils.bind(this, function() + { + if (state.shape != null) + { + state.shape.bounds = bds; + state.shape.redraw(); + + if (state.text != null && state.text.node != null) + { + state.text.node.style.opacity = '' + } + + Graph.setOpacityForNodes(this.getNodesForCells([state.cell]), (wipeIn) ? 1 : 0); + } + }) + }; +}; + +/** + * Runs the animations for the given cells. + */ + Graph.prototype.executeAnimations = function(animations, done, steps, delay) + { + steps = (steps != null) ? steps : 30; + delay = (delay != null) ? delay : 30; + var thread = null; + var step = 0; + + var animate = mxUtils.bind(this, function() + { + if (step == steps || this.stoppingCustomActions) + { + window.clearInterval(thread); + + for (var i = 0; i < animations.length; i++) + { + animations[i].stop(); + } + + if (done != null) + { + done(); + } + } + else + { + for (var i = 0; i < animations.length; i++) + { + animations[i].execute(step, steps); + } + } + + step++; + }); + + thread = window.setInterval(animate, delay); + animate(); +}; + +/** + * Returns the size of the page format scaled with the page size. + */ +Graph.prototype.getPageSize = function() +{ + return (this.pageVisible) ? new mxRectangle(0, 0, this.pageFormat.width * this.pageScale, + this.pageFormat.height * this.pageScale) : this.scrollTileSize; +}; + +/** + * Returns a rectangle describing the position and count of the + * background pages, where x and y are the position of the top, + * left page and width and height are the vertical and horizontal + * page count. + */ +Graph.prototype.getPageLayout = function(bounds, tr, s) +{ + bounds = (bounds != null) ? bounds : this.getGraphBounds(); + tr = (tr != null) ? tr : this.view.translate; + s = (s != null) ? s : this.view.scale; + var size = this.getPageSize(); + + if (bounds.width == 0 || bounds.height == 0) + { + return new mxRectangle(0, 0, 1, 1); + } + else + { + var x0 = Math.floor(Math.ceil(bounds.x / s - tr.x) / size.width); + var y0 = Math.floor(Math.ceil(bounds.y / s - tr.y) / size.height); + var w0 = Math.ceil((Math.floor((bounds.x + bounds.width) / + s) - tr.x) / size.width) - x0; + var h0 = Math.ceil((Math.floor((bounds.y + bounds.height) / + s) - tr.y) / size.height) - y0; + + return new mxRectangle(x0, y0, w0, h0); + } +}; + +/** + * Returns the default view translation for the given page layout. + */ +Graph.prototype.getDefaultTranslate = function(pageLayout) +{ + var pad = this.getPagePadding(); + var size = this.getPageSize(); + + return new mxPoint(pad.x - pageLayout.x * size.width, + pad.y - pageLayout.y * size.height); +}; + +/** + * Updates the minimum graph size + */ +Graph.prototype.updateMinimumSize = function() +{ + var pageLayout = this.getPageLayout(); + var pad = this.getPagePadding(); + var size = this.getPageSize(); + + var minw = Math.ceil(2 * pad.x + pageLayout.width * size.width); + var minh = Math.ceil(2 * pad.y + pageLayout.height * size.height); + + if (this.minimumGraphSize == null || + this.minimumGraphSize.width != minw || + this.minimumGraphSize.height != minh) + { + this.minimumGraphSize = new mxRectangle(0, 0, minw, minh); + } +}; + +/** + * Sanitizes the given HTML markup. + */ +Graph.prototype.sanitizeHtml = function(value, editing) +{ + return Graph.sanitizeHtml(value, editing); +}; + +/** + * Revalidates all cells with placeholders in the current graph model. + */ +Graph.prototype.updatePlaceholders = function() +{ + var model = this.model; + var validate = false; + + for (var key in this.model.cells) + { + var cell = this.model.cells[key]; + + if (this.isReplacePlaceholders(cell)) + { + this.view.invalidate(cell, false, false); + validate = true; + } + } + + if (validate) + { + this.view.validate(); + } +}; + +/** + * Adds support for placeholders in labels. + */ +Graph.prototype.isReplacePlaceholders = function(cell) +{ + return cell.value != null && typeof(cell.value) == 'object' && + cell.value.getAttribute('placeholders') == '1'; +}; + +/** + * Returns true if the given mouse wheel event should be used for zooming. This + * is invoked if no dialogs are showing and returns true with Alt or Control + * (or cmd in macOS only) is pressed. + */ +Graph.prototype.isZoomWheelEvent = function(evt) +{ + return (Graph.zoomWheel && !mxEvent.isShiftDown(evt) && !mxEvent.isMetaDown(evt) && + !mxEvent.isAltDown(evt) && (!mxEvent.isControlDown(evt) || mxClient.IS_MAC)) || + (!Graph.zoomWheel && (mxEvent.isAltDown(evt) || mxEvent.isControlDown(evt))); +}; + +/** + * Returns true if the given scroll wheel event should be used for scrolling. + */ +Graph.prototype.isScrollWheelEvent = function(evt) +{ + return !this.isZoomWheelEvent(evt); +}; + +/** + * Adds Alt+click to select cells behind cells (Shift+Click on Chrome OS). + */ +Graph.prototype.isTransparentClickEvent = function(evt) +{ + return mxEvent.isAltDown(evt) || (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt)); +}; + +/** + * Adds ctrl+shift+connect to disable connections. + */ +Graph.prototype.isIgnoreTerminalEvent = function(evt) +{ + return mxEvent.isAltDown(evt) && !mxEvent.isShiftDown(evt) && + !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt); +}; + +/** + * Returns true if the given edge should be ignored. + */ +Graph.prototype.isEdgeIgnored = function(cell) +{ + var result = false; + + if (cell != null) + { + var style = this.getCurrentCellStyle(cell); + + result = mxUtils.getValue(style, 'ignoreEdge', '0') == '1'; + } + + return result; +}; + +/** + * Adds support for placeholders in labels. + */ +Graph.prototype.isSplitTarget = function(target, cells, evt) +{ + return !this.model.isEdge(cells[0]) && + !mxEvent.isAltDown(evt) && !mxEvent.isShiftDown(evt) && + mxGraph.prototype.isSplitTarget.apply(this, arguments); +}; + +/** + * Adds support for placeholders in labels. + */ +Graph.prototype.getLabel = function(cell) +{ + var result = mxGraph.prototype.getLabel.apply(this, arguments); + + if (result != null && this.isReplacePlaceholders(cell) && cell.getAttribute('placeholder') == null) + { + result = this.replacePlaceholders(cell, result); + } + + return result; +}; + +/** + * Adds labelMovable style. + */ +Graph.prototype.isLabelMovable = function(cell) +{ + var style = this.getCurrentCellStyle(cell); + + return !this.isCellLocked(cell) && + ((this.model.isEdge(cell) && this.edgeLabelsMovable) || + (this.model.isVertex(cell) && (this.vertexLabelsMovable || + mxUtils.getValue(style, 'labelMovable', '0') == '1'))); +}; + +/** + * Adds event if grid size is changed. + */ +Graph.prototype.setGridSize = function(value) +{ + this.gridSize = value; + this.fireEvent(new mxEventObject('gridSizeChanged')); +}; + +/** + * Adds event if default parent is changed. + */ +Graph.prototype.setDefaultParent = function(cell) +{ + this.defaultParent = cell; + this.fireEvent(new mxEventObject('defaultParentChanged')); +}; + +/** + * Function: getClickableLinkForCell + * + * Returns the first non-null link for the cell or its ancestors. + * + * Parameters: + * + * cell - whose link should be returned. + */ +Graph.prototype.getClickableLinkForCell = function(cell) +{ + do + { + var link = this.getLinkForCell(cell); + + if (link != null) + { + return link; + } + + cell = this.model.getParent(cell); + } while (cell != null); + + return null; +}; + +/** + * Private helper method. + */ +Graph.prototype.getGlobalVariable = function(name) +{ + var val = null; + + if (name == 'date') + { + val = new Date().toLocaleDateString(); + } + else if (name == 'time') + { + val = new Date().toLocaleTimeString(); + } + else if (name == 'timestamp') + { + val = new Date().toLocaleString(); + } + else if (name.substring(0, 5) == 'date{') + { + var fmt = name.substring(5, name.length - 1); + val = this.formatDate(new Date(), fmt); + } + + return val; +}; + +/** + * Formats a date, see http://blog.stevenlevithan.com/archives/date-time-format + */ +Graph.prototype.formatDate = function(date, mask, utc) +{ + // LATER: Cache regexs + if (this.dateFormatCache == null) + { + this.dateFormatCache = { + i18n: { + dayNames: [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + ], + monthNames: [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + ] + }, + + masks: { + "default": "ddd mmm dd yyyy HH:MM:ss", + shortDate: "m/d/yy", + mediumDate: "mmm d, yyyy", + longDate: "mmmm d, yyyy", + fullDate: "dddd, mmmm d, yyyy", + shortTime: "h:MM TT", + mediumTime: "h:MM:ss TT", + longTime: "h:MM:ss TT Z", + isoDate: "yyyy-mm-dd", + isoTime: "HH:MM:ss", + isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", + isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" + } + }; + } + + var dF = this.dateFormatCache; + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, + timezoneClip = /[^-+\dA-Z]/g, + pad = function (val, len) { + val = String(val); + len = len || 2; + while (val.length < len) val = "0" + val; + return val; + }; + + // You can't provide utc if you skip other args (use the "UTC:" mask prefix) + if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { + mask = date; + date = undefined; + } + + // Passing date through Date applies Date.parse, if necessary + date = date ? new Date(date) : new Date; + if (isNaN(date)) throw SyntaxError("invalid date"); + + mask = String(dF.masks[mask] || mask || dF.masks["default"]); + + // Allow setting the utc argument via the mask + if (mask.slice(0, 4) == "UTC:") { + mask = mask.slice(4); + utc = true; + } + + var _ = utc ? "getUTC" : "get", + d = date[_ + "Date"](), + D = date[_ + "Day"](), + m = date[_ + "Month"](), + y = date[_ + "FullYear"](), + H = date[_ + "Hours"](), + M = date[_ + "Minutes"](), + s = date[_ + "Seconds"](), + L = date[_ + "Milliseconds"](), + o = utc ? 0 : date.getTimezoneOffset(), + flags = { + d: d, + dd: pad(d), + ddd: dF.i18n.dayNames[D], + dddd: dF.i18n.dayNames[D + 7], + m: m + 1, + mm: pad(m + 1), + mmm: dF.i18n.monthNames[m], + mmmm: dF.i18n.monthNames[m + 12], + yy: String(y).slice(2), + yyyy: y, + h: H % 12 || 12, + hh: pad(H % 12 || 12), + H: H, + HH: pad(H), + M: M, + MM: pad(M), + s: s, + ss: pad(s), + l: pad(L, 3), + L: pad(L > 99 ? Math.round(L / 10) : L), + t: H < 12 ? "a" : "p", + tt: H < 12 ? "am" : "pm", + T: H < 12 ? "A" : "P", + TT: H < 12 ? "AM" : "PM", + Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), + o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), + S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] + }; + + return mask.replace(token, function ($0) + { + return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); + }); +}; + +/** + * + */ +Graph.prototype.getLayerForCell = function(cell) +{ + while (cell != null && !this.model.isLayer(cell)) + { + cell = this.model.getParent(cell); + } + + return cell; +}; + +/** + * + */ +Graph.prototype.getLayerForCells = function(cells) +{ + var result = null; + + if (cells.length > 0) + { + result = this.getLayerForCell(cells[0]); + + for (var i = 1; i < cells.length; i++) + { + if (!this.model.isAncestor(result, cells[i])) + { + result = null; + break; + } + } + } + + return result; +}; + +/** + * + */ +Graph.prototype.createLayersDialog = function(onchange, inverted) +{ + var div = document.createElement('div'); + div.style.position = 'absolute'; + + var model = this.getModel(); + var childCount = model.getChildCount(model.root); + + for (var i = 0; i < childCount; i++) + { + (mxUtils.bind(this, function(layer) + { + var title = this.convertValueToString(layer) || + (mxResources.get('background') || 'Background'); + + var span = document.createElement('div'); + span.style.overflow = 'hidden'; + span.style.textOverflow = 'ellipsis'; + span.style.padding = '2px'; + span.style.whiteSpace = 'nowrap'; + span.style.cursor = 'pointer'; + span.setAttribute('title', mxResources.get( + model.isVisible(layer) ? + 'hideIt' : 'show', [title])); + + var inp = document.createElement('img'); + inp.setAttribute('draggable', 'false'); + inp.setAttribute('align', 'absmiddle'); + inp.setAttribute('border', '0'); + inp.style.position = 'relative'; + inp.style.width = '16px'; + inp.style.padding = '0px 6px 0 4px'; + + if (inverted) + { + inp.style.filter = 'invert(100%)'; + inp.style.top = '-2px'; + } + + span.appendChild(inp); + + mxUtils.write(span, title); + div.appendChild(span); + + function update() + { + if (model.isVisible(layer)) + { + inp.setAttribute('src', Editor.visibleImage); + mxUtils.setOpacity(span, 75); + } + else + { + inp.setAttribute('src', Editor.hiddenImage); + mxUtils.setOpacity(span, 25); + } + }; + + mxEvent.addListener(span, 'click', function() + { + model.setVisible(layer, !model.isVisible(layer)); + update(); + + if (onchange != null) + { + onchange(layer); + } + }); + + update(); + })(model.getChildAt(model.root, i))); + } + + return div; +}; + +/** + * Private helper method. + */ +Graph.prototype.replacePlaceholders = function(cell, str, vars, translate) +{ + var result = []; + + if (str != null) + { + var last = 0; + + while (match = this.placeholderPattern.exec(str)) + { + var val = match[0]; + + if (val.length > 2 && val != '%label%' && val != '%tooltip%') + { + var tmp = null; + + if (match.index > last && str.charAt(match.index - 1) == '%') + { + tmp = val.substring(1); + } + else + { + var name = val.substring(1, val.length - 1); + + // Workaround for invalid char for getting attribute in older versions of IE + if (name == 'id') + { + tmp = cell.id; + } + else if (name.indexOf('{') < 0) + { + var current = cell; + + while (tmp == null && current != null) + { + if (current.value != null && typeof(current.value) == 'object') + { + if (Graph.translateDiagram && Graph.diagramLanguage != null) + { + tmp = current.getAttribute(name + '_' + Graph.diagramLanguage); + } + + if (tmp == null) + { + tmp = (current.hasAttribute(name)) ? ((current.getAttribute(name) != null) ? + current.getAttribute(name) : '') : null; + } + } + + current = this.model.getParent(current); + } + } + + if (tmp == null) + { + tmp = this.getGlobalVariable(name); + } + + if (tmp == null && vars != null) + { + tmp = vars[name]; + } + } + + result.push(str.substring(last, match.index) + ((tmp != null) ? tmp : val)); + last = match.index + val.length; + } + } + + result.push(str.substring(last)); + } + + return result.join(''); +}; + +/** + * Resolves the given cells in the model and selects them. + */ +Graph.prototype.restoreSelection = function(cells) +{ + if (cells != null && cells.length > 0) + { + var temp = []; + + for (var i = 0; i < cells.length; i++) + { + var newCell = this.model.getCell(cells[i].id); + + if (newCell != null) + { + temp.push(newCell); + } + } + + this.setSelectionCells(temp); + } + else + { + this.clearSelection(); + } +}; + +/** + * Adds table range selection with Shift+Click. + */ +Graph.prototype.selectCellForEvent = function(cell, evt) +{ + if (!mxEvent.isShiftDown(evt) || this.isSelectionEmpty() || + !this.selectTableRange(this.getSelectionCell(), cell)) + { + mxGraph.prototype.selectCellForEvent.apply(this, arguments); + } +}; + +/** + * Returns true if + */ +Graph.prototype.selectTableRange = function(startCell, endCell) +{ + var result = false; + + if (this.isTableCell(startCell) && this.isTableCell(endCell)) + { + var startRow = this.model.getParent(startCell); + var table = this.model.getParent(startRow); + var endRow = this.model.getParent(endCell); + + if (table == this.model.getParent(endRow)) + { + var startCellIndex = startRow.getIndex(startCell); + var startRowIndex = table.getIndex(startRow); + var endCellIndex = endRow.getIndex(endCell); + var endRowIndex = table.getIndex(endRow); + + var fromRow = Math.min(startRowIndex, endRowIndex); + var toRow = Math.max(startRowIndex, endRowIndex); + var fromCell = Math.min(startCellIndex, endCellIndex); + var toCell = Math.max(startCellIndex, endCellIndex); + + var cells = []; + + for (var row = fromRow; row <= toRow; row++) + { + var currentRow = this.model.getChildAt(table, row); + + for (var col = fromCell; col <= toCell; col++) + { + cells.push(this.model.getChildAt(currentRow, col)); + } + } + + if (cells.length > 0 && (cells.length > 1 || + this.getSelectionCount() > 1 || + !this.isCellSelected(cells[0]))) + { + this.setSelectionCells(cells); + result = true; + } + } + } + + return result; +}; + +/** + * Returns the cells for the given table range. + */ +Graph.prototype.snapCellsToGrid = function(cells, gridSize) +{ + this.getModel().beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + var cell = cells[i]; + var geo = this.getCellGeometry(cell); + + if (geo != null) + { + geo = geo.clone(); + + if (this.getModel().isVertex(cell)) + { + geo.x = Math.round(geo.x / gridSize) * gridSize; + geo.y = Math.round(geo.y / gridSize) * gridSize; + geo.width = Math.round(geo.width / gridSize) * gridSize; + geo.height = Math.round(geo.height / gridSize) * gridSize; + } + else if (this.getModel().isEdge(cell) && geo.points != null) + { + for (var j = 0; j < geo.points.length; j++) + { + geo.points[j].x = Math.round(geo.points[j].x / gridSize) * gridSize; + geo.points[j].y = Math.round(geo.points[j].y / gridSize) * gridSize; + } + } + + this.getModel().setGeometry(cell, geo); + } + } + } + finally + { + this.getModel().endUpdate(); + } +}; + +/** + * Creates a drop handler for inserting the given cells. + */ +Graph.prototype.removeChildCells = function(cell) +{ + this.model.beginUpdate(); + try + { + var childCount = this.model.getChildCount(cell); + + for (var j = childCount; j >= 0; j--) + { + this.model.remove(this.model.getChildAt(cell, j)); + } + } + finally + { + this.model.endUpdate(); + } +}; + +/** + * Creates a drop handler for inserting the given cells. + */ +Graph.prototype.updateShapes = function(source, targets) +{ + this.model.beginUpdate(); + try + { + // Replaces target styles and removes composite childs + for (var i = 0; i < targets.length; i++) + { + if ((this.model.isVertex(source) && this.model.isVertex(targets[i])) || + this.model.isEdge(source) && this.model.isEdge(targets[i])) + { + var style = this.copyStyle(targets[i]); + this.model.setStyle(targets[i], this.model.getStyle(source)); + this.pasteStyle(style, [targets[i]]); + } + + if (mxUtils.getValue(this.getCellStyle(targets[i], + false), 'composite', '0') == '1') + { + this.removeChildCells(targets[i]); + } + } + } + finally + { + this.model.endUpdate(); + } +}; + +/** + * Selects cells for connect vertex return value. + */ +Graph.prototype.selectCellsForConnectVertex = function(cells, evt, hoverIcons) +{ + // Selects only target vertex if one exists + if (cells.length == 2 && this.model.isVertex(cells[1])) + { + this.setSelectionCell(cells[1]); + this.scrollCellToVisible(cells[1]); + + if (hoverIcons != null) + { + // Adds hover icons for cloned vertex or hides icons + if (mxEvent.isTouchEvent(evt)) + { + hoverIcons.update(hoverIcons.getState(this.view.getState(cells[1]))); + } + else + { + hoverIcons.reset(); + } + } + } + else + { + this.setSelectionCells(cells); + } +}; + +/** + * Never connects children in stack layouts or tables. + */ +Graph.prototype.isCloneConnectSource = function(source) +{ + var layout = null; + + if (this.layoutManager != null) + { + layout = this.layoutManager.getLayout(this.model.getParent(source)); + } + + return this.isTableRow(source) || this.isTableCell(source) || + (layout != null && layout.constructor == mxStackLayout); +}; + +/** + * Inserts the given edge before the given cell. + */ +Graph.prototype.insertEdgeBeforeCell = function(edge, cell) +{ + var index = null; + var tmp = cell; + + while (tmp.parent != null && tmp.geometry != null && + tmp.geometry.relative && tmp.parent != edge.parent) + { + tmp = this.model.getParent(tmp); + } + + if (tmp != null && tmp.parent != null && tmp.parent == edge.parent) + { + var index = tmp.parent.getIndex(tmp); + this.model.add(tmp.parent, edge, index); + } +}; + +/** + * Adds a connection to the given vertex or clones the vertex in special layout + * containers without creating a connection. + */ +Graph.prototype.connectVertex = function(source, direction, length, evt, forceClone, ignoreCellAt, createTarget, done) +{ + ignoreCellAt = (ignoreCellAt) ? ignoreCellAt : false; + + // Ignores relative edge labels + if (source.geometry.relative && this.model.isEdge(source.parent)) + { + return []; + } + + // Uses parent for relative child cells + while (source.geometry.relative && this.model.isVertex(source.parent)) + { + source = source.parent; + } + + // Handles clone connect sources + var cloneSource = this.isCloneConnectSource(source); + var composite = (cloneSource) ? source : this.getCompositeParent(source); + + var pt = (source.geometry.relative && source.parent.geometry != null) ? + new mxPoint(source.parent.geometry.width * source.geometry.x, + source.parent.geometry.height * source.geometry.y) : + new mxPoint(composite.geometry.x, composite.geometry.y); + + if (direction == mxConstants.DIRECTION_NORTH) + { + pt.x += composite.geometry.width / 2; + pt.y -= length ; + } + else if (direction == mxConstants.DIRECTION_SOUTH) + { + pt.x += composite.geometry.width / 2; + pt.y += composite.geometry.height + length; + } + else if (direction == mxConstants.DIRECTION_WEST) + { + pt.x -= length; + pt.y += composite.geometry.height / 2; + } + else + { + pt.x += composite.geometry.width + length; + pt.y += composite.geometry.height / 2; + } + + var parentState = this.view.getState(this.model.getParent(source)); + var s = this.view.scale; + var t = this.view.translate; + var dx = t.x * s; + var dy = t.y * s; + + if (parentState != null && this.model.isVertex(parentState.cell)) + { + dx = parentState.x; + dy = parentState.y; + } + + // Workaround for relative child cells + if (this.model.isVertex(source.parent) && source.geometry.relative) + { + pt.x += source.parent.geometry.x; + pt.y += source.parent.geometry.y; + } + + // Checks end point for target cell and container + var rect = (!ignoreCellAt) ? new mxRectangle(dx + pt.x * s, dy + pt.y * s).grow(40 * s) : null; + var tempCells = (rect != null) ? this.getCells(0, 0, 0, 0, null, null, rect, null, true) : null; + var sourceState = this.view.getState(source); + var container = null; + var target = null; + + if (tempCells != null) + { + tempCells = tempCells.reverse(); + + for (var i = 0; i < tempCells.length; i++) + { + if (!this.isCellLocked(tempCells[i]) && !this.model.isEdge(tempCells[i]) && tempCells[i] != source) + { + // Direct parent overrides all possible containers + if (!this.model.isAncestor(source, tempCells[i]) && this.isContainer(tempCells[i]) && + (container == null || tempCells[i] == this.model.getParent(source))) + { + container = tempCells[i]; + } + // Containers are used as target cells but swimlanes are used as parents + else if (target == null && this.isCellConnectable(tempCells[i]) && + !this.model.isAncestor(tempCells[i], source) && + !this.isSwimlane(tempCells[i])) + { + var targetState = this.view.getState(tempCells[i]); + + if (sourceState != null && targetState != null && !mxUtils.intersects(sourceState, targetState)) + { + target = tempCells[i]; + } + } + } + } + } + + var duplicate = (!mxEvent.isShiftDown(evt) || mxEvent.isControlDown(evt)) || forceClone; + + if (duplicate && (urlParams['sketch'] != '1' || forceClone)) + { + if (direction == mxConstants.DIRECTION_NORTH) + { + pt.y -= source.geometry.height / 2; + } + else if (direction == mxConstants.DIRECTION_SOUTH) + { + pt.y += source.geometry.height / 2; + } + else if (direction == mxConstants.DIRECTION_WEST) + { + pt.x -= source.geometry.width / 2; + } + else + { + pt.x += source.geometry.width / 2; + } + } + + var result = []; + var realTarget = target; + target = container; + + var execute = mxUtils.bind(this, function(targetCell) + { + if (createTarget == null || targetCell != null || (target == null && cloneSource)) + { + this.model.beginUpdate(); + try + { + if (realTarget == null && duplicate) + { + // Handles relative and composite cells + var cellToClone = this.getAbsoluteParent((targetCell != null) ? targetCell : source); + cellToClone = (cloneSource) ? source : this.getCompositeParent(cellToClone); + realTarget = (targetCell != null) ? targetCell : this.duplicateCells([cellToClone], false)[0]; + + if (targetCell != null) + { + this.addCells([realTarget], this.model.getParent(source), null, null, null, true); + } + + var geo = this.getCellGeometry(realTarget); + + if (geo != null) + { + if (targetCell != null && urlParams['sketch'] == '1') + { + if (direction == mxConstants.DIRECTION_NORTH) + { + pt.y -= geo.height / 2; + } + else if (direction == mxConstants.DIRECTION_SOUTH) + { + pt.y += geo.height / 2; + } + else if (direction == mxConstants.DIRECTION_WEST) + { + pt.x -= geo.width / 2; + } + else + { + pt.x += geo.width / 2; + } + } + + geo.x = pt.x - geo.width / 2; + geo.y = pt.y - geo.height / 2; + } + + if (container != null) + { + this.addCells([realTarget], container, null, null, null, true); + target = null; + } + else if (duplicate && !cloneSource) + { + this.addCells([realTarget], this.getDefaultParent(), null, null, null, true); + } + } + + var edge = ((mxEvent.isControlDown(evt) && mxEvent.isShiftDown(evt) && duplicate) || + (target == null && cloneSource)) ? null : this.insertEdge(this.model.getParent(source), + null, '', source, realTarget, this.createCurrentEdgeStyle()); + + if (edge != null) + { + result.push(edge); + this.applyNewEdgeStyle(source, [edge], direction); + + if (this.connectionHandler.insertBeforeSource) + { + this.insertEdgeBeforeCell(edge, source); + } + } + + // Special case: Click on west icon puts clone before cell + if (target == null && realTarget != null && source.parent != null && + cloneSource && direction == mxConstants.DIRECTION_WEST) + { + var index = source.parent.getIndex(source); + this.model.add(source.parent, realTarget, index); + } + + if (target == null && realTarget != null) + { + result.push(realTarget); + } + + if (realTarget == null && edge != null) + { + edge.geometry.setTerminalPoint(pt, false); + } + + if (edge != null) + { + this.fireEvent(new mxEventObject('cellsInserted', 'cells', [edge])); + } + } + finally + { + this.model.endUpdate(); + } + } + + if (done != null) + { + done(result); + } + else + { + return result; + } + }); + + if (createTarget != null && realTarget == null && duplicate && + (target != null || !cloneSource)) + { + createTarget(dx + pt.x * s, dy + pt.y * s, execute); + } + else + { + return execute(realTarget); + } +}; + +/** + * Returns all labels in the diagram as a string. + */ +Graph.prototype.getIndexableText = function(cells) +{ + cells = (cells != null) ? cells : this.model. + getDescendants(this.model.root); + var tmp = document.createElement('div'); + var labels = []; + var label = ''; + + for (var i = 0; i < cells.length; i++) + { + var cell = cells[i]; + + if (this.model.isVertex(cell) || this.model.isEdge(cell)) + { + if (this.isHtmlLabel(cell)) + { + tmp.innerHTML = Graph.sanitizeHtml(this.getLabel(cell)); + label = mxUtils.extractTextWithWhitespace([tmp]); + } + else + { + label = this.getLabel(cell); + } + + label = mxUtils.trim(label.replace(/[\x00-\x1F\x7F-\x9F]|\s+/g, ' ')); + + if (label.length > 0) + { + labels.push(label); + } + } + } + + return labels.join(' '); +}; + +/** + * Returns the label for the given cell. + */ +Graph.prototype.convertValueToString = function(cell) +{ + var value = this.model.getValue(cell); + + if (value != null && typeof(value) == 'object') + { + var result = null; + + if (this.isReplacePlaceholders(cell) && cell.getAttribute('placeholder') != null) + { + var name = cell.getAttribute('placeholder'); + var current = cell; + + while (result == null && current != null) + { + if (current.value != null && typeof(current.value) == 'object') + { + result = (current.hasAttribute(name)) ? ((current.getAttribute(name) != null) ? + current.getAttribute(name) : '') : null; + } + + current = this.model.getParent(current); + } + } + else + { + var result = null; + + if (Graph.translateDiagram && Graph.diagramLanguage != null) + { + result = value.getAttribute('label_' + Graph.diagramLanguage); + } + + if (result == null) + { + result = value.getAttribute('label') || ''; + } + } + + return result || ''; + } + + return mxGraph.prototype.convertValueToString.apply(this, arguments); +}; + +/** + * Returns the link for the given cell. + */ +Graph.prototype.getLinksForState = function(state) +{ + if (state != null && state.text != null && state.text.node != null) + { + return state.text.node.getElementsByTagName('a'); + } + + return null; +}; + +/** + * Returns the link for the given cell. + */ +Graph.prototype.getLinkForCell = function(cell) +{ + if (cell.value != null && typeof(cell.value) == 'object') + { + var link = cell.value.getAttribute('link'); + + // Removes links with leading javascript: protocol + // TODO: Check more possible attack vectors + if (link != null && link.toLowerCase().substring(0, 11) === 'javascript:') + { + link = link.substring(11); + } + + return link; + } + + return null; +}; + +/** + * Returns the link target for the given cell. + */ +Graph.prototype.getLinkTargetForCell = function(cell) +{ + if (cell.value != null && typeof(cell.value) == 'object') + { + return cell.value.getAttribute('linkTarget'); + } + + return null; +}; + +/** + * Adds style post processing steps. + */ +Graph.prototype.postProcessCellStyle = function(cell, style) +{ + return this.updateHorizontalStyle(cell,this.replaceDefaultColors(cell, + mxGraph.prototype.postProcessCellStyle.apply(this, arguments))); +}; + +/** + * Overrides label orientation for collapsed swimlanes inside stack and + * for partial rectangles inside tables. + */ +Graph.prototype.updateHorizontalStyle = function(cell, style) +{ + if (cell != null && style != null && this.layoutManager != null) + { + var parent = this.model.getParent(cell); + + if (this.model.isVertex(parent) && this.isCellCollapsed(cell)) + { + var layout = this.layoutManager.getLayout(parent); + + if (layout != null && layout.constructor == mxStackLayout) + { + style[mxConstants.STYLE_HORIZONTAL] = !layout.horizontal; + } + } + } + + return style; +}; + +/** + * Replaces default colors. + */ +Graph.prototype.replaceDefaultColors = function(cell, style) +{ + if (style != null) + { + var bg = mxUtils.hex2rgb(this.shapeBackgroundColor); + var fg = mxUtils.hex2rgb(this.shapeForegroundColor); + + this.replaceDefaultColor(style, mxConstants.STYLE_FONTCOLOR, fg, bg); + this.replaceDefaultColor(style, mxConstants.STYLE_FILLCOLOR, bg, fg); + this.replaceDefaultColor(style, mxConstants.STYLE_GRADIENTCOLOR, fg, bg); + this.replaceDefaultColor(style, mxConstants.STYLE_STROKECOLOR, fg, bg); + this.replaceDefaultColor(style, mxConstants.STYLE_IMAGE_BORDER, fg, bg); + this.replaceDefaultColor(style, mxConstants.STYLE_IMAGE_BACKGROUND, bg, fg); + this.replaceDefaultColor(style, mxConstants.STYLE_LABEL_BORDERCOLOR, fg, bg); + this.replaceDefaultColor(style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, bg, fg); + this.replaceDefaultColor(style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, bg, fg); + } + + return style; +}; + +/** + * Replaces the colors for the given key. + */ +Graph.prototype.replaceDefaultColor = function(style, key, value, inverseValue) +{ + if (style != null && style[key] == 'default' && value != null) + { + style[key] = this.getDefaultColor(style, key, value, inverseValue); + } +}; + +/** + * Replaces the colors for the given key. + */ +Graph.prototype.getDefaultColor = function(style, key, defaultValue, inverseDefaultValue) +{ + var temp = 'default' + key.charAt(0).toUpperCase() + key.substring(1); + + if (style[temp] == 'invert') + { + defaultValue = inverseDefaultValue; + } + + return defaultValue; +}; + +/** + * Disables alternate width persistence for stack layout parents + */ +Graph.prototype.updateAlternateBounds = function(cell, geo, willCollapse) +{ + if (cell != null && geo != null && this.layoutManager != null && geo.alternateBounds != null) + { + var layout = this.layoutManager.getLayout(this.model.getParent(cell)); + + if (layout != null && layout.constructor == mxStackLayout) + { + if (layout.horizontal) + { + geo.alternateBounds.height = 0; + } + else + { + geo.alternateBounds.width = 0; + } + } + } + + mxGraph.prototype.updateAlternateBounds.apply(this, arguments); +}; + +/** + * Adds Shift+collapse/expand and size management for folding inside stack + */ +Graph.prototype.isMoveCellsEvent = function(evt, state) +{ + return mxEvent.isShiftDown(evt) || mxUtils.getValue(state.style, 'moveCells', '0') == '1'; +}; + +/** + * Adds Shift+collapse/expand and size management for folding inside stack + */ +Graph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable, evt) +{ + recurse = (recurse != null) ? recurse : false; + + if (cells == null) + { + cells = this.getFoldableCells(this.getSelectionCells(), collapse); + } + + if (cells != null) + { + this.model.beginUpdate(); + + try + { + mxGraph.prototype.foldCells.apply(this, arguments); + + // Resizes all parent stacks if alt is not pressed + if (this.layoutManager != null) + { + for (var i = 0; i < cells.length; i++) + { + var state = this.view.getState(cells[i]); + var geo = this.getCellGeometry(cells[i]); + + if (state != null && geo != null) + { + var dx = Math.round(geo.width - state.width / this.view.scale); + var dy = Math.round(geo.height - state.height / this.view.scale); + + if (dy != 0 || dx != 0) + { + var parent = this.model.getParent(cells[i]); + var layout = this.layoutManager.getLayout(parent); + + if (layout == null) + { + // Moves cells to the right and down after collapse/expand + if (evt != null && this.isMoveCellsEvent(evt, state)) + { + this.moveSiblings(state, parent, dx, dy); + } + } + else if ((evt == null || !mxEvent.isAltDown(evt)) && + layout.constructor == mxStackLayout && !layout.resizeLast) + { + this.resizeParentStacks(parent, layout, dx, dy); + } + } + } + } + } + } + finally + { + this.model.endUpdate(); + } + + // Selects cells after folding + if (this.isEnabled()) + { + this.setSelectionCells(cells); + } + } +}; + +/** + * Overrides label orientation for collapsed swimlanes inside stack. + */ +Graph.prototype.moveSiblings = function(state, parent, dx, dy) +{ + this.model.beginUpdate(); + try + { + var cells = this.getCellsBeyond(state.x, state.y, parent, true, true); + + for (var i = 0; i < cells.length; i++) + { + if (cells[i] != state.cell) + { + var tmp = this.view.getState(cells[i]); + var geo = this.getCellGeometry(cells[i]); + + if (tmp != null && geo != null) + { + geo = geo.clone(); + geo.translate(Math.round(dx * Math.max(0, Math.min(1, (tmp.x - state.x) / state.width))), + Math.round(dy * Math.max(0, Math.min(1, (tmp.y - state.y) / state.height)))); + this.model.setGeometry(cells[i], geo); + } + } + } + } + finally + { + this.model.endUpdate(); + } +}; + +/** + * Overrides label orientation for collapsed swimlanes inside stack. + */ +Graph.prototype.resizeParentStacks = function(parent, layout, dx, dy) +{ + if (this.layoutManager != null && layout != null && layout.constructor == mxStackLayout && !layout.resizeLast) + { + this.model.beginUpdate(); + try + { + var dir = layout.horizontal; + + // Bubble resize up for all parent stack layouts with same orientation + while (parent != null && layout != null && layout.constructor == mxStackLayout && + layout.horizontal == dir && !layout.resizeLast) + { + var pgeo = this.getCellGeometry(parent); + var pstate = this.view.getState(parent); + + if (pstate != null && pgeo != null) + { + pgeo = pgeo.clone(); + + if (layout.horizontal) + { + pgeo.width += dx + Math.min(0, pstate.width / this.view.scale - pgeo.width); + } + else + { + pgeo.height += dy + Math.min(0, pstate.height / this.view.scale - pgeo.height); + } + + this.model.setGeometry(parent, pgeo); + } + + parent = this.model.getParent(parent); + layout = this.layoutManager.getLayout(parent); + } + } + finally + { + this.model.endUpdate(); + } + } +}; + +/** + * Disables drill-down for non-swimlanes. + */ +Graph.prototype.isContainer = function(cell) +{ + var style = this.getCurrentCellStyle(cell); + + if (this.isSwimlane(cell)) + { + return style['container'] != '0'; + } + else + { + return style['container'] == '1'; + } +}; + +/** + * Adds a connectable style. + */ +Graph.prototype.isCellConnectable = function(cell) +{ + var style = this.getCurrentCellStyle(cell); + + return (style['connectable'] != null) ? style['connectable'] != '0' : + mxGraph.prototype.isCellConnectable.apply(this, arguments); +}; + +/** + * Adds labelMovable style. + */ +Graph.prototype.isLabelMovable = function(cell) +{ + var style = this.getCurrentCellStyle(cell); + + return (style['movableLabel'] != null) ? style['movableLabel'] != '0' : + mxGraph.prototype.isLabelMovable.apply(this, arguments); +}; + +/** + * Function: selectAll + * + * Selects all children of the given parent cell or the children of the + * default parent if no parent is specified. To select leaf vertices and/or + * edges use . + * + * Parameters: + * + * parent - Optional whose children should be selected. + * Default is . + */ +Graph.prototype.selectAll = function(parent) +{ + parent = parent || this.getDefaultParent(); + + if (!this.isCellLocked(parent)) + { + mxGraph.prototype.selectAll.apply(this, arguments); + } +}; + +/** + * Function: selectCells + * + * Selects all vertices and/or edges depending on the given boolean + * arguments recursively, starting at the given parent or the default + * parent if no parent is specified. Use to select all cells. + * For vertices, only cells with no children are selected. + * + * Parameters: + * + * vertices - Boolean indicating if vertices should be selected. + * edges - Boolean indicating if edges should be selected. + * parent - Optional that acts as the root of the recursion. + * Default is . + */ +Graph.prototype.selectCells = function(vertices, edges, parent) +{ + parent = parent || this.getDefaultParent(); + + if (!this.isCellLocked(parent)) + { + mxGraph.prototype.selectCells.apply(this, arguments); + } +}; + +/** + * Function: getSwimlaneAt + * + * Returns the bottom-most swimlane that intersects the given point (x, y) + * in the cell hierarchy that starts at the given parent. + * + * Parameters: + * + * x - X-coordinate of the location to be checked. + * y - Y-coordinate of the location to be checked. + * parent - that should be used as the root of the recursion. + * Default is . + */ +Graph.prototype.getSwimlaneAt = function (x, y, parent) +{ + var result = mxGraph.prototype.getSwimlaneAt.apply(this, arguments); + + if (this.isCellLocked(result)) + { + result = null; + } + + return result; +}; + +/** + * Disables folding for non-swimlanes. + */ +Graph.prototype.isCellFoldable = function(cell) +{ + var style = this.getCurrentCellStyle(cell); + + return this.foldingEnabled && mxUtils.getValue(style, + mxConstants.STYLE_RESIZABLE, '1') != '0' && + (style['treeFolding'] == '1' || + (!this.isCellLocked(cell) && + ((this.isContainer(cell) && style['collapsible'] != '0') || + (!this.isContainer(cell) && style['collapsible'] == '1')))); +}; + +/** + * Stops all interactions and clears the selection. + */ +Graph.prototype.reset = function() +{ + if (this.isEditing()) + { + this.stopEditing(true); + } + + this.escape(); + + if (!this.isSelectionEmpty()) + { + this.clearSelection(); + } +}; + +/** + * Overridden to limit zoom to 1% - 16.000%. + */ +Graph.prototype.zoom = function(factor, center) +{ + factor = Math.max(0.01, Math.min(this.view.scale * factor, 160)) / this.view.scale; + + mxGraph.prototype.zoom.apply(this, arguments); +}; + +/** + * Function: zoomIn + * + * Zooms into the graph by . + */ +Graph.prototype.zoomIn = function() +{ + // Switches to 1% zoom steps below 15% + if (this.view.scale < 0.15) + { + this.zoom((this.view.scale + 0.01) / this.view.scale); + } + else + { + // Uses to 5% zoom steps for better grid rendering in webkit + // and to avoid rounding errors for zoom steps + this.zoom((Math.round(this.view.scale * this.zoomFactor * 20) / 20) / this.view.scale); + } +}; + +/** + * Function: zoomOut + * + * Zooms out of the graph by . + */ +Graph.prototype.zoomOut = function() +{ + // Switches to 1% zoom steps below 15% + if (this.view.scale <= 0.15) + { + this.zoom((this.view.scale - 0.01) / this.view.scale); + } + else + { + // Uses to 5% zoom steps for better grid rendering in webkit + // and to avoid rounding errors for zoom steps + this.zoom((Math.round(this.view.scale * (1 / this.zoomFactor) * 20) / 20) / this.view.scale); + } +}; + +/** + * Function: fitPages + * + * Fits the given number of pages to the current view horizontally. + * If pageCount is null then all pages will be used. This should not + * be called if pages are not visible. + */ +Graph.prototype.fitPages = function(pageCount, ignoreHeight) +{ + var vcount = 1; + + if (pageCount == null) + { + var layout = this.getPageLayout(); + pageCount = layout.width; + vcount = layout.height; + } + + var ps = this.pageScale; + var fmt = this.pageFormat; + var cw = this.container.clientWidth - 10; + var ch = this.container.clientHeight - 10; + var sx = cw / (pageCount * fmt.width) / ps; + + var scale = Math.floor(20 * ((ignoreHeight) ? sx : + Math.min(sx, ch / (vcount * fmt.height) / ps))) / 20; + + this.zoomTo(scale); + + if (mxUtils.hasScrollbars(this.container)) + { + var pad = this.getPagePadding(); + this.container.scrollLeft = Math.min(pad.x * this.view.scale, + (this.container.scrollWidth - this.container.clientWidth) / 2) - 1; + + if (!ignoreHeight) + { + if (pageCount >= 2) + { + this.container.scrollTop = Math.min(pad.y, + (this.container.scrollHeight - + this.container.clientHeight) / 2); + } + else + { + this.container.scrollTop = pad.y * this.view.scale - 1; + } + } + } +}; + +/** + * Function: fitWindow + * + * Sets the current visible rectangle of the window in graph coordinates. + */ +Graph.prototype.fitWindow = function(bounds, border) +{ + border = (border != null) ? border : 10; + + var cw = this.container.clientWidth - border; + var ch = this.container.clientHeight - border; + var scale = Math.floor(20 * Math.min(cw / bounds.width, ch / bounds.height)) / 20; + this.zoomTo(scale); + + if (mxUtils.hasScrollbars(this.container)) + { + var t = this.view.translate; + this.container.scrollLeft = (bounds.x + t.x) * this.view.scale - + Math.max((cw - bounds.width * this.view.scale) / 2 + border / 2, 0); + this.container.scrollTop = (bounds.y + t.y) * this.view.scale - + Math.max((ch - bounds.height * this.view.scale) / 2 + border / 2, 0); + } +}; + +/** + * Overrides tooltips to show custom tooltip or metadata. + */ +Graph.prototype.convertValueToTooltip = function(cell) +{ + var tmp = null; + + if (mxUtils.isNode(cell.value)) + { + if (Graph.translateDiagram && Graph.diagramLanguage != null) + { + tmp = cell.value.getAttribute('tooltip_' + Graph.diagramLanguage); + } + + if (tmp == null) + { + tmp = cell.value.getAttribute('tooltip'); + } + + if (tmp != null) + { + if (tmp != null && this.isReplacePlaceholders(cell)) + { + tmp = this.replacePlaceholders(cell, tmp); + } + + tmp = Graph.sanitizeHtml(tmp); + } + } + + return tmp; +}; + +/** + * Overrides tooltips to show custom tooltip or metadata. + */ +Graph.prototype.getTooltipForCell = function(cell) +{ + var tip = ''; + + if (mxUtils.isNode(cell.value)) + { + tip = this.convertValueToTooltip(cell); + + if (tip == null) + { + var ignored = this.builtInProperties; + var attrs = cell.value.attributes; + var temp = []; + tip = ''; + + // Hides links in edit mode + if (this.isEnabled()) + { + ignored.push('linkTarget'); + ignored.push('link'); + } + + for (var i = 0; i < attrs.length; i++) + { + if (((Graph.translateDiagram && attrs[i].nodeName == 'label') || + mxUtils.indexOf(ignored, attrs[i].nodeName) < 0) && + attrs[i].nodeValue.length > 0) + { + 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; + } + }); + + for (var i = 0; i < temp.length; i++) + { + if (temp[i].name != 'link' || !this.isCustomLink(temp[i].value)) + { + tip += ((temp[i].name != 'link') ? '' + mxUtils.htmlEntities(temp[i].name) + + ': ' : '') + mxUtils.htmlEntities(temp[i].value) + '\n'; + } + } + + if (tip.length > 0) + { + tip = tip.substring(0, tip.length - 1); + + if (mxClient.IS_SVG) + { + tip = '
' + + tip + '
'; + } + } + } + } + + return tip; +}; + +/** + * Adds rack child layout style. + */ +Graph.prototype.getFlowAnimationStyle = function() +{ + var head = document.getElementsByTagName('head')[0]; + + if (head != null && this.flowAnimationStyle == null) + { + this.flowAnimationStyle = document.createElement('style') + this.flowAnimationStyle.setAttribute('id', + 'geEditorFlowAnimation-' + Editor.guid()); + this.flowAnimationStyle.type = 'text/css'; + var id = this.flowAnimationStyle.getAttribute('id'); + this.flowAnimationStyle.innerHTML = this.getFlowAnimationStyleCss(id); + + head.appendChild(this.flowAnimationStyle); + } + + return this.flowAnimationStyle; +}; + +/** + * Adds rack child layout style. + */ +Graph.prototype.getFlowAnimationStyleCss = function(id) +{ + return '.' + id + ' {\n' + + 'animation: ' + id + ' 0.5s linear;\n' + + 'animation-iteration-count: infinite;\n' + + '}\n' + + '@keyframes ' + id + ' {\n' + + 'to {\n' + + 'stroke-dashoffset: ' + (this.view.scale * -16) + ';\n' + + '}\n' + + '}'; +}; + +/** + * Turns the given string into an array. + */ +Graph.prototype.stringToBytes = function(str) +{ + return Graph.stringToBytes(str); +}; + +/** + * Turns the given array into a string. + */ +Graph.prototype.bytesToString = function(arr) +{ + return Graph.bytesToString(arr); +}; + +/** + * Returns a base64 encoded version of the compressed outer XML of the given node. + */ +Graph.prototype.compressNode = function(node) +{ + return Graph.compressNode(node); +}; + +/** + * Returns a base64 encoded version of the compressed string. + */ +Graph.prototype.compress = function(data, deflate) +{ + return Graph.compress(data, deflate); +}; + +/** + * Returns a decompressed version of the base64 encoded string. + */ +Graph.prototype.decompress = function(data, inflate) +{ + return Graph.decompress(data, inflate); +}; + +/** + * Redirects to Graph.zapGremlins. + */ +Graph.prototype.zapGremlins = function(text) +{ + return Graph.zapGremlins(text); +}; + +/** + * Hover icons are used for hover, vertex handler and drag from sidebar. + */ +HoverIcons = function(graph) +{ + mxEventSource.call(this); + this.graph = graph; + this.init(); +}; + +// Extends mxEventSource +mxUtils.extend(HoverIcons, mxEventSource); + +/** + * Up arrow. + */ +HoverIcons.prototype.arrowSpacing = 2; + +/** + * Delay to switch to another state for overlapping bbox. Default is 500ms. + */ +HoverIcons.prototype.updateDelay = 500; + +/** + * Delay to switch between states. Default is 140ms. + */ +HoverIcons.prototype.activationDelay = 140; + +/** + * Up arrow. + */ +HoverIcons.prototype.currentState = null; + +/** + * Up arrow. + */ +HoverIcons.prototype.activeArrow = null; + +/** + * Up arrow. + */ +HoverIcons.prototype.inactiveOpacity = 15; + +/** + * Up arrow. + */ +HoverIcons.prototype.cssCursor = 'copy'; + +/** + * Whether to hide arrows that collide with vertices. + * LATER: Add keyboard override, touch support. + */ +HoverIcons.prototype.checkCollisions = true; + +/** + * Up arrow. + */ +HoverIcons.prototype.arrowFill = '#29b6f2'; + +/** + * Up arrow. + */ +HoverIcons.prototype.triangleUp = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-up.png', 26, 14) : + Graph.createSvgImage(18, 28, ''); + +/** + * Right arrow. + */ +HoverIcons.prototype.triangleRight = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-right.png', 14, 26) : + Graph.createSvgImage(26, 18, ''); + +/** + * Down arrow. + */ +HoverIcons.prototype.triangleDown = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-down.png', 26, 14) : + Graph.createSvgImage(18, 26, ''); + +/** + * Left arrow. + */ +HoverIcons.prototype.triangleLeft = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/triangle-left.png', 14, 26) : + Graph.createSvgImage(28, 18, ''); + +/** + * Round target. + */ +HoverIcons.prototype.roundDrop = (!mxClient.IS_SVG) ? new mxImage(IMAGE_PATH + '/round-drop.png', 26, 26) : + Graph.createSvgImage(26, 26, ''); + +/** + * Refresh target. + */ +HoverIcons.prototype.refreshTarget = new mxImage((mxClient.IS_SVG) ? 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjM2cHgiIGhlaWdodD0iMzZweCI+PGVsbGlwc2UgZmlsbD0iIzI5YjZmMiIgY3g9IjEyIiBjeT0iMTIiIHJ4PSIxMiIgcnk9IjEyIi8+PHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjgpIHRyYW5zbGF0ZSgyLjQsIDIuNCkiIHN0cm9rZT0iI2ZmZiIgZmlsbD0iI2ZmZiIgZD0iTTEyIDZ2M2w0LTQtNC00djNjLTQuNDIgMC04IDMuNTgtOCA4IDAgMS41Ny40NiAzLjAzIDEuMjQgNC4yNkw2LjcgMTQuOGMtLjQ1LS44My0uNy0xLjc5LS43LTIuOCAwLTMuMzEgMi42OS02IDYtNnptNi43NiAxLjc0TDE3LjMgOS4yYy40NC44NC43IDEuNzkuNyAyLjggMCAzLjMxLTIuNjkgNi02IDZ2LTNsLTQgNCA0IDR2LTNjNC40MiAwIDgtMy41OCA4LTggMC0xLjU3LS40Ni0zLjAzLTEuMjQtNC4yNnoiLz48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+Cg==' : + IMAGE_PATH + '/refresh.png', 38, 38); + +/** + * Tolerance for hover icon clicks. + */ +HoverIcons.prototype.tolerance = (mxClient.IS_TOUCH) ? 6 : 0; + +/** + * + */ +HoverIcons.prototype.init = function() +{ + this.arrowUp = this.createArrow(this.triangleUp, mxResources.get('plusTooltip'), mxConstants.DIRECTION_NORTH); + this.arrowRight = this.createArrow(this.triangleRight, mxResources.get('plusTooltip'), mxConstants.DIRECTION_EAST); + this.arrowDown = this.createArrow(this.triangleDown, mxResources.get('plusTooltip'), mxConstants.DIRECTION_SOUTH); + this.arrowLeft = this.createArrow(this.triangleLeft, mxResources.get('plusTooltip'), mxConstants.DIRECTION_WEST); + + this.elts = [this.arrowUp, this.arrowRight, this.arrowDown, this.arrowLeft]; + + this.resetHandler = mxUtils.bind(this, function() + { + this.reset(); + }); + + this.repaintHandler = mxUtils.bind(this, function() + { + this.repaint(); + }); + + this.graph.selectionModel.addListener(mxEvent.CHANGE, this.resetHandler); + this.graph.model.addListener(mxEvent.CHANGE, this.repaintHandler); + this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler); + this.graph.view.addListener(mxEvent.TRANSLATE, this.repaintHandler); + this.graph.view.addListener(mxEvent.SCALE, this.repaintHandler); + this.graph.view.addListener(mxEvent.DOWN, this.repaintHandler); + this.graph.view.addListener(mxEvent.UP, this.repaintHandler); + this.graph.addListener(mxEvent.ROOT, this.repaintHandler); + this.graph.addListener(mxEvent.ESCAPE, this.resetHandler); + mxEvent.addListener(this.graph.container, 'scroll', this.resetHandler); + + // Resets the mouse point on escape + this.graph.addListener(mxEvent.ESCAPE, mxUtils.bind(this, function() + { + this.mouseDownPoint = null; + })); + + // Removes hover icons if mouse leaves the container + mxEvent.addListener(this.graph.container, 'mouseleave', mxUtils.bind(this, function(evt) + { + // Workaround for IE11 firing mouseleave for touch in diagram + if (evt.relatedTarget != null && mxEvent.getSource(evt) == this.graph.container) + { + this.setDisplay('none'); + } + })); + + // Resets current state when in-place editor starts + this.graph.addListener(mxEvent.START_EDITING, mxUtils.bind(this, function(evt) + { + this.reset(); + })); + + // Resets current state after update of selection state for touch events + var graphClick = this.graph.click; + this.graph.click = mxUtils.bind(this, function(me) + { + graphClick.apply(this.graph, arguments); + + if (this.currentState != null && !this.graph.isCellSelected(this.currentState.cell) && + mxEvent.isTouchEvent(me.getEvent()) && !this.graph.model.isVertex(me.getCell())) + { + this.reset(); + } + }); + + // Checks if connection handler was active in mouse move + // as workaround for possible double connection inserted + var connectionHandlerActive = false; + + // Implements a listener for hover and click handling + this.graph.addMouseListener( + { + mouseDown: mxUtils.bind(this, function(sender, me) + { + connectionHandlerActive = false; + var evt = me.getEvent(); + + if (this.isResetEvent(evt)) + { + this.reset(); + } + else if (!this.isActive()) + { + var state = this.getState(me.getState()); + + if (state != null || !mxEvent.isTouchEvent(evt)) + { + this.update(state); + } + } + + this.setDisplay('none'); + }), + mouseMove: mxUtils.bind(this, function(sender, me) + { + var evt = me.getEvent(); + + if (this.isResetEvent(evt)) + { + this.reset(); + } + else if (!this.graph.isMouseDown && !mxEvent.isTouchEvent(evt)) + { + this.update(this.getState(me.getState()), + me.getGraphX(), me.getGraphY()); + } + + if (this.graph.connectionHandler != null && + this.graph.connectionHandler.shape != null) + { + connectionHandlerActive = true; + } + }), + mouseUp: mxUtils.bind(this, function(sender, me) + { + var evt = me.getEvent(); + var pt = mxUtils.convertPoint(this.graph.container, + mxEvent.getClientX(evt), mxEvent.getClientY(evt)) + + if (this.isResetEvent(evt)) + { + this.reset(); + } + else if (this.isActive() && !connectionHandlerActive && + this.mouseDownPoint != null) + { + this.click(this.currentState, this.getDirection(), me); + } + else if (this.isActive()) + { + // Selects target vertex after drag and clone if not only new edge was inserted + if (this.graph.getSelectionCount() != 1 || !this.graph.model.isEdge( + this.graph.getSelectionCell())) + { + this.update(this.getState(this.graph.view.getState( + this.graph.getCellAt(me.getGraphX(), me.getGraphY())))); + } + else + { + this.reset(); + } + } + else if (mxEvent.isTouchEvent(evt) || (this.bbox != null && + mxUtils.contains(this.bbox, me.getGraphX(), me.getGraphY()))) + { + // Shows existing hover icons if inside bounding box + this.setDisplay(''); + this.repaint(); + } + else if (!mxEvent.isTouchEvent(evt)) + { + this.reset(); + } + + connectionHandlerActive = false; + this.resetActiveArrow(); + }) + }); +}; + +/** + * + */ +HoverIcons.prototype.isResetEvent = function(evt, allowShift) +{ + return mxEvent.isAltDown(evt) || (this.activeArrow == null && mxEvent.isShiftDown(evt)) || + (mxEvent.isPopupTrigger(evt) && !this.graph.isCloneEvent(evt)); +}; + +/** + * + */ +HoverIcons.prototype.createArrow = function(img, tooltip, direction) +{ + var arrow = null; + arrow = mxUtils.createImage(img.src); + arrow.style.width = img.width + 'px'; + arrow.style.height = img.height + 'px'; + arrow.style.padding = this.tolerance + 'px'; + + if (tooltip != null) + { + arrow.setAttribute('title', tooltip); + } + + arrow.style.position = 'absolute'; + arrow.style.cursor = this.cssCursor; + + mxEvent.addGestureListeners(arrow, mxUtils.bind(this, function(evt) + { + if (this.currentState != null && !this.isResetEvent(evt)) + { + this.mouseDownPoint = mxUtils.convertPoint(this.graph.container, + mxEvent.getClientX(evt), mxEvent.getClientY(evt)); + this.drag(evt, this.mouseDownPoint.x, this.mouseDownPoint.y); + this.activeArrow = arrow; + this.setDisplay('none'); + mxEvent.consume(evt); + } + })); + + // Captures mouse events as events on graph + mxEvent.redirectMouseEvents(arrow, this.graph, this.currentState); + + mxEvent.addListener(arrow, 'mouseenter', mxUtils.bind(this, function(evt) + { + // Workaround for Firefox firing mouseenter on touchend + if (mxEvent.isMouseEvent(evt)) + { + if (this.activeArrow != null && this.activeArrow != arrow) + { + mxUtils.setOpacity(this.activeArrow, this.inactiveOpacity); + } + + this.graph.connectionHandler.constraintHandler.reset(); + mxUtils.setOpacity(arrow, 100); + this.activeArrow = arrow; + + this.fireEvent(new mxEventObject('focus', 'arrow', arrow, + 'direction', direction, 'event', evt)); + } + })); + + mxEvent.addListener(arrow, 'mouseleave', mxUtils.bind(this, function(evt) + { + if (mxEvent.isMouseEvent(evt)) + { + this.fireEvent(new mxEventObject('blur', 'arrow', arrow, + 'direction', direction, 'event', evt)); + } + + // Workaround for IE11 firing this event on touch + if (!this.graph.isMouseDown) + { + this.resetActiveArrow(); + } + })); + + return arrow; +}; + +/** + * + */ +HoverIcons.prototype.resetActiveArrow = function() +{ + if (this.activeArrow != null) + { + mxUtils.setOpacity(this.activeArrow, this.inactiveOpacity); + this.activeArrow = null; + } +}; + +/** + * + */ +HoverIcons.prototype.getDirection = function() +{ + var dir = mxConstants.DIRECTION_EAST; + + if (this.activeArrow == this.arrowUp) + { + dir = mxConstants.DIRECTION_NORTH; + } + else if (this.activeArrow == this.arrowDown) + { + dir = mxConstants.DIRECTION_SOUTH; + } + else if (this.activeArrow == this.arrowLeft) + { + dir = mxConstants.DIRECTION_WEST; + } + + return dir; +}; + +/** + * + */ +HoverIcons.prototype.visitNodes = function(visitor) +{ + for (var i = 0; i < this.elts.length; i++) + { + if (this.elts[i] != null) + { + visitor(this.elts[i]); + } + } +}; + +/** + * + */ +HoverIcons.prototype.removeNodes = function() +{ + this.visitNodes(function(elt) + { + if (elt.parentNode != null) + { + elt.parentNode.removeChild(elt); + } + }); +}; + +/** + * + */ +HoverIcons.prototype.setDisplay = function(display) +{ + this.visitNodes(function(elt) + { + elt.style.display = display; + }); +}; + +/** + * + */ +HoverIcons.prototype.isActive = function() +{ + return this.activeArrow != null && this.currentState != null; +}; + +/** + * + */ +HoverIcons.prototype.drag = function(evt, x, y) +{ + this.graph.popupMenuHandler.hideMenu(); + this.graph.stopEditing(false); + + // Checks if state was removed in call to stopEditing above + if (this.currentState != null) + { + this.graph.connectionHandler.start(this.currentState, x, y); + this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt); + this.graph.isMouseDown = true; + + // Hides handles for selection cell + var handler = this.graph.selectionCellsHandler.getHandler(this.currentState.cell); + + if (handler != null) + { + handler.setHandlesVisible(false); + } + + // Ctrl+shift drag sets source constraint + var es = this.graph.connectionHandler.edgeState; + + if (evt != null && mxEvent.isShiftDown(evt) && mxEvent.isControlDown(evt) && es != null && + mxUtils.getValue(es.style, mxConstants.STYLE_EDGE, null) === 'orthogonalEdgeStyle') + { + var direction = this.getDirection(); + es.cell.style = mxUtils.setStyle(es.cell.style, 'sourcePortConstraint', direction); + es.style['sourcePortConstraint'] = direction; + } + } +}; + +/** + * + */ +HoverIcons.prototype.getStateAt = function(state, x, y) +{ + return this.graph.view.getState(this.graph.getCellAt(x, y)); +}; + +/** + * + */ +HoverIcons.prototype.click = function(state, dir, me) +{ + var evt = me.getEvent(); + var x = me.getGraphX(); + var y = me.getGraphY(); + + var tmp = this.getStateAt(state, x, y); + + if (tmp != null && this.graph.model.isEdge(tmp.cell) && !this.graph.isCloneEvent(evt) && + (tmp.getVisibleTerminalState(true) == state || tmp.getVisibleTerminalState(false) == state)) + { + this.graph.setSelectionCell(tmp.cell); + this.reset(); + } + else if (state != null) + { + this.execute(state, dir, me); + } + + me.consume(); +}; + +/** + * + */ +HoverIcons.prototype.execute = function(state, dir, me) +{ + var evt = me.getEvent(); + + this.graph.selectCellsForConnectVertex(this.graph.connectVertex( + state.cell, dir, this.graph.defaultEdgeLength, evt, this.graph.isCloneEvent(evt), + this.graph.isCloneEvent(evt)), evt, this); +}; + +/** + * + */ +HoverIcons.prototype.reset = function(clearTimeout) +{ + clearTimeout = (clearTimeout == null) ? true : clearTimeout; + + if (clearTimeout && this.updateThread != null) + { + window.clearTimeout(this.updateThread); + } + + this.mouseDownPoint = null; + this.currentState = null; + this.activeArrow = null; + this.removeNodes(); + this.bbox = null; + + this.fireEvent(new mxEventObject('reset')); +}; + +/** + * + */ +HoverIcons.prototype.repaint = function() +{ + this.bbox = null; + + if (this.currentState != null) + { + // Checks if cell was deleted + this.currentState = this.getState(this.currentState); + + // Cell was deleted + if (this.currentState != null && + this.graph.model.isVertex(this.currentState.cell) && + this.graph.isCellConnectable(this.currentState.cell)) + { + var bds = mxRectangle.fromRectangle(this.currentState); + + // Uses outer bounding box to take rotation into account + if (this.currentState.shape != null && this.currentState.shape.boundingBox != null) + { + bds = mxRectangle.fromRectangle(this.currentState.shape.boundingBox); + } + + bds.grow(this.graph.tolerance); + bds.grow(this.arrowSpacing); + + var handler = this.graph.selectionCellsHandler.getHandler(this.currentState.cell); + + if (this.graph.isTableRow(this.currentState.cell)) + { + handler = this.graph.selectionCellsHandler.getHandler( + this.graph.model.getParent(this.currentState.cell)); + } + + var rotationBbox = null; + + if (handler != null) + { + bds.x -= handler.horizontalOffset / 2; + bds.y -= handler.verticalOffset / 2; + bds.width += handler.horizontalOffset; + bds.height += handler.verticalOffset; + + // Adds bounding box of rotation handle to avoid overlap + if (handler.rotationShape != null && handler.rotationShape.node != null && + handler.rotationShape.node.style.visibility != 'hidden' && + handler.rotationShape.node.style.display != 'none' && + handler.rotationShape.boundingBox != null) + { + rotationBbox = handler.rotationShape.boundingBox; + } + } + + // Positions arrows avoid collisions with rotation handle + var positionArrow = mxUtils.bind(this, function(arrow, x, y) + { + if (rotationBbox != null) + { + var bbox = new mxRectangle(x, y, arrow.clientWidth, arrow.clientHeight); + + if (mxUtils.intersects(bbox, rotationBbox)) + { + if (arrow == this.arrowUp) + { + y -= bbox.y + bbox.height - rotationBbox.y; + } + else if (arrow == this.arrowRight) + { + x += rotationBbox.x + rotationBbox.width - bbox.x; + } + else if (arrow == this.arrowDown) + { + y += rotationBbox.y + rotationBbox.height - bbox.y; + } + else if (arrow == this.arrowLeft) + { + x -= bbox.x + bbox.width - rotationBbox.x; + } + } + } + + arrow.style.left = x + 'px'; + arrow.style.top = y + 'px'; + mxUtils.setOpacity(arrow, this.inactiveOpacity); + }); + + positionArrow(this.arrowUp, + Math.round(this.currentState.getCenterX() - this.triangleUp.width / 2 - this.tolerance), + Math.round(bds.y - this.triangleUp.height - this.tolerance)); + + positionArrow(this.arrowRight, Math.round(bds.x + bds.width - this.tolerance), + Math.round(this.currentState.getCenterY() - this.triangleRight.height / 2 - this.tolerance)); + + positionArrow(this.arrowDown, parseInt(this.arrowUp.style.left), + Math.round(bds.y + bds.height - this.tolerance)); + + positionArrow(this.arrowLeft, Math.round(bds.x - this.triangleLeft.width - this.tolerance), + parseInt(this.arrowRight.style.top)); + + if (this.checkCollisions) + { + var right = this.graph.getCellAt(bds.x + bds.width + + this.triangleRight.width / 2, this.currentState.getCenterY()); + var left = this.graph.getCellAt(bds.x - this.triangleLeft.width / 2, this.currentState.getCenterY()); + var top = this.graph.getCellAt(this.currentState.getCenterX(), bds.y - this.triangleUp.height / 2); + var bottom = this.graph.getCellAt(this.currentState.getCenterX(), bds.y + bds.height + this.triangleDown.height / 2); + + // Shows hover icons large cell is behind all directions of current cell + if (right != null && right == left && left == top && top == bottom) + { + right = null; + left = null; + top = null; + bottom = null; + } + + var currentGeo = this.graph.getCellGeometry(this.currentState.cell); + + var checkCollision = mxUtils.bind(this, function(cell, arrow) + { + var geo = this.graph.model.isVertex(cell) && this.graph.getCellGeometry(cell); + + // Ignores collision if vertex is more than 3 times the size of this vertex + if (cell != null && !this.graph.model.isAncestor(cell, this.currentState.cell) && + !this.graph.isSwimlane(cell) && (geo == null || currentGeo == null || + (geo.height < 3 * currentGeo.height && geo.width < 3 * currentGeo.width))) + { + arrow.style.visibility = 'hidden'; + } + else + { + arrow.style.visibility = 'visible'; + } + }); + + checkCollision(right, this.arrowRight); + checkCollision(left, this.arrowLeft); + checkCollision(top, this.arrowUp); + checkCollision(bottom, this.arrowDown); + } + else + { + this.arrowLeft.style.visibility = 'visible'; + this.arrowRight.style.visibility = 'visible'; + this.arrowUp.style.visibility = 'visible'; + this.arrowDown.style.visibility = 'visible'; + } + + if (this.graph.tooltipHandler.isEnabled()) + { + this.arrowLeft.setAttribute('title', mxResources.get('plusTooltip')); + this.arrowRight.setAttribute('title', mxResources.get('plusTooltip')); + this.arrowUp.setAttribute('title', mxResources.get('plusTooltip')); + this.arrowDown.setAttribute('title', mxResources.get('plusTooltip')); + } + else + { + this.arrowLeft.removeAttribute('title'); + this.arrowRight.removeAttribute('title'); + this.arrowUp.removeAttribute('title'); + this.arrowDown.removeAttribute('title'); + } + } + else + { + this.reset(); + } + + // Updates bounding box + if (this.currentState != null) + { + this.bbox = this.computeBoundingBox(); + + // Adds tolerance for hover + if (this.bbox != null) + { + this.bbox.grow(10); + } + } + } +}; + +/** + * + */ +HoverIcons.prototype.computeBoundingBox = function() +{ + var bbox = (!this.graph.model.isEdge(this.currentState.cell)) ? mxRectangle.fromRectangle(this.currentState) : null; + + this.visitNodes(function(elt) + { + if (elt.parentNode != null) + { + var tmp = new mxRectangle(elt.offsetLeft, elt.offsetTop, elt.offsetWidth, elt.offsetHeight); + + if (bbox == null) + { + bbox = tmp; + } + else + { + bbox.add(tmp); + } + } + }); + + return bbox; +}; + +/** + * + */ +HoverIcons.prototype.getState = function(state) +{ + if (state != null) + { + var cell = state.cell; + + if (!this.graph.getModel().contains(cell)) + { + state = null; + } + else + { + // Uses connectable parent vertex if child is not connectable + if (this.graph.getModel().isVertex(cell) && !this.graph.isCellConnectable(cell)) + { + var parent = this.graph.getModel().getParent(cell); + + if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) + { + cell = parent; + } + } + + // Ignores locked cells and edges + if (this.graph.isCellLocked(cell) || this.graph.model.isEdge(cell)) + { + cell = null; + } + + state = this.graph.view.getState(cell); + + if (state != null && state.style == null) + { + state = null; + } + } + } + + return state; +}; + +/** + * + */ +HoverIcons.prototype.update = function(state, x, y) +{ + if (!this.graph.connectionArrowsEnabled || + (this.graph.freehand != null && this.graph.freehand.isDrawing()) || + (state != null && mxUtils.getValue(state.style, 'allowArrows', '1') == '0')) + { + this.reset(); + } + else + { + if (state != null && state.cell.geometry != null && state.cell.geometry.relative && + this.graph.model.isEdge(state.cell.parent)) + { + state = null; + } + + var timeOnTarget = null; + + // Time on target + if (this.prev != state || this.isActive()) + { + this.startTime = new Date().getTime(); + this.prev = state; + timeOnTarget = 0; + + if (this.updateThread != null) + { + window.clearTimeout(this.updateThread); + } + + if (state != null) + { + // Starts timer to update current state with no mouse events + this.updateThread = window.setTimeout(mxUtils.bind(this, function() + { + if (!this.isActive() && !this.graph.isMouseDown && + !this.graph.panningHandler.isActive()) + { + this.prev = state; + this.update(state, x, y); + } + }), this.updateDelay + 10); + } + } + else if (this.startTime != null) + { + timeOnTarget = new Date().getTime() - this.startTime; + } + + this.setDisplay(''); + + if (this.currentState != null && this.currentState != state && timeOnTarget < this.activationDelay && + this.bbox != null && !mxUtils.contains(this.bbox, x, y)) + { + this.reset(false); + } + else if (this.currentState != null || timeOnTarget > this.activationDelay) + { + if (this.currentState != state && ((timeOnTarget > this.updateDelay && state != null) || + this.bbox == null || x == null || y == null || !mxUtils.contains(this.bbox, x, y))) + { + if (state != null && this.graph.isEnabled()) + { + this.removeNodes(); + this.setCurrentState(state); + this.repaint(); + + // Resets connection points on other focused cells + if (this.graph.connectionHandler.constraintHandler.currentFocus != state) + { + this.graph.connectionHandler.constraintHandler.reset(); + } + } + else + { + this.reset(); + } + } + } + } +}; + +/** + * + */ +HoverIcons.prototype.setCurrentState = function(state) +{ + if (state.style['portConstraint'] != 'eastwest') + { + this.graph.container.appendChild(this.arrowUp); + this.graph.container.appendChild(this.arrowDown); + } + + this.graph.container.appendChild(this.arrowRight); + this.graph.container.appendChild(this.arrowLeft); + this.currentState = state; +}; + +/** + * Returns true if the given cell is a table. + */ +Graph.prototype.removeTextStyleForCell = function(cell, removeCellStyles) +{ + var style = this.getCurrentCellStyle(cell); + var result = false; + + this.getModel().beginUpdate(); + try + { + if (mxUtils.getValue(style, 'html', '0') == '1') + { + var label = this.convertValueToString(cell); + + if (mxUtils.getValue(style, 'nl2Br', '1') != '0') + { + // Removes newlines from HTML and converts breaks to newlines + // to match the HTML output in plain text + label = label.replace(/\n/g, '').replace(//g, '\n'); + } + + label = Editor.convertHtmlToText(label); + this.cellLabelChanged(cell, label); + result = true; + } + + if (removeCellStyles) + { + this.setCellStyles('fontSource', null, [cell]); + this.setCellStyles(mxConstants.STYLE_FONTFAMILY, null, [cell]); + this.setCellStyles(mxConstants.STYLE_FONTSIZE, null, [cell]); + this.setCellStyles(mxConstants.STYLE_FONTSTYLE, null, [cell]); + this.setCellStyles(mxConstants.STYLE_FONTCOLOR, null, [cell]); + this.setCellStyles(mxConstants.STYLE_LABEL_BORDERCOLOR, null, [cell]); + this.setCellStyles(mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, null, [cell]); + } + } + finally + { + this.getModel().endUpdate(); + } + + return result; +}; + +/** + * Returns true if the given cell is a table. + */ +Graph.prototype.createParent = function(parent, child, childCount, dx, dy) +{ + parent = this.cloneCell(parent); + + for (var i = 0; i < childCount; i++) + { + var clone = this.cloneCell(child); + var geo = this.getCellGeometry(clone) + + if (geo != null) + { + geo.x += i * dx; + geo.y += i * dy; + } + + parent.insert(clone); + } + + return parent; +}; + +/** + * Returns true if the given cell is a table. + */ +Graph.prototype.createTable = function(rowCount, colCount, w, h, title, startSize, tableStyle, rowStyle, cellStyle) +{ + w = (w != null) ? w : 60; + h = (h != null) ? h : 40; + startSize = (startSize != null) ? startSize : 30; + tableStyle = (tableStyle != null) ? tableStyle : 'shape=table;startSize=' + + ((title != null) ? startSize : '0') + ';container=1;collapsible=0;childLayout=tableLayout;'; + rowStyle = (rowStyle != null) ? rowStyle : 'shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;' + + 'top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'; + cellStyle = (cellStyle != null) ? cellStyle : 'shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;' + + 'overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;'; + + return this.createParent(this.createVertex(null, null, (title != null) ? title : '', + 0, 0, colCount * w, rowCount * h + ((title != null) ? startSize : 0), tableStyle), + this.createParent(this.createVertex(null, null, '', 0, 0, colCount * w, h, rowStyle), + this.createVertex(null, null, '', 0, 0, w, h, cellStyle), + colCount, w, 0), rowCount, 0, h); +}; + +/** + * Sets the values for the cells and rows in the given table and returns the table. + */ +Graph.prototype.setTableValues = function(table, values, rowValues) +{ + var rows = this.model.getChildCells(table, true); + + for (var i = 0; i < rows.length; i++) + { + if (rowValues != null) + { + rows[i].value = rowValues[i]; + } + + if (values != null) + { + var cells = this.model.getChildCells(rows[i], true); + + for (var j = 0; j < cells.length; j++) + { + if (values[i][j] != null) + { + cells[j].value = values[i][j]; + } + } + } + } + + return table; +}; + +/** + * + */ +Graph.prototype.createCrossFunctionalSwimlane = function(rowCount, colCount, w, h, title, tableStyle, rowStyle, firstCellStyle, cellStyle) +{ + w = (w != null) ? w : 120; + h = (h != null) ? h : 120; + + var s = 'collapsible=0;recursiveResize=0;expand=0;'; + tableStyle = (tableStyle != null) ? tableStyle : 'shape=table;childLayout=tableLayout;' + + ((title == null) ? 'startSize=0;fillColor=none;' : 'startSize=40;') + s; + rowStyle = (rowStyle != null) ? rowStyle : 'shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;' + + 'bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;startSize=40;' + s; + firstCellStyle = (firstCellStyle != null) ? firstCellStyle : 'swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;' + + 'connectable=0;fillColor=none;startSize=40;' + s; + cellStyle = (cellStyle != null) ? cellStyle : 'swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;connectable=0;strokeColor=inherit;' + + 'fillColor=none;startSize=0;' + s; + + var table = this.createVertex(null, null, (title != null) ? title : '', 0, 0, + colCount * w, rowCount * h, tableStyle); + var t = mxUtils.getValue(this.getCellStyle(table), mxConstants.STYLE_STARTSIZE, + mxConstants.DEFAULT_STARTSIZE); + table.geometry.width += t; + table.geometry.height += t; + + var row = this.createVertex(null, null, '', 0, t, colCount * w + t, h, rowStyle); + table.insert(this.createParent(row, this.createVertex(null, null, + '', t, 0, w, h, firstCellStyle), colCount, w, 0)); + + if (rowCount > 1) + { + row.geometry.y = h + t; + + return this.createParent(table, this.createParent(row, + this.createVertex(null, null, '', t, 0, w, h, cellStyle), + colCount, w, 0), rowCount - 1, 0, h); + } + else + { + return table; + } +}; + +/** + * Returns the row and column lines for the given table. + */ +Graph.prototype.visitTableCells = function(cell, visitor) +{ + var lastRow = null; + var rows = this.model.getChildCells(cell, true); + var start = this.getActualStartSize(cell, true); + + for (var i = 0; i < rows.length; i++) + { + var rowStart = this.getActualStartSize(rows[i], true); + var cols = this.model.getChildCells(rows[i], true); + var rowStyle = this.getCellStyle(rows[i], true); + var lastCol = null; + var row = []; + + for (var j = 0; j < cols.length; j++) + { + var geo = this.getCellGeometry(cols[j]); + var col = {cell: cols[j], rospan: 1, colspan: 1, row: i, col: j, geo: geo}; + geo = (geo.alternateBounds != null) ? geo.alternateBounds : geo; + col.point = new mxPoint(geo.width + (lastCol != null ? lastCol.point.x : start.x + rowStart.x), + geo.height + (lastRow != null && lastRow[0] != null ? lastRow[0].point.y : start.y + rowStart.y)); + col.actual = col; + + if (lastRow != null && lastRow[j] != null && lastRow[j].rowspan > 1) + { + col.rowspan = lastRow[j].rowspan - 1; + col.colspan = lastRow[j].colspan; + col.actual = lastRow[j].actual; + } + else + { + if (lastCol != null && lastCol.colspan > 1) + { + col.rowspan = lastCol.rowspan; + col.colspan = lastCol.colspan - 1; + col.actual = lastCol.actual; + } + else + { + var style = this.getCurrentCellStyle(cols[j], true); + + if (style != null) + { + col.rowspan = parseInt(style['rowspan'] || 1); + col.colspan = parseInt(style['colspan'] || 1); + } + } + } + + var head = mxUtils.getValue(rowStyle, mxConstants.STYLE_SWIMLANE_HEAD, 1) == 1 && + mxUtils.getValue(rowStyle, mxConstants.STYLE_STROKECOLOR, + mxConstants.NONE) != mxConstants.NONE; + + visitor(col, cols.length, rows.length, + start.x + ((head) ? rowStart.x : 0), + start.y + ((head) ? rowStart.y : 0)); + row.push(col); + lastCol = col; + } + + lastRow = row; + } + +}; + +/** + * Returns the row and column lines for the given table. + */ +Graph.prototype.getTableLines = function(cell, horizontal, vertical) +{ + var hl = []; + var vl = []; + + if (horizontal || vertical) + { + this.visitTableCells(cell, mxUtils.bind(this, function(iter, colCount, rowCount, x0, y0) + { + // Constructs horizontal lines + if (horizontal && iter.row < rowCount - 1) + { + if (hl[iter.row] == null) + { + hl[iter.row] = [new mxPoint(x0, iter.point.y)]; + } + + if (iter.rowspan > 1) + { + hl[iter.row].push(null); + } + + hl[iter.row].push(iter.point); + } + + // Constructs vertical lines + if (vertical && iter.col < colCount - 1) + { + if (vl[iter.col] == null) + { + vl[iter.col] = [new mxPoint(iter.point.x, y0)]; + } + + if (iter.colspan > 1) + { + vl[iter.col].push(null); + } + + vl[iter.col].push(iter.point); + } + })); + } + + return hl.concat(vl); +}; + +/** + * Returns true if the given cell is a table cell. + */ +Graph.prototype.isTableCell = function(cell) +{ + return this.model.isVertex(cell) && this.isTableRow(this.model.getParent(cell)); +}; + +/** + * Returns true if the given cell is a table row. + */ +Graph.prototype.isTableRow = function(cell) +{ + return this.model.isVertex(cell) && this.isTable(this.model.getParent(cell)); +}; + +/** + * Returns true if the given cell is a table. + */ +Graph.prototype.isTable = function(cell) +{ + var style = this.getCellStyle(cell); + + return style != null && style['childLayout'] == 'tableLayout'; +}; + +/** + * Returns true if the given cell is a table. + */ +Graph.prototype.isStack = function(cell) +{ + var style = this.getCellStyle(cell); + + return style != null && style['childLayout'] == 'stackLayout'; +}; + +/** + * Returns true if the given cell is a table row. + */ +Graph.prototype.isStackChild = function(cell) +{ + return this.model.isVertex(cell) && this.isStack(this.model.getParent(cell)); +}; + +/** + * Updates the row and table heights. + */ +Graph.prototype.setTableRowHeight = function(row, dy, extend) +{ + extend = (extend != null) ? extend : true; + var model = this.getModel(); + + model.beginUpdate(); + try + { + var rgeo = this.getCellGeometry(row); + + // Sets height of row + if (rgeo != null) + { + rgeo = rgeo.clone(); + rgeo.height += dy; + model.setGeometry(row, rgeo); + + var table = model.getParent(row); + var rows = model.getChildCells(table, true); + + // Shifts and resizes neighbor row + if (!extend) + { + var index = mxUtils.indexOf(rows, row); + + if (index < rows.length - 1) + { + var nextRow = rows[index + 1]; + var geo = this.getCellGeometry(nextRow); + + if (geo != null) + { + geo = geo.clone(); + geo.y += dy; + geo.height -= dy; + + model.setGeometry(nextRow, geo); + } + } + } + + // Updates height of table + var tgeo = this.getCellGeometry(table); + + if (tgeo != null) + { + // Always extends for last row + if (!extend) + { + extend = row == rows[rows.length - 1]; + } + + if (extend) + { + tgeo = tgeo.clone(); + tgeo.height += dy; + model.setGeometry(table, tgeo); + } + } + } + } + finally + { + model.endUpdate(); + } +}; + +/** + * Updates column width and row height. + */ +Graph.prototype.setTableColumnWidth = function(col, dx, extend) +{ + extend = (extend != null) ? extend : false; + + var model = this.getModel(); + var row = model.getParent(col); + var table = model.getParent(row); + var cells = model.getChildCells(row, true); + var index = mxUtils.indexOf(cells, col); + var lastColumn = index == cells.length - 1; + + model.beginUpdate(); + try + { + // Sets width of child cell + var rows = model.getChildCells(table, true); + + for (var i = 0; i < rows.length; i++) + { + row = rows[i]; + cells = model.getChildCells(row, true); + var cell = cells[index]; + var geo = this.getCellGeometry(cell); + + if (geo != null) + { + geo = geo.clone(); + geo.width += dx; + + if (geo.alternateBounds != null) + { + geo.alternateBounds.width += dx; + } + + model.setGeometry(cell, geo); + } + + // Shifts and resizes neighbor column + if (index < cells.length - 1) + { + cell = cells[index + 1]; + var geo = this.getCellGeometry(cell); + + if (geo != null) + { + geo = geo.clone(); + geo.x += dx; + + if (!extend) + { + geo.width -= dx; + + if (geo.alternateBounds != null) + { + geo.alternateBounds.width -= dx; + } + } + + model.setGeometry(cell, geo); + } + } + } + + if (lastColumn || extend) + { + // Updates width of table + var tgeo = this.getCellGeometry(table); + + if (tgeo != null) + { + tgeo = tgeo.clone(); + tgeo.width += dx; + model.setGeometry(table, tgeo); + } + } + + if (this.layoutManager != null) + { + this.layoutManager.executeLayout(table); + } + } + finally + { + model.endUpdate(); + } +}; + +/** + * Special Layout for tables. + */ +function TableLayout(graph) +{ + mxGraphLayout.call(this, graph); +}; + +/** + * Extends mxGraphLayout. + */ +TableLayout.prototype = new mxStackLayout(); +TableLayout.prototype.constructor = TableLayout; + +/** + * Function: isHorizontal + * + * Overrides stack layout to handle row reorder. + */ +TableLayout.prototype.isHorizontal = function() +{ + return false; +}; + +/** + * Function: isVertexIgnored + * + * Overrides to allow for table rows and cells. + */ +TableLayout.prototype.isVertexIgnored = function(vertex) +{ + return !this.graph.getModel().isVertex(vertex) || + !this.graph.isCellVisible(vertex); +}; + +/** + * Function: getSize + * + * Returns the total vertical or horizontal size of the given cells. + */ +TableLayout.prototype.getSize = function(cells, horizontal) +{ + var total = 0; + + for (var i = 0; i < cells.length; i++) + { + if (!this.isVertexIgnored(cells[i])) + { + var geo = this.graph.getCellGeometry(cells[i]); + + if (geo != null) + { + total += (horizontal) ? geo.width : geo.height; + } + } + } + + return total; +}; + +/** + * Function: getRowLayout + * + * Returns the column positions for the given row and table width. + */ +TableLayout.prototype.getRowLayout = function(row, width) +{ + var cells = this.graph.model.getChildCells(row, true); + var off = this.graph.getActualStartSize(row, true); + var sw = this.getSize(cells, true); + var rw = width - off.x - off.width; + var result = []; + var x = off.x; + + for (var i = 0; i < cells.length; i++) + { + var geo = this.graph.getCellGeometry(cells[i]); + + if (geo != null) + { + x += (geo.alternateBounds != null ? + geo.alternateBounds.width : + geo.width) * rw / sw; + result.push(Math.round(x)); + } + } + + return result; +}; + +/** + * Function: layoutRow + * + * Places the cells at the given positions in the given row. + */ +TableLayout.prototype.layoutRow = function(row, positions, height, tw) +{ + var model = this.graph.getModel(); + var cells = model.getChildCells(row, true); + var off = this.graph.getActualStartSize(row, true); + var x = off.x; + var sw = 0; + + if (positions != null) + { + positions = positions.slice(); + positions.splice(0, 0, off.x); + } + + for (var i = 0; i < cells.length; i++) + { + var geo = this.graph.getCellGeometry(cells[i]); + + if (geo != null) + { + geo = geo.clone(); + + geo.y = off.y; + geo.height = height - off.y - off.height; + + if (positions != null) + { + geo.x = positions[i]; + geo.width = positions[i + 1] - geo.x; + + // Fills with last geo if not enough cells + if (i == cells.length - 1 && i < positions.length - 2) + { + geo.width = tw - geo.x - off.x - off.width; + } + } + else + { + geo.x = x; + x += geo.width; + + if (i == cells.length - 1) + { + geo.width = tw - off.x - off.width - sw; + } + else + { + sw += geo.width; + } + } + + geo.alternateBounds = new mxRectangle(0, 0, geo.width, geo.height); + model.setGeometry(cells[i], geo); + } + } + + return sw; +}; + +/** + * Function: execute + * + * Implements . + */ +TableLayout.prototype.execute = function(parent) +{ + if (parent != null) + { + var offset = this.graph.getActualStartSize(parent, true); + var table = this.graph.getCellGeometry(parent); + var style = this.graph.getCellStyle(parent); + var resizeLastRow = mxUtils.getValue(style, + 'resizeLastRow', '0') == '1'; + var resizeLast = mxUtils.getValue(style, + 'resizeLast', '0') == '1'; + var fixedRows = mxUtils.getValue(style, + 'fixedRows', '0') == '1'; + var model = this.graph.getModel(); + var sw = 0; + + model.beginUpdate(); + try + { + var th = table.height - offset.y - offset.height; + var tw = table.width - offset.x - offset.width; + var rows = model.getChildCells(parent, true); + + // Updates row visibilities + for (var i = 0; i < rows.length; i++) + { + model.setVisible(rows[i], true); + } + + var sh = this.getSize(rows, false); + + if (th > 0 && tw > 0 && rows.length > 0 && sh > 0) + { + if (resizeLastRow) + { + var row = this.graph.getCellGeometry(rows[rows.length - 1]); + + if (row != null) + { + row = row.clone(); + row.height = th - sh + row.height; + model.setGeometry(rows[rows.length - 1], row); + } + } + + var pos = (resizeLast) ? null : this.getRowLayout(rows[0], tw); + var lastCells = []; + var y = offset.y; + + // Updates row geometries + for (var i = 0; i < rows.length; i++) + { + var row = this.graph.getCellGeometry(rows[i]); + + if (row != null) + { + row = row.clone(); + row.x = offset.x; + row.width = tw; + row.y = Math.round(y); + + if (resizeLastRow || fixedRows) + { + y += row.height; + } + else + { + y += (row.height / sh) * th; + } + + row.height = Math.round(y) - row.y; + model.setGeometry(rows[i], row); + } + + // Updates cell geometries + sw = Math.max(sw, this.layoutRow(rows[i], pos, row.height, tw, lastCells)); + } + + if (fixedRows && th < sh) + { + table = table.clone(); + table.height = y + offset.height; + model.setGeometry(parent, table); + } + + if (resizeLast && tw < sw + Graph.minTableColumnWidth) + { + table = table.clone(); + table.width = sw + offset.width + offset.x + Graph.minTableColumnWidth; + model.setGeometry(parent, table); + } + + // All geometries cloned at this point so can change in-place below + this.graph.visitTableCells(parent, mxUtils.bind(this, function(iter) + { + model.setVisible(iter.cell, iter.actual.cell == iter.cell); + + if (iter.actual.cell != iter.cell) + { + if (iter.actual.row == iter.row) + { + var g = (iter.geo.alternateBounds != null) ? + iter.geo.alternateBounds : iter.geo; + iter.actual.geo.width += g.width; + } + + if (iter.actual.col == iter.col) + { + var g = (iter.geo.alternateBounds != null) ? + iter.geo.alternateBounds : iter.geo; + iter.actual.geo.height += g.height; + } + } + })); + } + else + { + // Updates row visibilities + for (var i = 0; i < rows.length; i++) + { + model.setVisible(rows[i], false); + } + } + } + finally + { + model.endUpdate(); + } + } +}; + +(function() +{ + /** + * Reset the list of processed edges. + */ + var mxGraphViewResetValidationState = mxGraphView.prototype.resetValidationState; + mxGraphView.prototype.resetValidationState = function() + { + mxGraphViewResetValidationState.apply(this, arguments); + + this.validEdges = []; + }; + + /** + * Updates jumps for valid edges and repaints if needed. + */ + var mxGraphViewValidateCellState = mxGraphView.prototype.validateCellState; + mxGraphView.prototype.validateCellState = function(cell, recurse) + { + recurse = (recurse != null) ? recurse : true; + var state = this.getState(cell); + + // Forces repaint if jumps change on a valid edge + if (state != null && recurse && this.graph.model.isEdge(state.cell) && + state.style != null && state.style[mxConstants.STYLE_CURVED] != 1 && + !state.invalid && this.updateLineJumps(state)) + { + this.graph.cellRenderer.redraw(state, false, this.isRendering()); + } + + state = mxGraphViewValidateCellState.apply(this, arguments); + + // Adds to the list of edges that may intersect with later edges + if (state != null && recurse && this.graph.model.isEdge(state.cell) && + state.style != null && state.style[mxConstants.STYLE_CURVED] != 1) + { + // LATER: Reuse jumps for valid edges + this.validEdges.push(state); + } + + return state; + }; + + /** + * Overrides paint to add flowAnimation style. + */ + var mxShapePaint = mxShape.prototype.paint; + + mxShape.prototype.paint = function() + { + mxShapePaint.apply(this, arguments); + + if (this.state != null && this.node != null && + this.state.view.graph.enableFlowAnimation && + this.state.view.graph.model.isEdge(this.state.cell) && + mxUtils.getValue(this.state.style, 'flowAnimation', '0') == '1') + { + var paths = this.node.getElementsByTagName('path'); + + if (paths.length > 1) + { + if (mxUtils.getValue(this.state.style, mxConstants.STYLE_DASHED, '0') != '1') + { + paths[1].setAttribute('stroke-dasharray', (this.state.view.scale * 8)); + } + + var anim = this.state.view.graph.getFlowAnimationStyle(); + + if (anim != null) + { + paths[1].setAttribute('class', anim.getAttribute('id')); + } + } + } + }; + + /** + * Forces repaint if routed points have changed. + */ + var mxCellRendererIsShapeInvalid = mxCellRenderer.prototype.isShapeInvalid; + mxCellRenderer.prototype.isShapeInvalid = function(state, shape) + { + return mxCellRendererIsShapeInvalid.apply(this, arguments) || + (state.routedPoints != null && shape.routedPoints != null && + !mxUtils.equalPoints(shape.routedPoints, state.routedPoints)) + }; + + /** + * Updates jumps for invalid edges. + */ + var mxGraphViewUpdateCellState = mxGraphView.prototype.updateCellState; + mxGraphView.prototype.updateCellState = function(state) + { + mxGraphViewUpdateCellState.apply(this, arguments); + + // Updates jumps on invalid edge before repaint + if (this.graph.model.isEdge(state.cell) && + state.style[mxConstants.STYLE_CURVED] != 1) + { + this.updateLineJumps(state); + } + }; + + /** + * Updates the jumps between given state and processed edges. + */ + mxGraphView.prototype.updateLineJumps = function(state) + { + var pts = state.absolutePoints; + + if (Graph.lineJumpsEnabled) + { + var changed = state.routedPoints != null; + var actual = null; + + if (pts != null && this.validEdges != null && + mxUtils.getValue(state.style, 'jumpStyle', 'none') !== 'none') + { + var thresh = 0.5 * this.scale; + changed = false; + actual = []; + + // Type 0 means normal waypoint, 1 means jump + function addPoint(type, x, y) + { + var rpt = new mxPoint(x, y); + rpt.type = type; + + actual.push(rpt); + var curr = (state.routedPoints != null) ? state.routedPoints[actual.length - 1] : null; + + return curr == null || curr.type != type || curr.x != x || curr.y != y; + }; + + for (var i = 0; i < pts.length - 1; i++) + { + var p1 = pts[i + 1]; + var p0 = pts[i]; + var list = []; + + // Ignores waypoints on straight segments + var pn = pts[i + 2]; + + while (i < pts.length - 2 && + mxUtils.ptSegDistSq(p0.x, p0.y, pn.x, pn.y, + p1.x, p1.y) < 1 * this.scale * this.scale) + { + p1 = pn; + i++; + pn = pts[i + 2]; + } + + changed = addPoint(0, p0.x, p0.y) || changed; + + // Processes all previous edges + for (var e = 0; e < this.validEdges.length; e++) + { + var state2 = this.validEdges[e]; + var pts2 = state2.absolutePoints; + + if (pts2 != null && mxUtils.intersects(state, state2) && state2.style['noJump'] != '1') + { + // Compares each segment of the edge with the current segment + for (var j = 0; j < pts2.length - 1; j++) + { + var p3 = pts2[j + 1]; + var p2 = pts2[j]; + + // Ignores waypoints on straight segments + pn = pts2[j + 2]; + + while (j < pts2.length - 2 && + mxUtils.ptSegDistSq(p2.x, p2.y, pn.x, pn.y, + p3.x, p3.y) < 1 * this.scale * this.scale) + { + p3 = pn; + j++; + pn = pts2[j + 2]; + } + + var pt = mxUtils.intersection(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); + + // Handles intersection between two segments + if (pt != null && (Math.abs(pt.x - p0.x) > thresh || + Math.abs(pt.y - p0.y) > thresh) && + (Math.abs(pt.x - p1.x) > thresh || + Math.abs(pt.y - p1.y) > thresh)) + { + var dx = pt.x - p0.x; + var dy = pt.y - p0.y; + var temp = {distSq: dx * dx + dy * dy, x: pt.x, y: pt.y}; + + // Intersections must be ordered by distance from start of segment + for (var t = 0; t < list.length; t++) + { + if (list[t].distSq > temp.distSq) + { + list.splice(t, 0, temp); + temp = null; + + break; + } + } + + // Ignores multiple intersections at segment joint + if (temp != null && (list.length == 0 || + list[list.length - 1].x !== temp.x || + list[list.length - 1].y !== temp.y)) + { + list.push(temp); + } + } + } + } + } + + // Adds ordered intersections to routed points + for (var j = 0; j < list.length; j++) + { + changed = addPoint(1, list[j].x, list[j].y) || changed; + } + } + + var pt = pts[pts.length - 1]; + changed = addPoint(0, pt.x, pt.y) || changed; + } + + state.routedPoints = actual; + + return changed; + } + else + { + return false; + } + }; + + /** + * Overrides painting the actual shape for taking into account jump style. + */ + var mxConnectorPaintLine = mxConnector.prototype.paintLine; + + mxConnector.prototype.paintLine = function (c, absPts, rounded) + { + // Required for checking dirty state + this.routedPoints = (this.state != null) ? this.state.routedPoints : null; + + if (this.outline || this.state == null || this.style == null || + this.state.routedPoints == null || this.state.routedPoints.length == 0) + { + mxConnectorPaintLine.apply(this, arguments); + } + else + { + var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, + mxConstants.LINE_ARCSIZE) / 2; + var size = (parseInt(mxUtils.getValue(this.style, 'jumpSize', + Graph.defaultJumpSize)) - 2) / 2 + this.strokewidth; + var style = mxUtils.getValue(this.style, 'jumpStyle', 'none'); + var moveTo = true; + var last = null; + var len = null; + var pts = []; + var n = null; + c.begin(); + + for (var i = 0; i < this.state.routedPoints.length; i++) + { + var rpt = this.state.routedPoints[i]; + var pt = new mxPoint(rpt.x / this.scale, rpt.y / this.scale); + + // Takes first and last point from passed-in array + if (i == 0) + { + pt = absPts[0]; + } + else if (i == this.state.routedPoints.length - 1) + { + pt = absPts[absPts.length - 1]; + } + + var done = false; + + // Type 1 is an intersection + if (last != null && rpt.type == 1) + { + // Checks if next/previous points are too close + var next = this.state.routedPoints[i + 1]; + var dx = next.x / this.scale - pt.x; + var dy = next.y / this.scale - pt.y; + var dist = dx * dx + dy * dy; + + if (n == null) + { + n = new mxPoint(pt.x - last.x, pt.y - last.y); + len = Math.sqrt(n.x * n.x + n.y * n.y); + + if (len > 0) + { + n.x = n.x * size / len; + n.y = n.y * size / len; + } + else + { + n = null; + } + } + + if (dist > size * size && len > 0) + { + var dx = last.x - pt.x; + var dy = last.y - pt.y; + var dist = dx * dx + dy * dy; + + if (dist > size * size) + { + var p0 = new mxPoint(pt.x - n.x, pt.y - n.y); + var p1 = new mxPoint(pt.x + n.x, pt.y + n.y); + pts.push(p0); + + this.addPoints(c, pts, rounded, arcSize, false, null, moveTo); + + var f = (Math.round(n.x) < 0 || (Math.round(n.x) == 0 + && Math.round(n.y) <= 0)) ? 1 : -1; + moveTo = false; + + if (style == 'sharp') + { + c.lineTo(p0.x - n.y * f, p0.y + n.x * f); + c.lineTo(p1.x - n.y * f, p1.y + n.x * f); + c.lineTo(p1.x, p1.y); + } + else if (style == 'line') + { + c.moveTo(p0.x + n.y * f, p0.y - n.x * f); + c.lineTo(p0.x - n.y * f, p0.y + n.x * f); + c.moveTo(p1.x - n.y * f, p1.y + n.x * f); + c.lineTo(p1.x + n.y * f, p1.y - n.x * f); + c.moveTo(p1.x, p1.y); + } + else if (style == 'arc') + { + f *= 1.3; + c.curveTo(p0.x - n.y * f, p0.y + n.x * f, + p1.x - n.y * f, p1.y + n.x * f, + p1.x, p1.y); + } + else + { + c.moveTo(p1.x, p1.y); + moveTo = true; + } + + pts = [p1]; + done = true; + } + } + } + else + { + n = null; + } + + if (!done) + { + pts.push(pt); + last = pt; + } + } + + this.addPoints(c, pts, rounded, arcSize, false, null, moveTo); + c.stroke(); + } + }; + + /** + * Adds support for centerPerimeter which is a special case of a fixed point perimeter. + */ + var mxGraphViewGetFixedTerminalPoint = mxGraphView.prototype.getFixedTerminalPoint; + + mxGraphView.prototype.getFixedTerminalPoint = function(edge, terminal, source, constraint) + { + if (terminal != null && terminal.style[mxConstants.STYLE_PERIMETER] == 'centerPerimeter') + { + return new mxPoint(terminal.getCenterX(), terminal.getCenterY()); + } + else + { + return mxGraphViewGetFixedTerminalPoint.apply(this, arguments); + } + }; + + /** + * Adds support for snapToPoint style. + */ + var mxGraphViewUpdateFloatingTerminalPoint = mxGraphView.prototype.updateFloatingTerminalPoint; + + mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source) + { + if (start != null && edge != null && + (start.style['snapToPoint'] == '1' || + edge.style['snapToPoint'] == '1')) + { + start = this.getTerminalPort(edge, start, source); + var next = this.getNextPoint(edge, end, source); + + var orth = this.graph.isOrthogonal(edge); + var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0')); + var center = new mxPoint(start.getCenterX(), start.getCenterY()); + + if (alpha != 0) + { + var cos = Math.cos(-alpha); + var sin = Math.sin(-alpha); + next = mxUtils.getRotatedPoint(next, cos, sin, center); + } + + var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0); + border += parseFloat(edge.style[(source) ? + mxConstants.STYLE_SOURCE_PERIMETER_SPACING : + mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0); + var pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border); + + if (alpha != 0) + { + var cos = Math.cos(alpha); + var sin = Math.sin(alpha); + pt = mxUtils.getRotatedPoint(pt, cos, sin, center); + } + + edge.setAbsoluteTerminalPoint(this.snapToAnchorPoint(edge, start, end, source, pt), source); + } + else + { + mxGraphViewUpdateFloatingTerminalPoint.apply(this, arguments); + } + }; + + mxGraphView.prototype.snapToAnchorPoint = function(edge, start, end, source, pt) + { + if (start != null && edge != null) + { + var constraints = this.graph.getAllConnectionConstraints(start) + var nearest = null; + var dist = null; + + if (constraints != null) + { + for (var i = 0; i < constraints.length; i++) + { + var cp = this.graph.getConnectionPoint(start, constraints[i]); + + if (cp != null) + { + var tmp = (cp.x - pt.x) * (cp.x - pt.x) + (cp.y - pt.y) * (cp.y - pt.y); + + if (dist == null || tmp < dist) + { + nearest = cp; + dist = tmp; + } + } + } + } + + if (nearest != null) + { + pt = nearest; + } + } + + return pt; + }; + + /** + * Adds support for placeholders in text elements of shapes. + */ + var mxStencilEvaluateTextAttribute = mxStencil.prototype.evaluateTextAttribute; + + mxStencil.prototype.evaluateTextAttribute = function(node, attribute, shape) + { + var result = mxStencilEvaluateTextAttribute.apply(this, arguments); + var placeholders = node.getAttribute('placeholders'); + + if (placeholders == '1' && shape.state != null) + { + result = shape.state.view.graph.replacePlaceholders(shape.state.cell, result); + } + + return result; + }; + + /** + * Adds custom stencils defined via shape=stencil(value) style. The value is a base64 encoded, compressed and + * URL encoded XML definition of the shape according to the stencil definition language of mxGraph. + * + * Needs to be in this file to make sure its part of the embed client code. Also the check for ZLib is + * different than for the Editor code. + */ + var mxCellRendererCreateShape = mxCellRenderer.prototype.createShape; + mxCellRenderer.prototype.createShape = function(state) + { + if (state.style != null && typeof(pako) !== 'undefined') + { + var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null); + + // Extracts and decodes stencil XML if shape has the form shape=stencil(value) + if (shape != null && typeof shape === 'string' && shape.substring(0, 8) == 'stencil(') + { + try + { + var stencil = shape.substring(8, shape.length - 1); + var doc = mxUtils.parseXml(Graph.decompress(stencil)); + + return new mxShape(new mxStencil(doc.documentElement)); + } + catch (e) + { + if (window.console != null) + { + console.log('Error in shape: ' + e); + } + } + } + } + + return mxCellRendererCreateShape.apply(this, arguments); + }; +})(); + +/** + * Overrides stencil registry for dynamic loading of stencils. + */ +/** + * Maps from library names to an array of Javascript filenames, + * which are synchronously loaded. Currently only stencil files + * (.xml) and JS files (.js) are supported. + * IMPORTANT: For embedded diagrams to work entries must also + * be added in EmbedServlet.java. + */ +mxStencilRegistry.libraries = {}; + +/** + * Global switch to disable dynamic loading. + */ +mxStencilRegistry.dynamicLoading = true; + +/** + * Global switch to disable eval for JS (preload all JS instead). + */ +mxStencilRegistry.allowEval = true; + +/** + * Stores all package names that have been dynamically loaded. + * Each package is only loaded once. + */ +mxStencilRegistry.packages = []; + +/** + * Stores all package names that have been dynamically loaded. + * Each package is only loaded once. + */ +mxStencilRegistry.filesLoaded = {}; + +// Extends the default stencil registry to add dynamic loading +mxStencilRegistry.getStencil = function(name) +{ + var result = mxStencilRegistry.stencils[name]; + + if (result == null && mxCellRenderer.defaultShapes[name] == null && mxStencilRegistry.dynamicLoading) + { + var basename = mxStencilRegistry.getBasenameForStencil(name); + + // Loads stencil files and tries again + if (basename != null) + { + var libs = mxStencilRegistry.libraries[basename]; + + if (libs != null) + { + if (mxStencilRegistry.packages[basename] == null) + { + for (var i = 0; i < libs.length; i++) + { + var fname = libs[i]; + + if (!mxStencilRegistry.filesLoaded[fname]) + { + mxStencilRegistry.filesLoaded[fname] = true; + + if (fname.toLowerCase().substring(fname.length - 4, fname.length) == '.xml') + { + mxStencilRegistry.loadStencilSet(fname, null); + } + else if (fname.toLowerCase().substring(fname.length - 3, fname.length) == '.js') + { + try + { + if (mxStencilRegistry.allowEval) + { + var req = mxUtils.load(fname); + + if (req != null && req.getStatus() >= 200 && req.getStatus() <= 299) + { + eval.call(window, req.getText()); + } + } + } + catch (e) + { + if (window.console != null) + { + console.log('error in getStencil:', name, basename, libs, fname, e); + } + } + } + else + { + // FIXME: This does not yet work as the loading is triggered after + // the shape was used in the graph, at which point the keys have + // typically been translated in the calling method. + //mxResources.add(fname); + } + } + } + + mxStencilRegistry.packages[basename] = 1; + } + } + else + { + // Replaces '_-_' with '_' + basename = basename.replace('_-_', '_'); + mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/' + basename + '.xml', null); + } + + result = mxStencilRegistry.stencils[name]; + } + } + + return result; +}; + +// Returns the basename for the given stencil or null if no file must be +// loaded to render the given stencil. +mxStencilRegistry.getBasenameForStencil = function(name) +{ + var tmp = null; + + if (name != null && typeof name === 'string') + { + var parts = name.split('.'); + + if (parts.length > 0 && parts[0] == 'mxgraph') + { + tmp = parts[1]; + + for (var i = 2; i < parts.length - 1; i++) + { + tmp += '/' + parts[i]; + } + } + } + + return tmp; +}; + +// Loads the given stencil set +mxStencilRegistry.loadStencilSet = function(stencilFile, postStencilLoad, force, async) +{ + force = (force != null) ? force : false; + + // Uses additional cache for detecting previous load attempts + var xmlDoc = mxStencilRegistry.packages[stencilFile]; + + if (force || xmlDoc == null) + { + var install = false; + + if (xmlDoc == null) + { + try + { + if (async) + { + mxStencilRegistry.loadStencil(stencilFile, mxUtils.bind(this, function(xmlDoc2) + { + if (xmlDoc2 != null && xmlDoc2.documentElement != null) + { + mxStencilRegistry.packages[stencilFile] = xmlDoc2; + install = true; + mxStencilRegistry.parseStencilSet(xmlDoc2.documentElement, postStencilLoad, install); + } + })); + + return; + } + else + { + xmlDoc = mxStencilRegistry.loadStencil(stencilFile); + mxStencilRegistry.packages[stencilFile] = xmlDoc; + install = true; + } + } + catch (e) + { + if (window.console != null) + { + console.log('error in loadStencilSet:', stencilFile, e); + } + } + } + + if (xmlDoc != null && xmlDoc.documentElement != null) + { + mxStencilRegistry.parseStencilSet(xmlDoc.documentElement, postStencilLoad, install); + } + } +}; + +// Loads the given stencil XML file. +mxStencilRegistry.loadStencil = function(filename, fn) +{ + if (fn != null) + { + var req = mxUtils.get(filename, mxUtils.bind(this, function(req) + { + fn((req.getStatus() >= 200 && req.getStatus() <= 299) ? req.getXml() : null); + }), mxUtils.bind(this, function(req) + { + fn(null); + })); + } + else + { + return mxUtils.load(filename).getXml(); + } +}; + +// Takes array of strings +mxStencilRegistry.parseStencilSets = function(stencils) +{ + for (var i = 0; i < stencils.length; i++) + { + mxStencilRegistry.parseStencilSet(mxUtils.parseXml(stencils[i]).documentElement); + } +}; + +// Parses the given stencil set +mxStencilRegistry.parseStencilSet = function(root, postStencilLoad, install) +{ + if (root.nodeName == 'stencils') + { + var shapes = root.firstChild; + + while (shapes != null) + { + if (shapes.nodeName == 'shapes') + { + mxStencilRegistry.parseStencilSet(shapes, postStencilLoad, install); + } + + shapes = shapes.nextSibling; + } + } + else + { + install = (install != null) ? install : true; + var shape = root.firstChild; + var packageName = ''; + var name = root.getAttribute('name'); + + if (name != null) + { + packageName = name + '.'; + } + + while (shape != null) + { + if (shape.nodeType == mxConstants.NODETYPE_ELEMENT) + { + name = shape.getAttribute('name'); + + if (name != null) + { + packageName = packageName.toLowerCase(); + var stencilName = name.replace(/ /g,"_"); + + if (install) + { + mxStencilRegistry.addStencil(packageName + stencilName.toLowerCase(), new mxStencil(shape)); + } + + if (postStencilLoad != null) + { + var w = shape.getAttribute('w'); + var h = shape.getAttribute('h'); + + w = (w == null) ? 80 : parseInt(w, 10); + h = (h == null) ? 80 : parseInt(h, 10); + + postStencilLoad(packageName, stencilName, name, w, h); + } + } + } + + shape = shape.nextSibling; + } + } +}; + +/** + * These overrides are only added if mxVertexHandler is defined (ie. not in embedded graph) + */ +if (typeof mxVertexHandler !== 'undefined') +{ + (function() + { + // Sets colors for handles + mxConstants.HANDLE_FILLCOLOR = '#29b6f2'; + mxConstants.HANDLE_STROKECOLOR = '#0088cf'; + mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff'; + mxConstants.OUTLINE_COLOR = '#00a8ff'; + mxConstants.OUTLINE_HANDLE_FILLCOLOR = '#99ccff'; + mxConstants.OUTLINE_HANDLE_STROKECOLOR = '#00a8ff'; + mxConstants.CONNECT_HANDLE_FILLCOLOR = '#cee7ff'; + mxConstants.EDGE_SELECTION_COLOR = '#00a8ff'; + mxConstants.DEFAULT_VALID_COLOR = '#00a8ff'; + mxConstants.LABEL_HANDLE_FILLCOLOR = '#cee7ff'; + mxConstants.GUIDE_COLOR = '#0088cf'; + mxConstants.HIGHLIGHT_STROKEWIDTH = 5; + mxConstants.HIGHLIGHT_OPACITY = 50; + mxConstants.HIGHLIGHT_SIZE = 5; + + // Sets window decoration icons + mxWindow.prototype.closeImage = Graph.createSvgImage(18, 10, + '').src; + mxWindow.prototype.minimizeImage = Graph.createSvgImage(14, 10, + '').src; + mxWindow.prototype.normalizeImage = Graph.createSvgImage(14, 10, + '').src; + mxWindow.prototype.resizeImage = Graph.createSvgImage(10, 10, + '').src; + + // Enables snapping to off-grid terminals for edge waypoints + mxEdgeHandler.prototype.snapToTerminals = true; + + // Enables guides + mxGraphHandler.prototype.guidesEnabled = true; + + // Removes parents where all child cells are moved out + mxGraphHandler.prototype.removeEmptyParents = true; + + // Enables fading of rubberband + mxRubberband.prototype.fadeOut = true; + + // Alt-move disables guides + mxGuide.prototype.isEnabledForEvent = function(evt) + { + return !mxEvent.isAltDown(evt); + }; + + // Ignores all table cells in layouts + var graphLayoutIsVertexIgnored = mxGraphLayout.prototype.isVertexIgnored; + mxGraphLayout.prototype.isVertexIgnored = function(vertex) + { + return graphLayoutIsVertexIgnored.apply(this, arguments) || + this.graph.isTableRow(vertex) || this.graph.isTableCell(vertex); + }; + + // Adds support for ignoreEdge style + var graphLayoutIsEdgeIgnored = mxGraphLayout.prototype.isEdgeIgnored; + mxGraphLayout.prototype.isEdgeIgnored = function(edge) + { + return graphLayoutIsEdgeIgnored.apply(this, arguments) || + this.graph.isEdgeIgnored(edge); + }; + + // Extends connection handler to enable ctrl+drag for cloning source cell + // since copyOnConnect is now disabled by default + var mxConnectionHandlerCreateTarget = mxConnectionHandler.prototype.isCreateTarget; + mxConnectionHandler.prototype.isCreateTarget = function(evt) + { + return this.graph.isCloneEvent(evt) != mxConnectionHandlerCreateTarget.apply(this, arguments); + }; + + // Overrides highlight shape for connection points + mxConstraintHandler.prototype.createHighlightShape = function() + { + var hl = new mxEllipse(null, this.highlightColor, this.highlightColor, 0); + hl.opacity = mxConstants.HIGHLIGHT_OPACITY; + + return hl; + }; + + // Overrides edge preview to use current edge shape and default style + mxConnectionHandler.prototype.livePreview = true; + mxConnectionHandler.prototype.cursor = 'crosshair'; + + // Uses current edge style for connect preview + mxConnectionHandler.prototype.createEdgeState = function(me) + { + var style = this.graph.createCurrentEdgeStyle(); + var edge = this.graph.createEdge(null, null, null, null, null, style); + var state = new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge)); + + for (var key in this.graph.currentEdgeStyle) + { + state.style[key] = this.graph.currentEdgeStyle[key]; + } + + // Applies newEdgeStyle for preview + if (this.previous != null) + { + var temp = this.previous.style['newEdgeStyle']; + + if (temp != null) + { + try + { + var styles = JSON.parse(temp); + + for (var key in styles) + { + state.style[key] = styles[key]; + } + } + catch (e) + { + // ignore + } + } + } + + state.style = this.graph.postProcessCellStyle(state.cell, state.style); + + return state; + }; + + // Overrides dashed state with current edge style + var connectionHandlerCreateShape = mxConnectionHandler.prototype.createShape; + mxConnectionHandler.prototype.createShape = function() + { + var shape = connectionHandlerCreateShape.apply(this, arguments); + + shape.isDashed = this.graph.currentEdgeStyle[mxConstants.STYLE_DASHED] == '1'; + + return shape; + } + + // Overrides live preview to keep current style + mxConnectionHandler.prototype.updatePreview = function(valid) + { + // do not change color of preview + }; + + // Overrides connection handler to ignore edges instead of not allowing connections + var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker; + mxConnectionHandler.prototype.createMarker = function() + { + var marker = mxConnectionHandlerCreateMarker.apply(this, arguments); + + var markerGetCell = marker.getCell; + marker.getCell = mxUtils.bind(this, function(me) + { + var result = markerGetCell.apply(this, arguments); + + this.error = null; + + return result; + }); + + return marker; + }; + + /** + * + */ + Graph.prototype.defaultVertexStyle = {}; + + /** + * Contains the default style for edges. + */ + Graph.prototype.defaultEdgeStyle = {'edgeStyle': 'orthogonalEdgeStyle', 'rounded': '0', + 'jettySize': 'auto', 'orthogonalLoop': '1'}; + + /** + * Returns the current edge style as a string. + */ + Graph.prototype.createCurrentEdgeStyle = function() + { + var style = 'edgeStyle=' + (this.currentEdgeStyle['edgeStyle'] || 'none') + ';'; + var keys = ['shape', 'curved', 'rounded', 'comic', 'sketch', 'fillWeight', 'hachureGap', + 'hachureAngle', 'jiggle', 'disableMultiStroke', 'disableMultiStrokeFill', 'fillStyle', + 'curveFitting', 'simplification', 'comicStyle', 'jumpStyle', 'jumpSize']; + + for (var i = 0; i < keys.length; i++) + { + if (this.currentEdgeStyle[keys[i]] != null) + { + style += keys[i] + '=' + this.currentEdgeStyle[keys[i]] + ';'; + } + } + + // Overrides the global default to match the default edge style + if (this.currentEdgeStyle['orthogonalLoop'] != null) + { + style += 'orthogonalLoop=' + this.currentEdgeStyle['orthogonalLoop'] + ';'; + } + else if (Graph.prototype.defaultEdgeStyle['orthogonalLoop'] != null) + { + style += 'orthogonalLoop=' + Graph.prototype.defaultEdgeStyle['orthogonalLoop'] + ';'; + } + + // Overrides the global default to match the default edge style + if (this.currentEdgeStyle['jettySize'] != null) + { + style += 'jettySize=' + this.currentEdgeStyle['jettySize'] + ';'; + } + else if (Graph.prototype.defaultEdgeStyle['jettySize'] != null) + { + style += 'jettySize=' + Graph.prototype.defaultEdgeStyle['jettySize'] + ';'; + } + + // Special logic for custom property of elbowEdgeStyle + if (this.currentEdgeStyle['edgeStyle'] == 'elbowEdgeStyle' && this.currentEdgeStyle['elbow'] != null) + { + style += 'elbow=' + this.currentEdgeStyle['elbow'] + ';'; + } + + if (this.currentEdgeStyle['html'] != null) + { + style += 'html=' + this.currentEdgeStyle['html'] + ';'; + } + else + { + style += 'html=1;'; + } + + return style; + }; + + /** + * Hook for subclassers. + */ + Graph.prototype.getPagePadding = function() + { + return new mxPoint(0, 0); + }; + + /** + * Loads the stylesheet for this graph. + */ + Graph.prototype.loadStylesheet = function() + { + var node = (this.themes != null) ? this.themes[this.defaultThemeName] : + (!mxStyleRegistry.dynamicLoading) ? null : + mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement(); + + if (node != null) + { + var dec = new mxCodec(node.ownerDocument); + dec.decode(node, this.getStylesheet()); + } + }; + + /** + * Creates lookup from object IDs to cell IDs. + */ + Graph.prototype.createCellLookup = function(cells, lookup) + { + lookup = (lookup != null) ? lookup : new Object(); + + for (var i = 0; i < cells.length; i++) + { + var cell = cells[i]; + lookup[mxObjectIdentity.get(cell)] = cell.getId(); + var childCount = this.model.getChildCount(cell); + + for (var j = 0; j < childCount; j++) + { + this.createCellLookup([this.model.getChildAt(cell, j)], lookup); + } + } + + return lookup; + }; + + /** + * Creates lookup from original to cloned cell IDs where mapping is + * the mapping used in cloneCells and lookup is a mapping from + * object IDs to cell IDs. + */ + Graph.prototype.createCellMapping = function(mapping, lookup, cellMapping) + { + cellMapping = (cellMapping != null) ? cellMapping : new Object(); + + for (var objectId in mapping) + { + var cellId = lookup[objectId]; + + if (cellMapping[cellId] == null) + { + // Uses empty string if clone ID was null which means + // the cell was cloned but not inserted into the model. + cellMapping[cellId] = mapping[objectId].getId() || ''; + } + } + + return cellMapping; + }; + + /** + * + */ + Graph.prototype.importGraphModel = function(node, dx, dy, crop) + { + dx = (dx != null) ? dx : 0; + dy = (dy != null) ? dy : 0; + + var codec = new mxCodec(node.ownerDocument); + var tempModel = new mxGraphModel(); + codec.decode(node, tempModel); + var cells = [] + + // Clones cells to remove invalid edges + var cloneMap = new Object(); + var cellMapping = new Object(); + var layers = tempModel.getChildren(this.cloneCell(tempModel.root, + this.isCloneInvalidEdges(), cloneMap)); + + if (layers != null) + { + // Creates lookup from object IDs to cell IDs + var lookup = this.createCellLookup([tempModel.root]); + + // Uses copy as layers are removed from array inside loop + layers = layers.slice(); + + this.model.beginUpdate(); + try + { + // Merges into unlocked current layer if one layer is pasted + if (layers.length == 1 && !this.isCellLocked(this.getDefaultParent())) + { + var children = tempModel.getChildren(layers[0]); + + if (children != null) + { + cells = this.moveCells(children, + dx, dy, false, this.getDefaultParent()); + + // Imported default parent maps to local default parent + cellMapping[tempModel.getChildAt(tempModel.root, 0).getId()] = + this.getDefaultParent().getId(); + } + } + else + { + for (var i = 0; i < layers.length; i++) + { + var children = this.model.getChildren(this.moveCells( + [layers[i]], dx, dy, false, this.model.getRoot())[0]); + + if (children != null) + { + cells = cells.concat(children); + } + } + } + + if (cells != null) + { + // Adds mapping for all cloned entries from imported to local cell ID + this.createCellMapping(cloneMap, lookup, cellMapping); + this.updateCustomLinks(cellMapping, cells); + + if (crop) + { + if (this.isGridEnabled()) + { + dx = this.snap(dx); + dy = this.snap(dy); + } + + var bounds = this.getBoundingBoxFromGeometry(cells, true); + + if (bounds != null) + { + this.moveCells(cells, dx - bounds.x, dy - bounds.y); + } + } + } + } + finally + { + this.model.endUpdate(); + } + } + + return cells; + }; + + /** + * Translates this point by the given vector. + * + * @param {number} dx X-coordinate of the translation. + * @param {number} dy Y-coordinate of the translation. + */ + Graph.prototype.encodeCells = function(cells) + { + var cloneMap = new Object(); + var clones = this.cloneCells(cells, null, cloneMap); + + // Creates a dictionary for fast lookups + var dict = new mxDictionary(); + + for (var i = 0; i < cells.length; i++) + { + dict.put(cells[i], true); + } + + var codec = new mxCodec(); + 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 = this.view.getState(cells[i]); + + if (state != null) + { + var geo = this.getCellGeometry(clones[i]); + + if (geo != null && geo.relative && !this.model.isEdge(cells[i]) && + dict.get(this.model.getParent(cells[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; + } + } + } + + this.updateCustomLinks(this.createCellMapping(cloneMap, + this.createCellLookup(cells)), clones); + + return codec.encode(model); + }; + + /** + * Overridden to use table cell instead of table as parent. + */ + Graph.prototype.isSwimlane = function(cell, ignoreState) + { + var shape = null; + + if (cell != null && !this.model.isEdge(cell) && + this.model.getParent(cell) != + this.model.getRoot()) + { + var style = this.getCurrentCellStyle(cell, ignoreState) + shape = style[mxConstants.STYLE_SHAPE]; + } + + return shape == mxConstants.SHAPE_SWIMLANE || + shape == 'table' || shape == 'tableRow'; + }; + + /** + * Overridden to check table cells and rows. + */ + var graphIsCellEditable = Graph.prototype.isCellEditable; + Graph.prototype.isCellEditable = function(cell) + { + if (cell == null || !graphIsCellEditable.apply(this, arguments)) + { + return false; + } + else if (this.isTableCell(cell) || this.isTableRow(cell)) + { + return this.isCellEditable(this.model.getParent(cell)); + } + else + { + return true; + } + }; + + /** + * Overridden to check table cells and rows. + */ + var graphIsCellMovable = Graph.prototype.isCellMovable; + Graph.prototype.isCellMovable = function(cell) + { + if (cell == null || !graphIsCellMovable.apply(this, arguments)) + { + return false; + } + else if (this.isTableCell(cell) || this.isTableRow(cell)) + { + return this.isCellMovable(this.model.getParent(cell)); + } + else + { + return true; + } + }; + + /** + * Overridden to add expand style. + */ + var graphIsExtendParent = Graph.prototype.isExtendParent; + Graph.prototype.isExtendParent = function(cell) + { + var parent = this.model.getParent(cell); + + if (parent != null) + { + var style = this.getCurrentCellStyle(parent); + + if (style['expand'] != null) + { + return style['expand'] != '0'; + } + } + + return graphIsExtendParent.apply(this, arguments) && + (parent == null || !this.isTable(parent)); + }; + + /** + * Overridden to use table cell instead of table as parent. + */ + var graphSplitEdge = Graph.prototype.splitEdge; + Graph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy, x, y, parent) + { + if (parent == null) + { + parent = this.model.getParent(edge); + + if (this.isTable(parent) || this.isTableRow(parent)) + { + parent = this.getCellAt(x, y, null, true, false); + } + } + + var newEdge = null; + + this.model.beginUpdate(); + try + { + var newEdge = graphSplitEdge.apply(this, [edge, cells, newEdge, dx, dy, x, y, parent]); + + // Removes cloned value on first segment + this.model.setValue(newEdge, ''); + + // Removes child labels on first or second segment depending on coordinate + // LATER: Split and reposition labels based on x and y + var sourceLabels = this.getChildCells(newEdge, true); + + for (var i = 0; i < sourceLabels.length; i++) + { + var geo = this.getCellGeometry(sourceLabels[i]); + + if (geo != null && geo.relative && geo.x > 0) + { + this.model.remove(sourceLabels[i]); + } + } + + var targetLabels = this.getChildCells(edge, true); + + for (var i = 0; i < targetLabels.length; i++) + { + var geo = this.getCellGeometry(targetLabels[i]); + + if (geo != null && geo.relative && geo.x <= 0) + { + this.model.remove(targetLabels[i]); + } + } + + // Removes entryX/Y and exitX/Y if snapToPoint is used + var target = this.model.getTerminal(newEdge, false); + + if (target != null) + { + var style = this.getCurrentCellStyle(target); + + if (style != null && style['snapToPoint'] == '1') + { + this.setCellStyles(mxConstants.STYLE_EXIT_X, null, [edge]); + this.setCellStyles(mxConstants.STYLE_EXIT_Y, null, [edge]); + this.setCellStyles(mxConstants.STYLE_ENTRY_X, null, [newEdge]); + this.setCellStyles(mxConstants.STYLE_ENTRY_Y, null, [newEdge]); + } + } + + } + finally + { + this.model.endUpdate(); + } + + return newEdge; + }; + + /** + * Overridden to flatten cell hierarchy for selecting next and previous. + */ + var graphSelectCell = Graph.prototype.selectCell; + Graph.prototype.selectCell = function(isNext, isParent, isChild) + { + if (isParent || isChild) + { + graphSelectCell.apply(this, arguments); + } + else + { + var cell = this.getSelectionCell(); + var index = null; + var cells = []; + + // LATER: Reverse traverse order for !isNext + var flatten = mxUtils.bind(this, function(temp) + { + if (this.view.getState(temp) != null && + (this.model.isVertex(temp) || + this.model.isEdge(temp))) + { + cells.push(temp); + + if (temp == cell) + { + index = cells.length - 1; + } + else if ((isNext && cell == null && cells.length > 0) || + (index != null && ((isNext && cells.length > index)) || + (!isNext && index > 0))) + { + return; + } + } + + for (var i = 0; i < this.model.getChildCount(temp); i++) + { + flatten(this.model.getChildAt(temp, i)); + } + }); + + flatten(this.model.root); + + if (cells.length > 0) + { + if (index != null) + { + index = mxUtils.mod(index + ((isNext) ? 1 : -1), cells.length) + } + else + { + index = 0; + } + + this.setSelectionCell(cells[index]); + } + } + }; + + /** + * Swaps the given shapes. + */ + Graph.prototype.swapShapes = function(cells, dx, dy, clone, target, evt, mapping) + { + var result = false; + + if (!clone && target != null && cells.length == 1) + { + var targetState = this.view.getState(target); + var sourceState = this.view.getState(cells[0]); + + if (targetState != null && sourceState != null && + (evt != null && mxEvent.isShiftDown(evt))) + { + var g1 = this.getCellGeometry(target); + var g2 = this.getCellGeometry(cells[0]); + + if (g1 != null && g2 != null) + { + var ng1 = g1.clone(); + var ng2 = g2.clone(); + ng2.x = ng1.x; + ng2.y = ng1.y; + ng1.x = g2.x; + ng1.y = g2.y; + + this.model.beginUpdate(); + try + { + this.model.setGeometry(target, ng1); + this.model.setGeometry(cells[0], ng2); + } + finally + { + this.model.endUpdate(); + } + + result = true; + } + } + } + + return result; + }; + + /** + * Overrides cloning cells in moveCells. + */ + var graphMoveCells = Graph.prototype.moveCells; + Graph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping) + { + if (this.swapShapes(cells, dx, dy, clone, target, evt, mapping)) + { + return cells; + } + + mapping = (mapping != null) ? mapping : new Object(); + + // Replaces source tables with rows + if (this.isTable(target)) + { + var newCells = []; + + for (var i = 0; i < cells.length; i++) + { + if (this.isTable(cells[i])) + { + newCells = newCells.concat(this.model.getChildCells(cells[i], true).reverse()); + } + else + { + newCells.push(cells[i]); + } + } + + cells = newCells; + } + + this.model.beginUpdate(); + try + { + // Updates source and target table heights and matches + // column count for moving rows between tables + var sourceTables = []; + + for (var i = 0; i < cells.length; i++) + { + if (target != null && this.isTableRow(cells[i])) + { + var parent = this.model.getParent(cells[i]); + var row = this.getCellGeometry(cells[i]); + + if (this.isTable(parent)) + { + sourceTables.push(parent); + } + + if (parent != null && row != null && + this.isTable(parent) && + this.isTable(target) && + (clone || parent != target)) + { + if (!clone) + { + var table = this.getCellGeometry(parent); + + if (table != null) + { + table = table.clone(); + table.height -= row.height; + this.model.setGeometry(parent, table); + } + } + + var table = this.getCellGeometry(target); + + if (table != null) + { + table = table.clone(); + table.height += row.height; + this.model.setGeometry(target, table); + } + + // Matches column count + var rows = this.model.getChildCells(target, true); + + if (rows.length > 0) + { + cells[i] = (clone) ? this.cloneCell(cells[i]) : cells[i]; + var sourceCols = this.model.getChildCells(cells[i], true); + var cols = this.model.getChildCells(rows[0], true); + var count = cols.length - sourceCols.length; + + if (count > 0) + { + for (var j = 0; j < count; j++) + { + var col = this.cloneCell(sourceCols[sourceCols.length - 1]); + + if (col != null) + { + col.value = ''; + + this.model.add(cells[i], col); + } + } + } + else if (count < 0) + { + for (var j = 0; j > count; j--) + { + this.model.remove(sourceCols[sourceCols.length + j - 1]); + } + } + + // Updates column widths + sourceCols = this.model.getChildCells(cells[i], true); + + for (var j = 0; j < cols.length; j++) + { + var geo = this.getCellGeometry(cols[j]); + var geo2 = this.getCellGeometry(sourceCols[j]); + + if (geo != null && geo2 != null) + { + geo2 = geo2.clone(); + geo2.width = geo.width; + + this.model.setGeometry(sourceCols[j], geo2); + } + } + } + } + } + } + + var result = graphMoveCells.apply(this, arguments); + + // Removes empty tables + for (var i = 0; i < sourceTables.length; i++) + { + if (!clone && this.model.contains(sourceTables[i]) && + this.model.getChildCount(sourceTables[i]) == 0) + { + this.model.remove(sourceTables[i]); + } + } + + if (clone) + { + this.updateCustomLinks(this.createCellMapping(mapping, + this.createCellLookup(cells)), result); + } + } + finally + { + this.model.endUpdate(); + } + + return result; + }; + + /** + * Overriddes to delete label for table cells. + */ + var graphRemoveCells = Graph.prototype.removeCells; + Graph.prototype.removeCells = function(cells, includeEdges) + { + var result = []; + + this.model.beginUpdate(); + try + { + // Clears labels on table cells + for (var i = 0; i < cells.length; i++) + { + if (this.isTableCell(cells[i])) + { + var row = this.model.getParent(cells[i]); + var table = this.model.getParent(row); + + // Removes table if one cell in one row left + if (this.model.getChildCount(row) == 1 && + this.model.getChildCount(table) == 1) + { + if (mxUtils.indexOf(cells, table) < 0 && + mxUtils.indexOf(result, table) < 0) + { + result.push(table); + } + } + else + { + this.labelChanged(cells[i], ''); + } + } + else + { + // Deletes table if all rows are removed + if (this.isTableRow(cells[i])) + { + var table = this.model.getParent(cells[i]); + + if (mxUtils.indexOf(cells, table) < 0 && + mxUtils.indexOf(result, table) < 0) + { + var rows = this.model.getChildCells(table, true); + var deleteCount = 0; + + for (var j = 0; j < rows.length; j++) + { + if (mxUtils.indexOf(cells, rows[j]) >= 0) + { + deleteCount++; + } + } + + if (deleteCount == rows.length) + { + result.push(table); + } + } + } + + result.push(cells[i]); + } + } + + result = graphRemoveCells.apply(this, [result, includeEdges]); + } + finally + { + this.model.endUpdate(); + } + + return result; + }; + + /** + * Updates cells IDs for custom links in the given cells using an + * optional graph to avoid changing the undo history. + */ + Graph.prototype.updateCustomLinks = function(mapping, cells, graph) + { + graph = (graph != null) ? graph : new Graph(); + + for (var i = 0; i < cells.length; i++) + { + if (cells[i] != null) + { + graph.updateCustomLinksForCell(mapping, cells[i], graph); + } + } + }; + + /** + * Updates cell IDs in custom links on the given cell and its label. + */ + Graph.prototype.updateCustomLinksForCell = function(mapping, cell) + { + this.doUpdateCustomLinksForCell(mapping, cell); + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + this.updateCustomLinksForCell(mapping, + this.model.getChildAt(cell, i)); + } + }; + + /** + * Updates cell IDs in custom links on the given cell and its label. + */ + Graph.prototype.doUpdateCustomLinksForCell = function(mapping, cell) + { + // Hook for subclassers + }; + + /** + * Overrides method to provide connection constraints for shapes. + */ + Graph.prototype.getAllConnectionConstraints = function(terminal, source) + { + if (terminal != null) + { + var constraints = mxUtils.getValue(terminal.style, 'points', null); + + if (constraints != null) + { + // Requires an array of arrays with x, y (0..1), an optional + // [perimeter (0 or 1), dx, and dy] eg. points=[[0,0,1,-10,10],[0,1,0],[1,1]] + var result = []; + + try + { + var c = JSON.parse(constraints); + + for (var i = 0; i < c.length; i++) + { + var tmp = c[i]; + result.push(new mxConnectionConstraint(new mxPoint(tmp[0], tmp[1]), (tmp.length > 2) ? tmp[2] != '0' : true, + null, (tmp.length > 3) ? tmp[3] : 0, (tmp.length > 4) ? tmp[4] : 0)); + } + } + catch (e) + { + // ignore + } + + return result; + } + else if (terminal.shape != null && terminal.shape.bounds != null) + { + var dir = terminal.shape.direction; + var bounds = terminal.shape.bounds; + var scale = terminal.shape.scale; + var w = bounds.width / scale; + var h = bounds.height / scale; + + if (dir == mxConstants.DIRECTION_NORTH || dir == mxConstants.DIRECTION_SOUTH) + { + var tmp = w; + w = h; + h = tmp; + } + + constraints = terminal.shape.getConstraints(terminal.style, w, h); + + if (constraints != null) + { + return constraints; + } + else if (terminal.shape.stencil != null && terminal.shape.stencil.constraints != null) + { + return terminal.shape.stencil.constraints; + } + else if (terminal.shape.constraints != null) + { + return terminal.shape.constraints; + } + } + } + + return null; + }; + + /** + * Inverts the elbow edge style without removing existing styles. + */ + Graph.prototype.flipEdge = function(edge) + { + if (edge != null) + { + var style = this.getCurrentCellStyle(edge); + var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW, + mxConstants.ELBOW_HORIZONTAL); + var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ? + mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL; + this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]); + } + }; + + /** + * Disables drill-down for non-swimlanes. + */ + Graph.prototype.isValidRoot = function(cell) + { + // Counts non-relative children + var childCount = this.model.getChildCount(cell); + var realChildCount = 0; + + for (var i = 0; i < childCount; i++) + { + var child = this.model.getChildAt(cell, i); + + if (this.model.isVertex(child)) + { + var geometry = this.getCellGeometry(child); + + if (geometry != null && !geometry.relative) + { + realChildCount++; + } + } + } + + return realChildCount > 0 || this.isContainer(cell); + }; + + /** + * Disables drill-down for non-swimlanes. + */ + Graph.prototype.isValidDropTarget = function(cell, cells, evt) + { + var style = this.getCurrentCellStyle(cell); + var tables = true; + var rows = true; + + for (var i = 0; i < cells.length && rows; i++) + { + tables = tables && this.isTable(cells[i]); + rows = rows && this.isTableRow(cells[i]); + } + + return !this.isCellLocked(cell) && ((cells.length == 1 && evt != null && + mxEvent.isShiftDown(evt) && !mxEvent.isControlDown(evt) && + !mxEvent.isAltDown(evt)) || this.isTargetShape(cell, cells, evt) || + ((mxUtils.getValue(style, 'part', '0') != '1' || this.isContainer(cell)) && + mxUtils.getValue(style, 'dropTarget', '1') != '0' && (mxGraph.prototype. + isValidDropTarget.apply(this, arguments) || this.isContainer(cell)) && + !this.isTableRow(cell) && (!this.isTable(cell) || rows || tables))); + }; + + /** + * Overrides createGroupCell to set the group style for new groups to 'group'. + */ + Graph.prototype.createGroupCell = function() + { + var group = mxGraph.prototype.createGroupCell.apply(this, arguments); + group.setStyle('group'); + + return group; + }; + + /** + * Disables extending parents with stack layouts on add + */ + Graph.prototype.isExtendParentsOnAdd = function(cell) + { + var result = mxGraph.prototype.isExtendParentsOnAdd.apply(this, arguments); + + if (result && cell != null && this.layoutManager != null) + { + var parent = this.model.getParent(cell); + + if (parent != null) + { + var layout = this.layoutManager.getLayout(parent); + + if (layout != null && layout.constructor == mxStackLayout) + { + result = false; + } + } + } + + return result; + }; + + /** + * Overrides autosize to add a border. + */ + Graph.prototype.getPreferredSizeForCell = function(cell) + { + var result = mxGraph.prototype.getPreferredSizeForCell.apply(this, arguments); + + // Adds buffer + if (result != null) + { + result.width += 10; + result.height += 4; + + if (this.gridEnabled) + { + result.width = this.snap(result.width); + result.height = this.snap(result.height); + } + } + + return result; + } + + /** + * Turns the given cells and returns the changed cells. + */ + Graph.prototype.turnShapes = function(cells, backwards) + { + var model = this.getModel(); + var select = []; + + model.beginUpdate(); + try + { + for (var i = 0; i < cells.length; i++) + { + var cell = cells[i]; + + if (model.isEdge(cell)) + { + var src = model.getTerminal(cell, true); + var trg = model.getTerminal(cell, false); + + model.setTerminal(cell, trg, true); + model.setTerminal(cell, src, false); + + var geo = model.getGeometry(cell); + + if (geo != null) + { + geo = geo.clone(); + + if (geo.points != null) + { + geo.points.reverse(); + } + + var sp = geo.getTerminalPoint(true); + var tp = geo.getTerminalPoint(false) + + geo.setTerminalPoint(sp, false); + geo.setTerminalPoint(tp, true); + model.setGeometry(cell, geo); + + // Inverts constraints + var edgeState = this.view.getState(cell); + var sourceState = this.view.getState(src); + var targetState = this.view.getState(trg); + + if (edgeState != null) + { + var sc = (sourceState != null) ? this.getConnectionConstraint(edgeState, sourceState, true) : null; + var tc = (targetState != null) ? this.getConnectionConstraint(edgeState, targetState, false) : null; + + this.setConnectionConstraint(cell, src, true, tc); + this.setConnectionConstraint(cell, trg, false, sc); + + // Inverts perimeter spacings + var temp = mxUtils.getValue(edgeState.style, mxConstants.STYLE_SOURCE_PERIMETER_SPACING); + this.setCellStyles(mxConstants.STYLE_SOURCE_PERIMETER_SPACING, mxUtils.getValue( + edgeState.style, mxConstants.STYLE_TARGET_PERIMETER_SPACING), [cell]); + this.setCellStyles(mxConstants.STYLE_TARGET_PERIMETER_SPACING, temp, [cell]); + } + + select.push(cell); + } + } + else if (model.isVertex(cell)) + { + var geo = this.getCellGeometry(cell); + + if (geo != null) + { + // Rotates the size and position in the geometry + if (!this.isTable(cell) && !this.isTableRow(cell) && + !this.isTableCell(cell) && !this.isSwimlane(cell)) + { + geo = geo.clone(); + geo.x += geo.width / 2 - geo.height / 2; + geo.y += geo.height / 2 - geo.width / 2; + var tmp = geo.width; + geo.width = geo.height; + geo.height = tmp; + model.setGeometry(cell, geo); + } + + // Reads the current direction and advances by 90 degrees + var state = this.view.getState(cell); + + if (state != null) + { + var dirs = [mxConstants.DIRECTION_EAST, mxConstants.DIRECTION_SOUTH, + mxConstants.DIRECTION_WEST, mxConstants.DIRECTION_NORTH]; + var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, + mxConstants.DIRECTION_EAST); + this.setCellStyles(mxConstants.STYLE_DIRECTION, + dirs[mxUtils.mod(mxUtils.indexOf(dirs, dir) + + ((backwards) ? -1 : 1), dirs.length)], [cell]); + } + + select.push(cell); + } + } + } + } + finally + { + model.endUpdate(); + } + + return select; + }; + + /** + * Returns true if the given stencil contains any placeholder text. + */ + Graph.prototype.stencilHasPlaceholders = function(stencil) + { + if (stencil != null && stencil.fgNode != null) + { + var node = stencil.fgNode.firstChild; + + while (node != null) + { + if (node.nodeName == 'text' && node.getAttribute('placeholders') == '1') + { + return true; + } + + node = node.nextSibling; + } + } + + return false; + }; + + /** + * Updates the child cells with placeholders if metadata of a + * cell has changed and propagates geometry changes in tables. + */ + var graphProcessChange = Graph.prototype.processChange; + Graph.prototype.processChange = function(change) + { + if (change instanceof mxGeometryChange && + (this.isTableCell(change.cell) || this.isTableRow(change.cell)) && + ((change.previous == null && change.geometry != null) || + (change.previous != null && !change.previous.equals(change.geometry)))) + { + var cell = change.cell; + + if (this.isTableCell(cell)) + { + cell = this.model.getParent(cell); + } + + if (this.isTableRow(cell)) + { + cell = this.model.getParent(cell); + } + + // Forces repaint of table with unchanged style and geometry + var state = this.view.getState(cell); + + if (state != null && state.shape != null) + { + this.view.invalidate(cell); + state.shape.bounds = null; + } + } + + graphProcessChange.apply(this, arguments); + + if (change instanceof mxValueChange && change.cell != null && + change.cell.value != null && typeof(change.cell.value) == 'object') + { + this.invalidateDescendantsWithPlaceholders(change.cell); + } + }; + + /** + * Replaces the given element with a span. + */ + Graph.prototype.invalidateDescendantsWithPlaceholders = function(cell) + { + // Invalidates all descendants with placeholders + var desc = this.model.getDescendants(cell); + + // LATER: Check if only label or tooltip have changed + if (desc.length > 0) + { + for (var i = 0; i < desc.length; i++) + { + var state = this.view.getState(desc[i]); + + if (state != null && state.shape != null && state.shape.stencil != null && + this.stencilHasPlaceholders(state.shape.stencil)) + { + this.removeStateForCell(desc[i]); + } + else if (this.isReplacePlaceholders(desc[i])) + { + this.view.invalidate(desc[i], false, false); + } + } + } + }; + + /** + * Replaces the given element with a span. + */ + Graph.prototype.replaceElement = function(elt, tagName) + { + var span = elt.ownerDocument.createElement((tagName != null) ? tagName : 'span'); + var attributes = Array.prototype.slice.call(elt.attributes); + + while (attr = attributes.pop()) + { + span.setAttribute(attr.nodeName, attr.nodeValue); + } + + span.innerHTML = elt.innerHTML; + elt.parentNode.replaceChild(span, elt); + }; + + /** + * + */ + Graph.prototype.processElements = function(elt, fn) + { + if (elt != null) + { + var elts = elt.getElementsByTagName('*'); + + for (var i = 0; i < elts.length; i++) + { + fn(elts[i]); + } + } + }; + + /** + * Handles label changes for XML user objects. + */ + Graph.prototype.updateLabelElements = function(cells, fn, tagName) + { + cells = (cells != null) ? cells : this.getSelectionCells(); + var div = document.createElement('div'); + + for (var i = 0; i < cells.length; i++) + { + // Changes font tags inside HTML labels + var style = this.getCurrentCellStyle(cells[i]); + + if (style != null && style['html'] == '1') + { + var label = this.convertValueToString(cells[i]); + + if (label != null && label.length > 0) + { + div.innerHTML = label; + var elts = div.getElementsByTagName((tagName != null) ? tagName : '*'); + + for (var j = 0; j < elts.length; j++) + { + fn(elts[j]); + } + + if (div.innerHTML != label) + { + this.cellLabelChanged(cells[i], div.innerHTML); + } + } + } + } + }; + + /** + * Handles label changes for XML user objects. + */ + Graph.prototype.cellLabelChanged = function(cell, value, autoSize) + { + // Removes all illegal control characters in user input + value = Graph.zapGremlins(value); + + this.model.beginUpdate(); + try + { + if (cell.value != null && typeof cell.value == 'object') + { + if (this.isReplacePlaceholders(cell) && + cell.getAttribute('placeholder') != null) + { + // LATER: Handle delete, name change + var name = cell.getAttribute('placeholder'); + var current = cell; + + while (current != null) + { + if (current == this.model.getRoot() || (current.value != null && + typeof(current.value) == 'object' && current.hasAttribute(name))) + { + this.setAttributeForCell(current, name, value); + + break; + } + + current = this.model.getParent(current); + } + } + + var tmp = cell.value.cloneNode(true); + + if (Graph.translateDiagram && Graph.diagramLanguage != null && + tmp.hasAttribute('label_' + Graph.diagramLanguage)) + { + tmp.setAttribute('label_' + Graph.diagramLanguage, value); + } + else + { + tmp.setAttribute('label', value); + } + + value = tmp; + } + + mxGraph.prototype.cellLabelChanged.apply(this, arguments); + } + finally + { + this.model.endUpdate(); + } + }; + + /** + * Removes transparent empty groups if all children are removed. + */ + Graph.prototype.cellsRemoved = function(cells) + { + if (cells != null) + { + var dict = new mxDictionary(); + + for (var i = 0; i < cells.length; i++) + { + dict.put(cells[i], true); + } + + // LATER: Recurse up the cell hierarchy + var parents = []; + + for (var i = 0; i < cells.length; i++) + { + var parent = this.model.getParent(cells[i]); + + if (parent != null && !dict.get(parent)) + { + dict.put(parent, true); + parents.push(parent); + } + } + + for (var i = 0; i < parents.length; i++) + { + var state = this.view.getState(parents[i]); + + if (state != null && (this.model.isEdge(state.cell) || + this.model.isVertex(state.cell)) && + this.isCellDeletable(state.cell) && + this.isTransparentState(state)) + { + var allChildren = true; + + for (var j = 0; j < this.model.getChildCount(state.cell) && allChildren; j++) + { + if (!dict.get(this.model.getChildAt(state.cell, j))) + { + allChildren = false; + } + } + + if (allChildren) + { + cells.push(state.cell); + } + } + } + } + + mxGraph.prototype.cellsRemoved.apply(this, arguments); + }; + + /** + * Overrides ungroup to check if group should be removed. + */ + Graph.prototype.removeCellsAfterUngroup = function(cells) + { + var cellsToRemove = []; + + for (var i = 0; i < cells.length; i++) + { + if (this.isCellDeletable(cells[i]) && + this.isTransparentState( + this.view.getState(cells[i]))) + { + cellsToRemove.push(cells[i]); + } + } + + cells = cellsToRemove; + + mxGraph.prototype.removeCellsAfterUngroup.apply(this, arguments); + }; + + /** + * Sets the link for the given cell. + */ + Graph.prototype.setLinkForCell = function(cell, link) + { + this.setAttributeForCell(cell, 'link', link); + }; + + /** + * Sets the link for the given cell. + */ + Graph.prototype.setTooltipForCell = function(cell, link) + { + var key = 'tooltip'; + + if (Graph.translateDiagram && Graph.diagramLanguage != null && + mxUtils.isNode(cell.value) && cell.value.hasAttribute('tooltip_' + Graph.diagramLanguage)) + { + key = 'tooltip_' + Graph.diagramLanguage; + } + + this.setAttributeForCell(cell, key, link); + }; + + /** + * Returns the cells in the model (or given array) that have all of the + * given tags in their tags property. + */ + Graph.prototype.getAttributeForCell = function(cell, attributeName, defaultValue) + { + var value = (cell.value != null && typeof cell.value === 'object') ? + cell.value.getAttribute(attributeName) : null; + + return (value != null) ? value : defaultValue; + }; + + /** + * Sets the link for the given cell. + */ + Graph.prototype.setAttributeForCell = function(cell, attributeName, attributeValue) + { + var value = null; + + if (cell.value != null && typeof(cell.value) == 'object') + { + value = cell.value.cloneNode(true); + } + else + { + var doc = mxUtils.createXmlDocument(); + + value = doc.createElement('UserObject'); + value.setAttribute('label', cell.value || ''); + } + + if (attributeValue != null) + { + value.setAttribute(attributeName, attributeValue); + } + else + { + value.removeAttribute(attributeName); + } + + this.model.setValue(cell, value); + }; + + /** + * + */ + Graph.prototype.isTargetShape = function(target, cells, evt) + { + var shape = mxUtils.getValue( + this.getCurrentCellStyle(target), + mxConstants.STYLE_SHAPE, ''); + + for (var i = 0; i < cells.length; i++) + { + var shapes = mxUtils.getValue( + this.getCurrentCellStyle(cells[i]), + 'targetShapes', '').split(','); + + if (mxUtils.indexOf(shapes, shape) >= 0) + { + return true; + } + } + + return false; + }; + + /** + * Overridden to stop moving edge labels between cells. + */ + var graphGetDropTarget = Graph.prototype.getDropTarget; + Graph.prototype.getDropTarget = function(cells, evt, cell, clone) + { + // Disables drop into group if alt is pressed + if (mxEvent.isAltDown(evt)) + { + return null; + } + + // Disables dragging edge labels out of edges + for (var i = 0; i < cells.length; i++) + { + var parent = this.model.getParent(cells[i]); + + if (this.model.isEdge(parent) && mxUtils.indexOf(cells, parent) < 0) + { + return null; + } + } + + var target = graphGetDropTarget.apply(this, arguments); + + // Always drops rows to tables + var rows = true; + + for (var i = 0; i < cells.length && rows; i++) + { + rows = rows && this.isTableRow(cells[i]); + } + + if (rows) + { + if (this.isTableCell(target)) + { + target = this.model.getParent(target); + } + + if (this.isTableRow(target)) + { + target = this.model.getParent(target); + } + + if (!this.isTable(target)) + { + target = null; + } + } + + return target; + }; + + /** + * Overrides double click handling to avoid accidental inserts of new labels in dblClick below. + */ + Graph.prototype.click = function(me) + { + mxGraph.prototype.click.call(this, me); + + // Stores state and source for checking in dblClick + this.firstClickState = me.getState(); + this.firstClickSource = me.getSource(); + }; + + /** + * Overrides double click handling to add the tolerance and inserting text. + */ + Graph.prototype.dblClick = function(evt, cell) + { + if (this.isEnabled()) + { + cell = this.insertTextForEvent(evt, cell); + mxGraph.prototype.dblClick.call(this, evt, cell); + } + }; + + /** + * Overrides double click handling to add the tolerance and inserting text. + */ + Graph.prototype.insertTextForEvent = function(evt, cell) + { + var pt = mxUtils.convertPoint(this.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt)); + + // Automatically adds new child cells to edges on double click + if (evt != null && !this.model.isVertex(cell)) + { + var state = (this.model.isEdge(cell)) ? this.view.getState(cell) : null; + var src = mxEvent.getSource(evt); + + if ((this.firstClickState == state && this.firstClickSource == src) && + (state == null || (state.text == null || state.text.node == null || + state.text.boundingBox == null || (!mxUtils.contains(state.text.boundingBox, + pt.x, pt.y) && !mxUtils.isAncestorNode(state.text.node, mxEvent.getSource(evt))))) && + ((state == null && !this.isCellLocked(this.getDefaultParent())) || + (state != null && !this.isCellLocked(state.cell))) && + (state != null || + (mxClient.IS_SVG && src == this.view.getCanvas().ownerSVGElement))) + { + if (state == null) + { + state = this.view.getState(this.getCellAt(pt.x, pt.y)); + } + + cell = this.addText(pt.x, pt.y, state); + } + } + + return cell; + }; + + /** + * Returns a point that specifies the location for inserting cells. + */ + Graph.prototype.getInsertPoint = function() + { + var gs = this.getGridSize(); + var dx = this.container.scrollLeft / this.view.scale - this.view.translate.x; + var dy = this.container.scrollTop / this.view.scale - this.view.translate.y; + + if (this.pageVisible) + { + var layout = this.getPageLayout(); + var page = this.getPageSize(); + dx = Math.max(dx, layout.x * page.width); + dy = Math.max(dy, layout.y * page.height); + } + + return new mxPoint(this.snap(dx + gs), this.snap(dy + gs)); + }; + + /** + * + */ + Graph.prototype.getFreeInsertPoint = function() + { + var view = this.view; + var bds = this.getGraphBounds(); + var pt = this.getInsertPoint(); + + // Places at same x-coord and 2 grid sizes below existing graph + var x = this.snap(Math.round(Math.max(pt.x, bds.x / view.scale - view.translate.x + + ((bds.width == 0) ? 2 * this.gridSize : 0)))); + var y = this.snap(Math.round(Math.max(pt.y, (bds.y + bds.height) / view.scale - view.translate.y + + 2 * this.gridSize))); + + return new mxPoint(x, y); + }; + + /** + * + */ + Graph.prototype.getCenterInsertPoint = function(bbox) + { + bbox = (bbox != null) ? bbox : new mxRectangle(); + + if (mxUtils.hasScrollbars(this.container)) + { + return new mxPoint( + this.snap(Math.round((this.container.scrollLeft + this.container.clientWidth / 2) / + this.view.scale - this.view.translate.x - bbox.width / 2)), + this.snap(Math.round((this.container.scrollTop + this.container.clientHeight / 2) / + this.view.scale - this.view.translate.y - bbox.height / 2))); + } + else + { + return new mxPoint( + this.snap(Math.round(this.container.clientWidth / 2 / this.view.scale - + this.view.translate.x - bbox.width / 2)), + this.snap(Math.round(this.container.clientHeight / 2 / this.view.scale - + this.view.translate.y - bbox.height / 2))); + } + }; + + /** + * Hook for subclassers to return true if the current insert point was defined + * using a mouse hover event. + */ + Graph.prototype.isMouseInsertPoint = function() + { + return false; + }; + + /** + * Adds a new label at the given position and returns the new cell. State is + * an optional edge state to be used as the parent for the label. Vertices + * are not allowed currently as states. + */ + Graph.prototype.addText = function(x, y, state) + { + // Creates a new edge label with a predefined text + var label = new mxCell(); + label.value = 'Text'; + label.geometry = new mxGeometry(0, 0, 0, 0); + label.vertex = true; + var style = 'html=1;align=center;verticalAlign=middle;resizable=0;points=[];'; + + if (state != null && this.model.isEdge(state.cell)) + { + label.style = 'edgeLabel;' + style; + label.geometry.relative = true; + label.connectable = false; + + // Resets the relative location stored inside the geometry + var pt2 = this.view.getRelativePoint(state, x, y); + label.geometry.x = Math.round(pt2.x * 10000) / 10000; + label.geometry.y = Math.round(pt2.y); + + // Resets the offset inside the geometry to find the offset from the resulting point + label.geometry.offset = new mxPoint(0, 0); + pt2 = this.view.getPoint(state, label.geometry); + + var scale = this.view.scale; + label.geometry.offset = new mxPoint(Math.round((x - pt2.x) / scale), Math.round((y - pt2.y) / scale)); + } + else + { + var tr = this.view.translate; + label.style = 'text;' + style; + label.geometry.width = 40; + label.geometry.height = 20; + label.geometry.x = Math.round(x / this.view.scale) - + tr.x - ((state != null) ? state.origin.x : 0); + label.geometry.y = Math.round(y / this.view.scale) - + tr.y - ((state != null) ? state.origin.y : 0); + label.style += 'autosize=1;' + } + + this.getModel().beginUpdate(); + try + { + this.addCells([label], (state != null) ? state.cell : null); + this.fireEvent(new mxEventObject('textInserted', 'cells', [label])); + + // Updates size of text after possible change of style via event + this.autoSizeCell(label); + } + finally + { + this.getModel().endUpdate(); + } + + return label; + }; + + /** + * Adds a handler for clicking on shapes with links. This replaces all links in labels. + */ + Graph.prototype.addClickHandler = function(highlight, beforeClick, onClick) + { + // Replaces links in labels for consistent right-clicks + var checkLinks = mxUtils.bind(this, function() + { + var links = this.container.getElementsByTagName('a'); + + if (links != null) + { + for (var i = 0; i < links.length; i++) + { + var href = this.getAbsoluteUrl(links[i].getAttribute('href')); + + if (href != null) + { + links[i].setAttribute('rel', this.linkRelation); + links[i].setAttribute('href', href); + + if (beforeClick != null) + { + mxEvent.addGestureListeners(links[i], null, null, beforeClick); + } + } + } + } + }); + + this.model.addListener(mxEvent.CHANGE, checkLinks); + checkLinks(); + + var cursor = this.container.style.cursor; + var tol = this.getTolerance(); + var graph = this; + + var mouseListener = + { + currentState: null, + currentLink: null, + currentTarget: null, + highlight: (highlight != null && highlight != '' && highlight != mxConstants.NONE) ? + new mxCellHighlight(graph, highlight, 4) : null, + startX: 0, + startY: 0, + scrollLeft: 0, + scrollTop: 0, + updateCurrentState: function(me) + { + var tmp = me.sourceState; + + // Gets first intersecting ancestor with link + if (tmp == null || graph.getLinkForCell(tmp.cell) == null) + { + var cell = graph.getCellAt(me.getGraphX(), me.getGraphY(), null, null, null, function(state, x, y) + { + return graph.getLinkForCell(state.cell) == null; + }); + + tmp = (tmp != null && !graph.model.isAncestor(cell, tmp.cell)) ? null : graph.view.getState(cell); + } + + if (tmp != this.currentState) + { + if (this.currentState != null) + { + this.clear(); + } + + this.currentState = tmp; + + if (this.currentState != null) + { + this.activate(this.currentState); + } + } + }, + mouseDown: function(sender, me) + { + this.startX = me.getGraphX(); + this.startY = me.getGraphY(); + this.scrollLeft = graph.container.scrollLeft; + this.scrollTop = graph.container.scrollTop; + + if (this.currentLink == null && graph.container.style.overflow == 'auto') + { + graph.container.style.cursor = 'move'; + } + + this.updateCurrentState(me); + }, + mouseMove: function(sender, me) + { + if (graph.isMouseDown) + { + if (this.currentLink != null) + { + var dx = Math.abs(this.startX - me.getGraphX()); + var dy = Math.abs(this.startY - me.getGraphY()); + + if (dx > tol || dy > tol) + { + this.clear(); + } + } + } + else + { + // Checks for parent link + var linkNode = me.getSource(); + + while (linkNode != null && linkNode.nodeName.toLowerCase() != 'a') + { + linkNode = linkNode.parentNode; + } + + if (linkNode != null) + { + this.clear(); + } + else + { + if (graph.tooltipHandler != null && this.currentLink != null && this.currentState != null) + { + graph.tooltipHandler.reset(me, true, this.currentState); + } + + if (this.currentState != null && (me.getState() == this.currentState || me.sourceState == null) && + graph.intersects(this.currentState, me.getGraphX(), me.getGraphY())) + { + return; + } + + this.updateCurrentState(me); + } + } + }, + mouseUp: function(sender, me) + { + var source = me.getSource(); + var evt = me.getEvent(); + + // Checks for parent link + var linkNode = source; + + while (linkNode != null && linkNode.nodeName.toLowerCase() != 'a') + { + linkNode = linkNode.parentNode; + } + + // Ignores clicks on links and collapse/expand icon + if (linkNode == null && + (((Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol && + Math.abs(this.scrollTop - graph.container.scrollTop) < tol) && + (me.sourceState == null || !me.isSource(me.sourceState.control))) && + (((mxEvent.isLeftMouseButton(evt) || mxEvent.isMiddleMouseButton(evt)) && + !mxEvent.isPopupTrigger(evt)) || mxEvent.isTouchEvent(evt)))) + { + if (this.currentLink != null) + { + var blank = graph.isBlankLink(this.currentLink); + + if ((this.currentLink.substring(0, 5) === 'data:' || + !blank) && beforeClick != null) + { + beforeClick(evt, this.currentLink); + } + + if (!mxEvent.isConsumed(evt)) + { + var target = (this.currentTarget != null) ? + this.currentTarget : ((mxEvent.isMiddleMouseButton(evt)) ? '_blank' : + ((blank) ? graph.linkTarget : '_top')); + + graph.openLink(this.currentLink, target); + me.consume(); + } + } + else if (onClick != null && !me.isConsumed() && + (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)) + { + onClick(me.getEvent()); + } + } + + this.clear(); + }, + activate: function(state) + { + this.currentLink = graph.getAbsoluteUrl(graph.getLinkForCell(state.cell)); + + if (this.currentLink != null) + { + this.currentTarget = graph.getLinkTargetForCell(state.cell) + graph.container.style.cursor = 'pointer'; + + if (this.highlight != null) + { + this.highlight.highlight(state); + } + } + }, + clear: function() + { + if (graph.container != null) + { + graph.container.style.cursor = cursor; + } + + this.currentTarget = null; + this.currentState = null; + this.currentLink = null; + + if (this.highlight != null) + { + this.highlight.hide(); + } + + if (graph.tooltipHandler != null) + { + graph.tooltipHandler.hide(); + } + } + }; + + // Ignores built-in click handling + graph.click = function(me) {}; + graph.addMouseListener(mouseListener); + + mxEvent.addListener(document, 'mouseleave', function(evt) + { + mouseListener.clear(); + }); + }; + + /** + * Duplicates the given cells and returns the duplicates. + */ + Graph.prototype.duplicateCells = function(cells, append) + { + cells = (cells != null) ? cells : this.getSelectionCells(); + append = (append != null) ? append : true; + + // Duplicates rows for table cells + for (var i = 0; i < cells.length; i++) + { + if (this.isTableCell(cells[i])) + { + cells[i] = this.model.getParent(cells[i]); + } + } + + cells = this.model.getTopmostCells(cells); + + var model = this.getModel(); + var s = this.gridSize; + var select = []; + + model.beginUpdate(); + try + { + var cloneMap = new Object(); + var lookup = this.createCellLookup(cells); + var clones = this.cloneCells(cells, false, cloneMap, true); + + for (var i = 0; i < cells.length; i++) + { + var parent = model.getParent(cells[i]); + + if (parent != null) + { + var child = this.moveCells([clones[i]], s, s, false)[0]; + select.push(child); + + if (append) + { + model.add(parent, clones[i]); + } + else + { + // Maintains child index by inserting after clone in parent + var index = parent.getIndex(cells[i]); + model.add(parent, clones[i], index + 1); + } + + // Extends tables + if (this.isTable(parent)) + { + var row = this.getCellGeometry(clones[i]); + var table = this.getCellGeometry(parent); + + if (row != null && table != null) + { + table = table.clone(); + table.height += row.height; + model.setGeometry(parent, table); + } + } + } + else + { + select.push(clones[i]); + } + } + + // Updates custom links after inserting into the model for cells to have new IDs + this.updateCustomLinks(this.createCellMapping(cloneMap, lookup), clones, this); + this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', clones)); + } + finally + { + model.endUpdate(); + } + + return select; + }; + + /** + * Inserts the given image at the cursor in a content editable text box using + * the insertimage command on the document instance. + */ + Graph.prototype.insertImage = function(newValue, w, h) + { + // To find the new image, we create a list of all existing links first + if (newValue != null && this.cellEditor.textarea != null) + { + var tmp = this.cellEditor.textarea.getElementsByTagName('img'); + var oldImages = []; + + for (var i = 0; i < tmp.length; i++) + { + oldImages.push(tmp[i]); + } + + // LATER: Fix inserting link/image in IE8/quirks after focus lost + document.execCommand('insertimage', false, newValue); + + // Sets size of new image + var newImages = this.cellEditor.textarea.getElementsByTagName('img'); + + if (newImages.length == oldImages.length + 1) + { + // Inverse order in favor of appended images + for (var i = newImages.length - 1; i >= 0; i--) + { + if (i == 0 || newImages[i] != oldImages[i - 1]) + { + // Workaround for lost styles during undo and redo is using attributes + newImages[i].setAttribute('width', w); + newImages[i].setAttribute('height', h); + + break; + } + } + } + } + }; + + /** + * Inserts the given image at the cursor in a content editable text box using + * the insertimage command on the document instance. + */ + Graph.prototype.insertLink = function(value) + { + if (this.cellEditor.textarea != null) + { + if (value.length == 0) + { + document.execCommand('unlink', false); + } + else if (mxClient.IS_FF) + { + // Workaround for Firefox that adds a new link and removes + // the href from the inner link if its parent is a span is + // to remove all inner links inside the new outer link + var tmp = this.cellEditor.textarea.getElementsByTagName('a'); + var oldLinks = []; + + for (var i = 0; i < tmp.length; i++) + { + oldLinks.push(tmp[i]); + } + + document.execCommand('createlink', false, mxUtils.trim(value)); + + // Finds the new link element + var newLinks = this.cellEditor.textarea.getElementsByTagName('a'); + + if (newLinks.length == oldLinks.length + 1) + { + // Inverse order in favor of appended links + for (var i = newLinks.length - 1; i >= 0; i--) + { + if (newLinks[i] != oldLinks[i - 1]) + { + // Removes all inner links from the new link and + // moves the children to the inner link parent + var tmp = newLinks[i].getElementsByTagName('a'); + + while (tmp.length > 0) + { + var parent = tmp[0].parentNode; + + while (tmp[0].firstChild != null) + { + parent.insertBefore(tmp[0].firstChild, tmp[0]); + } + + parent.removeChild(tmp[0]); + } + + break; + } + } + } + } + else + { + // LATER: Fix inserting link/image in IE8/quirks after focus lost + document.execCommand('createlink', false, mxUtils.trim(value)); + } + } + }; + + /** + * + * @param cell + * @returns {Boolean} + */ + Graph.prototype.isCellResizable = function(cell) + { + var result = mxGraph.prototype.isCellResizable.apply(this, arguments); + var style = this.getCurrentCellStyle(cell); + + return !this.isTableCell(cell) && !this.isTableRow(cell) && (result || + (mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0' && + style[mxConstants.STYLE_WHITE_SPACE] == 'wrap')); + }; + + /** + * Function: distributeCells + * + * Distribuets the centers of the given cells equally along the available + * horizontal or vertical space. + * + * Parameters: + * + * horizontal - Boolean that specifies the direction of the distribution. + * cells - Optional array of to be distributed. Edges are ignored. + */ + Graph.prototype.distributeCells = function(horizontal, cells, spacing) + { + if (cells == null) + { + cells = this.getSelectionCells(); + } + + if (cells != null && cells.length > 1) + { + var vertices = []; + var max = null; + var min = null; + var cellsSize = 0; + + for (var i = 0; i < cells.length; i++) + { + if (this.getModel().isVertex(cells[i])) + { + var state = this.view.getState(cells[i]); + + if (state != null) + { + var tmp = (horizontal) ? state.getCenterX() : state.getCenterY(); + max = (max != null) ? Math.max(max, tmp) : tmp; + min = (min != null) ? Math.min(min, tmp) : tmp; + + if (spacing) + { + cellsSize += (horizontal) ? state.width : state.height; + } + + vertices.push(state); + } + } + } + + if (vertices.length > 2) + { + vertices.sort(function(a, b) + { + return (horizontal) ? a.x - b.x : a.y - b.y; + }); + + if (spacing) + { + cellsSize -= (horizontal? (vertices[0].width / 2 + vertices[vertices.length - 1].width / 2) : + (vertices[0].height / 2 + vertices[vertices.length - 1].height / 2)) + } + + var t = this.view.translate; + var s = this.view.scale; + + min = min / s - ((horizontal) ? t.x : t.y); + max = max / s - ((horizontal) ? t.x : t.y); + + this.getModel().beginUpdate(); + try + { + var dt = (max - min - cellsSize) / (vertices.length - 1); + var t0 = min + (spacing? (horizontal? vertices[0].width / 2 : vertices[0].height / 2) : 0); + + for (var i = 1; i < vertices.length - 1; i++) + { + var pstate = this.view.getState(this.model.getParent(vertices[i].cell)); + var geo = this.getCellGeometry(vertices[i].cell); + t0 += dt; + + if (geo != null && pstate != null) + { + geo = geo.clone(); + + if (horizontal) + { + geo.x = Math.round(t0 - (spacing? 0 : geo.width / 2)) - pstate.origin.x; + } + else + { + geo.y = Math.round(t0 - (spacing? 0 : geo.height / 2)) - pstate.origin.y; + } + + this.getModel().setGeometry(vertices[i].cell, geo); + } + + if (spacing) + { + t0 += horizontal? vertices[i].width : vertices[i].height; + } + } + } + finally + { + this.getModel().endUpdate(); + } + } + } + + return cells; + }; + + /** + * Adds meta-drag an Mac. + * @param evt + * @returns + */ + Graph.prototype.isCloneEvent = function(evt) + { + return (mxClient.IS_MAC && mxEvent.isMetaDown(evt)) || mxEvent.isControlDown(evt); + }; + + /** + * Translates this point by the given vector. + * + * @param {number} dx X-coordinate of the translation. + * @param {number} dy Y-coordinate of the translation. + */ + Graph.prototype.createSvgImageExport = function() + { + var exp = new mxImageExport(); + + // Adds hyperlinks (experimental) + exp.getLinkForCellState = mxUtils.bind(this, function(state, canvas) + { + return this.getLinkForCell(state.cell); + }); + + // Adds tooltips (experimental) + exp.getTitleForCellState = mxUtils.bind(this, function(state, canvas) + { + return Editor.convertHtmlToText(this.convertValueToTooltip(state.cell)); + }); + + return exp; + }; + + /** + * Parses the given background image. + */ + Graph.prototype.parseBackgroundImage = function(json) + { + var result = null; + + if (json != null && json.length > 0) + { + var obj = JSON.parse(json); + result = new mxImage(obj.src, obj.width, obj.height) + } + + return result; + }; + + /** + * Parses the given background image. + */ + Graph.prototype.getBackgroundImageObject = function(obj) + { + return obj; + }; + + /** + * Translates this point by the given vector. + * + * @param {number} dx X-coordinate of the translation. + * @param {number} dy Y-coordinate of the translation. + */ + Graph.prototype.getSvg = function(background, scale, border, nocrop, crisp, + ignoreSelection, showText, imgExport, linkTarget, hasShadow, incExtFonts, + keepTheme, exportType, cells) + { + var lookup = null; + + if (cells != null) + { + lookup = new mxDictionary(); + + for (var i = 0; i < cells.length; i++) + { + lookup.put(cells[i], true); + } + } + + //Disable Css Transforms if it is used + var origUseCssTrans = this.useCssTransforms; + + if (origUseCssTrans) + { + this.useCssTransforms = false; + this.view.revalidate(); + this.sizeDidChange(); + } + + try + { + scale = (scale != null) ? scale : 1; + border = (border != null) ? border : 0; + crisp = (crisp != null) ? crisp : true; + ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true; + showText = (showText != null) ? showText : true; + hasShadow = (hasShadow != null) ? hasShadow : false; + + var bounds = (exportType == 'page') ? this.view.getBackgroundPageBounds() : + (((ignoreSelection && lookup == null) || nocrop || + exportType == 'diagram') ? this.getGraphBounds() : + this.getBoundingBox(this.getSelectionCells())); + var vs = this.view.scale; + + if (exportType == 'diagram' && this.backgroundImage != null) + { + bounds = mxRectangle.fromRectangle(bounds); + bounds.add(new mxRectangle( + (this.view.translate.x + this.backgroundImage.x) * vs, + (this.view.translate.y + this.backgroundImage.y) * vs, + this.backgroundImage.width * vs, + this.backgroundImage.height * vs)); + } + + if (bounds == null) + { + throw Error(mxResources.get('drawingEmpty')); + } + + // Prepares SVG document that holds the output + var s = scale / vs; + var w = Math.max(1, Math.ceil(bounds.width * s) + 2 * border) + + ((hasShadow && border == 0) ? 5 : 0); + var h = Math.max(1, Math.ceil(bounds.height * s) + 2 * border) + + ((hasShadow && border == 0) ? 5 : 0); + var tmp = (crisp) ? -0.5 : 0; + var root = Graph.createSvgNode(tmp, tmp, w, h, background); + var svgDoc = root.ownerDocument; + + // Renders graph. Offset will be multiplied with state's scale when painting state. + // TextOffset only seems to affect FF output but used everywhere for consistency. + var group = (svgDoc.createElementNS != null) ? + svgDoc.createElementNS(mxConstants.NS_SVG, 'g') : svgDoc.createElement('g'); + root.appendChild(group); + + var svgCanvas = this.createSvgCanvas(group); + svgCanvas.foOffset = (crisp) ? -0.5 : 0; + svgCanvas.textOffset = (crisp) ? -0.5 : 0; + svgCanvas.imageOffset = (crisp) ? -0.5 : 0; + svgCanvas.translate(Math.floor(border / scale - bounds.x / vs), + Math.floor(border / scale - bounds.y / vs)); + + // Convert HTML entities + var htmlConverter = document.createElement('div'); + + // Adds simple text fallback for viewers with no support for foreignObjects + var getAlternateText = svgCanvas.getAlternateText; + svgCanvas.getAlternateText = function(fo, x, y, w, h, str, + align, valign, wrap, format, overflow, clip, rotation) + { + // Assumes a max character width of 0.5em + if (str != null && this.state.fontSize > 0) + { + try + { + if (mxUtils.isNode(str)) + { + str = str.innerText; + } + else + { + htmlConverter.innerHTML = str; + str = mxUtils.extractTextWithWhitespace(htmlConverter.childNodes); + } + + // Workaround for substring breaking double byte UTF + var exp = Math.ceil(2 * w / this.state.fontSize); + var result = []; + var length = 0; + var index = 0; + + while ((exp == 0 || length < exp) && index < str.length) + { + var char = str.charCodeAt(index); + + if (char == 10 || char == 13) + { + if (length > 0) + { + break; + } + } + else + { + result.push(str.charAt(index)); + + if (char < 255) + { + length++; + } + } + + index++; + } + + // Uses result and adds ellipsis if more than 1 char remains + if (result.length < str.length && str.length - result.length > 1) + { + str = mxUtils.trim(result.join('')) + '...'; + } + + return str; + } + catch (e) + { + return getAlternateText.apply(this, arguments); + } + } + else + { + return getAlternateText.apply(this, arguments); + } + }; + + // Paints background image + var bgImg = this.backgroundImage; + + if (bgImg != null) + { + var s2 = vs / scale; + var tr = this.view.translate; + var tmp = new mxRectangle((bgImg.x + tr.x) * s2, (bgImg.y + tr.y) * s2, + bgImg.width * s2, bgImg.height * s2); + + // Checks if visible + if (mxUtils.intersects(bounds, tmp)) + { + svgCanvas.image(bgImg.x + tr.x, bgImg.y + tr.y, + bgImg.width, bgImg.height, bgImg.src, true); + } + } + + svgCanvas.scale(s); + svgCanvas.textEnabled = showText; + + imgExport = (imgExport != null) ? imgExport : this.createSvgImageExport(); + var imgExportDrawCellState = imgExport.drawCellState; + + // Ignores custom links + var imgExportGetLinkForCellState = imgExport.getLinkForCellState; + + imgExport.getLinkForCellState = function(state, canvas) + { + var result = imgExportGetLinkForCellState.apply(this, arguments); + + return (result != null && !state.view.graph.isCustomLink(result)) ? result : null; + }; + + imgExport.getLinkTargetForCellState = function(state, canvas) + { + return state.view.graph.getLinkTargetForCell(state.cell); + }; + + // Implements ignoreSelection flag + imgExport.drawCellState = function(state, canvas) + { + var graph = state.view.graph; + var selected = (lookup != null) ? lookup.get(state.cell) : + graph.isCellSelected(state.cell); + var parent = graph.model.getParent(state.cell); + + // Checks if parent cell is selected + while ((!ignoreSelection || lookup != null) && + !selected && parent != null) + { + selected = (lookup != null) ? lookup.get(parent) : + graph.isCellSelected(parent); + parent = graph.model.getParent(parent); + } + + if ((ignoreSelection && lookup == null) || selected) + { + graph.view.redrawEnumerationState(state); + imgExportDrawCellState.apply(this, arguments); + this.doDrawShape(state.secondLabel, canvas); + } + }; + + var viewRoot = (this.view.currentRoot != null) ? + this.view.currentRoot : this.model.root; + imgExport.drawState(this.getView().getState(viewRoot), svgCanvas); + this.updateSvgLinks(root, linkTarget, true); + this.addForeignObjectWarning(svgCanvas, root); + + return root; + } + finally + { + if (origUseCssTrans) + { + this.useCssTransforms = true; + this.view.revalidate(); + this.sizeDidChange(); + } + } + }; + + /** + * Adds warning for truncated labels in older viewers. + */ + Graph.prototype.addForeignObjectWarning = function(canvas, root) + { + if (urlParams['svg-warning'] != '0' && root.getElementsByTagName('foreignObject').length > 0) + { + var sw = canvas.createElement('switch'); + var g1 = canvas.createElement('g'); + g1.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility'); + var a = canvas.createElement('a'); + a.setAttribute('transform', 'translate(0,-5)'); + + // Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below + // in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output. + if (a.setAttributeNS == null || (root.ownerDocument != document && document.documentMode == null)) + { + a.setAttribute('xlink:href', Graph.foreignObjectWarningLink); + a.setAttribute('target', '_blank'); + } + else + { + a.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', Graph.foreignObjectWarningLink); + a.setAttributeNS(mxConstants.NS_XLINK, 'target', '_blank'); + } + + var text = canvas.createElement('text'); + text.setAttribute('text-anchor', 'middle'); + text.setAttribute('font-size', '10px'); + text.setAttribute('x', '50%'); + text.setAttribute('y', '100%'); + mxUtils.write(text, Graph.foreignObjectWarningText); + + sw.appendChild(g1); + a.appendChild(text); + sw.appendChild(a); + root.appendChild(sw); + } + }; + + /** + * Hook for creating the canvas used in getSvg. + */ + Graph.prototype.updateSvgLinks = function(node, target, removeCustom) + { + var links = node.getElementsByTagName('a'); + + for (var i = 0; i < links.length; i++) + { + if (links[i].getAttribute('target') == null) + { + var href = links[i].getAttribute('href'); + + if (href == null) + { + href = links[i].getAttribute('xlink:href'); + } + + if (href != null) + { + if (target != null && /^https?:\/\//.test(href)) + { + links[i].setAttribute('target', target); + } + else if (removeCustom && this.isCustomLink(href)) + { + links[i].setAttribute('href', 'javascript:void(0);'); + } + } + } + } + }; + + /** + * Hook for creating the canvas used in getSvg. + */ + Graph.prototype.createSvgCanvas = function(node) + { + var canvas = new mxSvgCanvas2D(node); + canvas.minStrokeWidth = this.cellRenderer.minSvgStrokeWidth; + canvas.pointerEvents = true; + + return canvas; + }; + + /** + * + */ + Graph.prototype.getSelectedTextBlocks = function() + { + // See https://stackoverflow.com/questions/667951/how-to-get-nodes-lying-inside-a-range-with-javascript + function getNextNode(node) + { + if (node.firstChild) + return node.firstChild; + while (node) + { + if (node.nextSibling) + return node.nextSibling; + node = node.parentNode; + } + }; + + function getNodesInRange(range) + { + var start = range.startContainer; + var end = range.endContainer; + var commonAncestor = range.commonAncestorContainer; + var nodes = []; + var node; + + // walk parent nodes from start to common ancestor + for (node = start.parentNode; node; node = node.parentNode) + { + nodes.push(node); + if (node == commonAncestor) + break; + } + nodes.reverse(); + + // walk children and siblings from start until end is found + for (node = start; node; node = getNextNode(node)) + { + nodes.push(node); + if (node == end) + break; + } + + return nodes; + }; + + var nodes = [this.getSelectedElement()]; + + if (window.getSelection) + { + var sel = window.getSelection(); + + if (sel.getRangeAt && sel.rangeCount) + { + nodes = getNodesInRange(sel.getRangeAt(0)); + } + } + + var result = []; + + for (var i = 0; i < nodes.length; i++) + { + var node = nodes[i]; + + while (this.cellEditor.textarea != null && + this.cellEditor.textarea.contains(node) && + node != this.cellEditor.textarea && + node.parentNode != null) + { + if (node.nodeType == mxConstants.NODETYPE_ELEMENT && + mxUtils.getCurrentStyle(node).display == 'block') + { + if (mxUtils.indexOf(result, node) < 0) + { + result.push(node); + } + + break; + } + else + { + node = node.parentNode; + } + } + } + + return result; + }; + + /** + * Returns the first ancestor of the current selection with the given name. + */ + Graph.prototype.getSelectedElement = function() + { + var node = null; + + if (window.getSelection) + { + var sel = window.getSelection(); + + if (sel.getRangeAt && sel.rangeCount) + { + var range = sel.getRangeAt(0); + node = range.commonAncestorContainer; + } + } + else if (document.selection) + { + node = document.selection.createRange().parentElement(); + } + + return node; + }; + + /** + * Returns the text editing element. + */ + Graph.prototype.getSelectedEditingElement = function() + { + var node = this.getSelectedElement(); + + while (node != null && node.nodeType != mxConstants.NODETYPE_ELEMENT) + { + node = node.parentNode; + } + + if (node != null) + { + // Workaround for commonAncestor on range in IE11 returning parent of common ancestor + if (node == this.cellEditor.textarea && this.cellEditor.textarea.children.length == 1 && + this.cellEditor.textarea.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT) + { + node = this.cellEditor.textarea.firstChild; + } + } + + return node; + }; + + /** + * Returns the first ancestor of the current selection with the given name. + */ + Graph.prototype.getParentByName = function(node, name, stopAt) + { + while (node != null) + { + if (node.nodeName == name) + { + return node; + } + + if (node == stopAt) + { + return null; + } + + node = node.parentNode; + } + + return node; + }; + + /** + * Returns the first ancestor of the current selection with the given name. + */ + Graph.prototype.getParentByNames = function(node, names, stopAt) + { + while (node != null) + { + if (mxUtils.indexOf(names, node.nodeName) >= 0) + { + return node; + } + + if (node == stopAt) + { + return null; + } + + node = node.parentNode; + } + + return node; + }; + + /** + * Selects the given node. + */ + Graph.prototype.selectNode = function(node) + { + var sel = null; + + // IE9 and non-IE + if (window.getSelection) + { + sel = window.getSelection(); + + if (sel.getRangeAt && sel.rangeCount) + { + var range = document.createRange(); + range.selectNode(node); + sel.removeAllRanges(); + sel.addRange(range); + } + } + // IE < 9 + else if ((sel = document.selection) && sel.type != 'Control') + { + var originalRange = sel.createRange(); + originalRange.collapse(true); + var range = sel.createRange(); + range.setEndPoint('StartToStart', originalRange); + range.select(); + } + }; + + /** + * Flips the given cells horizontally or vertically. + */ + Graph.prototype.flipEdgePoints = function(cell, horizontal, c) + { + var geo = this.getCellGeometry(cell); + + if (geo != null) + { + geo = geo.clone(); + + if (geo.points != null) + { + for (var i = 0; i < geo.points.length; i++) + { + if (horizontal) + { + geo.points[i].x = c + (c - geo.points[i].x); + } + else + { + geo.points[i].y = c + (c - geo.points[i].y); + } + } + } + + var flipTerminalPoint = function(pt) + { + if (pt != null) + { + if (horizontal) + { + pt.x = c + (c - pt.x); + } + else + { + pt.y = c + (c - pt.y); + } + } + }; + + flipTerminalPoint(geo.getTerminalPoint(true)); + flipTerminalPoint(geo.getTerminalPoint(false)); + + this.model.setGeometry(cell, geo); + } + }; + + /** + * Flips the given cells horizontally or vertically. + */ + Graph.prototype.flipChildren = function(cell, horizontal, c) + { + this.model.beginUpdate(); + try + { + var childCount = this.model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + var child = this.model.getChildAt(cell, i); + + if (this.model.isEdge(child)) + { + this.flipEdgePoints(child, horizontal, c); + } + else + { + var geo = this.getCellGeometry(child); + + if (geo != null) + { + geo = geo.clone(); + + if (horizontal) + { + geo.x = c + (c - geo.x - geo.width); + } + else + { + geo.y = c + (c - geo.y - geo.height); + } + + this.model.setGeometry(child, geo); + } + } + } + } + finally + { + this.model.endUpdate(); + } + }; + + /** + * Flips the given cells horizontally or vertically. + */ + Graph.prototype.flipCells = function(cells, horizontal) + { + this.model.beginUpdate(); + try + { + cells = this.model.getTopmostCells(cells); + var vertices = []; + + for (var i = 0; i < cells.length; i++) + { + if (this.model.isEdge(cells[i])) + { + var state = this.view.getState(cells[i]); + + if (state != null) + { + this.flipEdgePoints(cells[i], horizontal, ((horizontal ? state.getCenterX() : + state.getCenterY()) / this.view.scale) - ((horizontal) ? + state.origin.x : state.origin.y) - ((horizontal) ? + this.view.translate.x : this.view.translate.y)); + } + } + else + { + var geo = this.getCellGeometry(cells[i]); + + if (geo != null) + { + this.flipChildren(cells[i], horizontal, horizontal ? + geo.getCenterX() - geo.x : + geo.getCenterY() - geo.y); + } + + vertices.push(cells[i]); + } + } + + this.toggleCellStyles(horizontal ? mxConstants.STYLE_FLIPH : + mxConstants.STYLE_FLIPV, false, vertices); + } + finally + { + this.model.endUpdate(); + } + }; + + /** + * Deletes the given cells and returns the cells to be selected. + */ + Graph.prototype.deleteCells = function(cells, includeEdges) + { + var select = null; + + if (cells != null && cells.length > 0) + { + this.model.beginUpdate(); + try + { + // Shrinks tables + for (var i = 0; i < cells.length; i++) + { + var parent = this.model.getParent(cells[i]); + + if (this.isTable(parent)) + { + var row = this.getCellGeometry(cells[i]); + var table = this.getCellGeometry(parent); + + if (row != null && table != null) + { + table = table.clone(); + table.height -= row.height; + this.model.setGeometry(parent, table); + } + } + } + + var parents = (this.selectParentAfterDelete) ? this.model.getParents(cells) : null; + this.removeCells(cells, includeEdges); + } + finally + { + this.model.endUpdate(); + } + + // Selects parents for easier editing of groups + if (parents != null) + { + select = []; + + for (var i = 0; i < parents.length; i++) + { + if (this.model.contains(parents[i]) && + (this.model.isVertex(parents[i]) || + this.model.isEdge(parents[i]))) + { + select.push(parents[i]); + } + } + } + } + + return select; + }; + + /** + * Inserts a column in the table for the given cell. + */ + Graph.prototype.insertTableColumn = function(cell, before) + { + var model = this.getModel(); + model.beginUpdate(); + + try + { + var table = cell; + var index = 0; + + if (this.isTableCell(cell)) + { + var row = model.getParent(cell); + table = model.getParent(row); + index = mxUtils.indexOf(model.getChildCells(row, true), cell); + } + else + { + if (this.isTableRow(cell)) + { + table = model.getParent(cell); + } + else + { + cell = model.getChildCells(table, true)[0]; + } + + if (!before) + { + index = model.getChildCells(cell, true).length - 1; + } + } + + var rows = model.getChildCells(table, true); + var dw = Graph.minTableColumnWidth; + + for (var i = 0; i < rows.length; i++) + { + var child = model.getChildCells(rows[i], true)[index]; + var clone = model.cloneCell(child, false); + + // Handles possible missing child in row + if (clone == null) + { + clone = this.createVertex(); + } + + var geo = this.getCellGeometry(clone); + + // Removes value, col/rowspan and alternate bounds + clone.value = null; + clone.style = mxUtils.setStyle(mxUtils.setStyle( + clone.style, 'rowspan', null), 'colspan', null); + + if (geo != null) + { + if (geo.alternateBounds != null) + { + geo.width = geo.alternateBounds.width; + geo.height = geo.alternateBounds.height; + geo.alternateBounds = null; + } + + dw = geo.width; + var rowGeo = this.getCellGeometry(rows[i]); + + if (rowGeo != null) + { + geo.height = rowGeo.height; + } + } + + model.add(rows[i], clone, index + ((before) ? 0 : 1)); + } + + var tableGeo = this.getCellGeometry(table); + + if (tableGeo != null) + { + tableGeo = tableGeo.clone(); + tableGeo.width += dw; + + model.setGeometry(table, tableGeo); + } + } + finally + { + model.endUpdate(); + } + }; + + /** + * Inserts a row in the table for the given cell. + */ + Graph.prototype.deleteLane = function(cell) + { + var model = this.getModel(); + model.beginUpdate(); + + try + { + var pool = null; + var lane = cell; + var style = this.getCurrentCellStyle(lane); + + if (style['childLayout'] == 'stackLayout') + { + pool = lane; + } + else + { + pool = model.getParent(lane); + } + + var lanes = model.getChildCells(pool, true); + + if (lanes.length == 0) + { + model.remove(pool); + } + else + { + if (pool == lane) + { + lane = lanes[lanes.length - 1]; + } + + model.remove(lane); + } + } + finally + { + model.endUpdate(); + } + }; + + /** + * Inserts a row in the table for the given cell. + */ + Graph.prototype.insertLane = function(cell, before) + { + var model = this.getModel(); + model.beginUpdate(); + + try + { + var pool = null; + var lane = cell; + var style = this.getCurrentCellStyle(lane); + + if (style['childLayout'] == 'stackLayout') + { + pool = lane; + var lanes = model.getChildCells(pool, true); + lane = lanes[(before) ? 0 : lanes.length - 1]; + } + else + { + pool = model.getParent(lane); + } + + var index = pool.getIndex(lane); + lane = model.cloneCell(lane, false); + lane.value = null; + model.add(pool, lane, index + ((before) ? 0 : 1)); + } + finally + { + model.endUpdate(); + } + }; + + /** + * Inserts a row in the table for the given cell. + */ + Graph.prototype.insertTableRow = function(cell, before) + { + var model = this.getModel(); + model.beginUpdate(); + + try + { + var table = cell; + var row = cell; + + if (this.isTableCell(cell)) + { + row = model.getParent(cell); + table = model.getParent(row); + } + else if (this.isTableRow(cell)) + { + table = model.getParent(cell); + } + else + { + var rows = model.getChildCells(table, true); + row = rows[(before) ? 0 : rows.length - 1]; + } + + var cells = model.getChildCells(row, true); + var index = table.getIndex(row); + row = model.cloneCell(row, false); + row.value = null; + + var rowGeo = this.getCellGeometry(row); + + if (rowGeo != null) + { + for (var i = 0; i < cells.length; i++) + { + var cell = model.cloneCell(cells[i], false); + + // Removes value, col/rowspan and alternate bounds + cell.value = null; + cell.style = mxUtils.setStyle(mxUtils.setStyle( + cell.style, 'rowspan', null), 'colspan', null); + + var geo = this.getCellGeometry(cell); + + if (geo != null) + { + if (geo.alternateBounds != null) + { + geo.width = geo.alternateBounds.width; + geo.height = geo.alternateBounds.height; + geo.alternateBounds = null; + } + + geo.height = rowGeo.height; + } + + row.insert(cell); + } + + model.add(table, row, index + ((before) ? 0 : 1)); + + var tableGeo = this.getCellGeometry(table); + + if (tableGeo != null) + { + tableGeo = tableGeo.clone(); + tableGeo.height += rowGeo.height; + + model.setGeometry(table, tableGeo); + } + } + } + finally + { + model.endUpdate(); + } + }; + + /** + * + */ + Graph.prototype.deleteTableColumn = function(cell) + { + var model = this.getModel(); + model.beginUpdate(); + + try + { + var table = cell; + var row = cell; + + if (this.isTableCell(cell)) + { + row = model.getParent(cell); + } + + if (this.isTableRow(row)) + { + table = model.getParent(row); + } + + var rows = model.getChildCells(table, true); + + if (rows.length == 0) + { + model.remove(table); + } + else + { + if (!this.isTableRow(row)) + { + row = rows[0]; + } + + var cells = model.getChildCells(row, true); + + if (cells.length <= 1) + { + model.remove(table); + } + else + { + var index = cells.length - 1; + + if (this.isTableCell(cell)) + { + index = mxUtils.indexOf(cells, cell); + } + + var width = 0; + + for (var i = 0; i < rows.length; i++) + { + var child = model.getChildCells(rows[i], true)[index]; + model.remove(child); + + var geo = this.getCellGeometry(child); + + if (geo != null) + { + width = Math.max(width, geo.width); + } + } + + var tableGeo = this.getCellGeometry(table); + + if (tableGeo != null) + { + tableGeo = tableGeo.clone(); + tableGeo.width -= width; + + model.setGeometry(table, tableGeo); + } + } + } + } + finally + { + model.endUpdate(); + } + }; + + /** + * + */ + Graph.prototype.deleteTableRow = function(cell) + { + var model = this.getModel(); + model.beginUpdate(); + + try + { + var table = cell; + var row = cell; + + if (this.isTableCell(cell)) + { + row = model.getParent(cell); + cell = row; + } + + if (this.isTableRow(cell)) + { + table = model.getParent(row); + } + + var rows = model.getChildCells(table, true); + + if (rows.length <= 1) + { + model.remove(table); + } + else + { + if (!this.isTableRow(row)) + { + row = rows[rows.length - 1]; + } + + model.remove(row); + var height = 0; + + var geo = this.getCellGeometry(row); + + if (geo != null) + { + height = geo.height; + } + + var tableGeo = this.getCellGeometry(table); + + if (tableGeo != null) + { + tableGeo = tableGeo.clone(); + tableGeo.height -= height; + + model.setGeometry(table, tableGeo); + } + } + } + finally + { + model.endUpdate(); + } + }; + + /** + * Inserts a new row into the given table. + */ + Graph.prototype.insertRow = function(table, index) + { + var bd = table.tBodies[0]; + var cells = bd.rows[0].cells; + var cols = 0; + + // Counts columns including colspans + for (var i = 0; i < cells.length; i++) + { + var colspan = cells[i].getAttribute('colspan'); + cols += (colspan != null) ? parseInt(colspan) : 1; + } + + var row = bd.insertRow(index); + + for (var i = 0; i < cols; i++) + { + mxUtils.br(row.insertCell(-1)); + } + + return row.cells[0]; + }; + + /** + * Deletes the given column. + */ + Graph.prototype.deleteRow = function(table, index) + { + table.tBodies[0].deleteRow(index); + }; + + /** + * Deletes the given column. + */ + Graph.prototype.insertColumn = function(table, index) + { + var hd = table.tHead; + + if (hd != null) + { + // TODO: use colIndex + for (var h = 0; h < hd.rows.length; h++) + { + var th = document.createElement('th'); + hd.rows[h].appendChild(th); + mxUtils.br(th); + } + } + + var bd = table.tBodies[0]; + + for (var i = 0; i < bd.rows.length; i++) + { + var cell = bd.rows[i].insertCell(index); + mxUtils.br(cell); + } + + return bd.rows[0].cells[(index >= 0) ? index : bd.rows[0].cells.length - 1]; + }; + + /** + * Deletes the given column. + */ + Graph.prototype.deleteColumn = function(table, index) + { + if (index >= 0) + { + var bd = table.tBodies[0]; + var rows = bd.rows; + + for (var i = 0; i < rows.length; i++) + { + if (rows[i].cells.length > index) + { + rows[i].deleteCell(index); + } + } + } + }; + + /** + * Inserts the given HTML at the caret position (no undo). + */ + Graph.prototype.pasteHtmlAtCaret = function(html) + { + var sel, range; + + // IE9 and non-IE + if (window.getSelection) + { + sel = window.getSelection(); + + if (sel.getRangeAt && sel.rangeCount) + { + range = sel.getRangeAt(0); + range.deleteContents(); + + // Range.createContextualFragment() would be useful here but is + // only relatively recently standardized and is not supported in + // some browsers (IE9, for one) + var el = document.createElement("div"); + el.innerHTML = html; + var frag = document.createDocumentFragment(), node; + + while ((node = el.firstChild)) + { + lastNode = frag.appendChild(node); + } + + range.insertNode(frag); + } + } + // IE < 9 + else if ((sel = document.selection) && sel.type != "Control") + { + // FIXME: Does not work if selection is empty + sel.createRange().pasteHTML(html); + } + }; + + /** + * Creates an anchor elements for handling the given link in the + * hint that is shown when the cell is selected. + */ + Graph.prototype.createLinkForHint = function(link, label) + { + link = (link != null) ? link : 'javascript:void(0);'; + + if (label == null || label.length == 0) + { + if (this.isCustomLink(link)) + { + label = this.getLinkTitle(link); + } + else + { + label = link; + } + } + + // Helper function to shorten strings + function short(str, max) + { + if (str.length > max) + { + str = str.substring(0, Math.round(max / 2)) + '...' + + str.substring(str.length - Math.round(max / 4)); + } + + return str; + }; + + var a = document.createElement('a'); + a.setAttribute('rel', this.linkRelation); + a.setAttribute('href', this.getAbsoluteUrl(link)); + a.setAttribute('title', short((this.isCustomLink(link)) ? + this.getLinkTitle(link) : link, 80)); + + if (this.linkTarget != null) + { + a.setAttribute('target', this.linkTarget); + } + + // Adds shortened label to link + mxUtils.write(a, short(label, 40)); + + // Handles custom links + if (this.isCustomLink(link)) + { + mxEvent.addListener(a, 'click', mxUtils.bind(this, function(evt) + { + this.customLinkClicked(link); + mxEvent.consume(evt); + })); + } + + return a; + }; + + /** + * Customized graph for touch devices. + */ + Graph.prototype.initTouch = function() + { + // Disables new connections via "hotspot" + this.connectionHandler.marker.isEnabled = function() + { + return this.graph.connectionHandler.first != null; + }; + + // Hides menu when editing starts + this.addListener(mxEvent.START_EDITING, function(sender, evt) + { + this.popupMenuHandler.hideMenu(); + }); + + // Adds custom hit detection if native hit detection found no cell + var graphUpdateMouseEvent = this.updateMouseEvent; + this.updateMouseEvent = function(me) + { + me = graphUpdateMouseEvent.apply(this, arguments); + + if (mxEvent.isTouchEvent(me.getEvent()) && me.getState() == null) + { + var cell = this.getCellAt(me.graphX, me.graphY); + + if (cell != null && this.isSwimlane(cell) && this.hitsSwimlaneContent(cell, me.graphX, me.graphY)) + { + cell = null; + } + else + { + me.state = this.view.getState(cell); + + if (me.state != null && me.state.shape != null) + { + this.container.style.cursor = me.state.shape.node.style.cursor; + } + } + } + + if (me.getState() == null && this.isEnabled()) + { + this.container.style.cursor = 'default'; + } + + return me; + }; + + // Context menu trigger implementation depending on current selection state + // combined with support for normal popup trigger. + var cellSelected = false; + var selectionEmpty = false; + var menuShowing = false; + + var oldFireMouseEvent = this.fireMouseEvent; + + this.fireMouseEvent = function(evtName, me, sender) + { + if (evtName == mxEvent.MOUSE_DOWN) + { + // For hit detection on edges + me = this.updateMouseEvent(me); + + cellSelected = this.isCellSelected(me.getCell()); + selectionEmpty = this.isSelectionEmpty(); + menuShowing = this.popupMenuHandler.isMenuShowing(); + } + + oldFireMouseEvent.apply(this, arguments); + }; + + // Shows popup menu if cell was selected or selection was empty and background was clicked + // FIXME: Conflicts with mxPopupMenuHandler.prototype.getCellForPopupEvent in Editor.js by + // selecting parent for selected children in groups before this check can be made. + this.popupMenuHandler.mouseUp = mxUtils.bind(this, function(sender, me) + { + var isMouseEvent = mxEvent.isMouseEvent(me.getEvent()); + this.popupMenuHandler.popupTrigger = !this.isEditing() && this.isEnabled() && + (me.getState() == null || !me.isSource(me.getState().control)) && + (this.popupMenuHandler.popupTrigger || (!menuShowing && !isMouseEvent && + ((selectionEmpty && me.getCell() == null && this.isSelectionEmpty()) || + (cellSelected && this.isCellSelected(me.getCell()))))); + + // Delays popup menu to allow for double tap to start editing + var popup = (!cellSelected || isMouseEvent) ? null : mxUtils.bind(this, function(cell) + { + window.setTimeout(mxUtils.bind(this, function() + { + if (!this.isEditing()) + { + var origin = mxUtils.getScrollOrigin(); + this.popupMenuHandler.popup(me.getX() + origin.x + 1, + me.getY() + origin.y + 1, cell, me.getEvent()); + } + }), 300); + }); + + mxPopupMenuHandler.prototype.mouseUp.apply(this.popupMenuHandler, [sender, me, popup]); + }); + }; + + /** + * HTML in-place editor + */ + mxCellEditor.prototype.isContentEditing = function() + { + var state = this.graph.view.getState(this.editingCell); + + return state != null && state.style['html'] == 1; + }; + + /** + * Returns true if all selected text is inside a table element. + */ + mxCellEditor.prototype.isTableSelected = function() + { + return this.graph.getParentByName( + this.graph.getSelectedElement(), + 'TABLE', this.textarea) != null; + }; + + /** + * Returns true if text is selected. + */ + mxCellEditor.prototype.isTextSelected = function() + { + var txt = ''; + + if (window.getSelection) + { + txt = window.getSelection(); + } + else if (document.getSelection) + { + txt = document.getSelection(); + } + else if (document.selection) + { + txt = document.selection.createRange().text; + } + + return txt != ''; + }; + + /** + * Inserts a tab at the cursor position. + */ + mxCellEditor.prototype.insertTab = function(spaces) + { + var editor = this.textarea; + var doc = editor.ownerDocument.defaultView; + var sel = doc.getSelection(); + var range = sel.getRangeAt(0); + var tabNode = Graph.createTabNode(spaces); + range.insertNode(tabNode); + range.setStartAfter(tabNode); + range.setEndAfter(tabNode); + sel.removeAllRanges(); + sel.addRange(range); + }; + + /** + * Sets the alignment of the current selected cell. This sets the + * alignment in the cell style, removes all alignment within the + * text and invokes the built-in alignment function. + * + * Only the built-in function is invoked if shift is pressed or + * if table cells are selected and shift is not pressed. + */ + mxCellEditor.prototype.alignText = function(align, evt) + { + var shiftPressed = evt != null && mxEvent.isShiftDown(evt); + + if (shiftPressed || (window.getSelection != null && window.getSelection().containsNode != null)) + { + var allSelected = true; + + this.graph.processElements(this.textarea, function(node) + { + if (shiftPressed || window.getSelection().containsNode(node, true)) + { + node.removeAttribute('align'); + node.style.textAlign = null; + } + else + { + allSelected = false; + } + }); + + if (allSelected) + { + this.graph.cellEditor.setAlign(align); + } + } + + document.execCommand('justify' + align.toLowerCase(), false, null); + }; + + /** + * Creates the keyboard event handler for the current graph and history. + */ + mxCellEditor.prototype.saveSelection = function() + { + if (window.getSelection) + { + var sel = window.getSelection(); + + if (sel.getRangeAt && sel.rangeCount) + { + var ranges = []; + + for (var i = 0, len = sel.rangeCount; i < len; ++i) + { + ranges.push(sel.getRangeAt(i)); + } + + return ranges; + } + } + else if (document.selection && document.selection.createRange) + { + return document.selection.createRange(); + } + + return null; + }; + + /** + * Creates the keyboard event handler for the current graph and history. + */ + mxCellEditor.prototype.restoreSelection = function(savedSel) + { + try + { + if (savedSel) + { + if (window.getSelection) + { + sel = window.getSelection(); + sel.removeAllRanges(); + + for (var i = 0, len = savedSel.length; i < len; ++i) + { + sel.addRange(savedSel[i]); + } + } + else if (document.selection && savedSel.select) + { + savedSel.select(); + } + } + } + catch (e) + { + // ignore + } + }; + + /** + * Handling of special nl2Br style for not converting newlines to breaks in HTML labels. + * NOTE: Since it's easier to set this when the label is created we assume that it does + * not change during the lifetime of the mxText instance. + */ + var mxCellRendererInitializeLabel = mxCellRenderer.prototype.initializeLabel; + mxCellRenderer.prototype.initializeLabel = function(state) + { + if (state.text != null) + { + state.text.replaceLinefeeds = mxUtils.getValue(state.style, 'nl2Br', '1') != '0'; + } + + mxCellRendererInitializeLabel.apply(this, arguments); + }; + + var mxConstraintHandlerUpdate = mxConstraintHandler.prototype.update; + mxConstraintHandler.prototype.update = function(me, source) + { + if (this.isKeepFocusEvent(me) || !mxEvent.isAltDown(me.getEvent())) + { + mxConstraintHandlerUpdate.apply(this, arguments); + } + else + { + this.reset(); + } + }; + + /** + * No dashed shapes. + */ + mxGuide.prototype.createGuideShape = function(horizontal) + { + var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH); + + return guide; + }; + + /** + * HTML in-place editor + */ + mxCellEditor.prototype.escapeCancelsEditing = false; + + /** + * Overridden to set CSS classes. + */ + var mxCellEditorStartEditing = mxCellEditor.prototype.startEditing; + mxCellEditor.prototype.startEditing = function(cell, trigger) + { + cell = this.graph.getStartEditingCell(cell, trigger); + + mxCellEditorStartEditing.apply(this, arguments); + + // Overrides class in case of HTML content to add + // dashed borders for divs and table cells + var state = this.graph.view.getState(cell); + + if (state != null && state.style['html'] == 1) + { + this.textarea.className = 'mxCellEditor geContentEditable'; + } + else + { + this.textarea.className = 'mxCellEditor mxPlainTextEditor'; + } + + // Toggles markup vs wysiwyg mode + this.codeViewMode = false; + + // Stores current selection range when switching between markup and code + this.switchSelectionState = null; + + // Selects editing cell + this.graph.setSelectionCell(cell); + + // Enables focus outline for edges and edge labels + var parent = this.graph.getModel().getParent(cell); + var geo = this.graph.getCellGeometry(cell); + + if ((this.graph.getModel().isEdge(parent) && geo != null && geo.relative) || + this.graph.getModel().isEdge(cell)) + { + // IE>8 and FF on Windows uses outline default of none + if (mxClient.IS_IE || mxClient.IS_IE11 || (mxClient.IS_FF && mxClient.IS_WIN)) + { + this.textarea.style.outline = 'gray dotted 1px'; + } + else + { + this.textarea.style.outline = ''; + } + } + } + + /** + * HTML in-place editor + */ + var cellEditorInstallListeners = mxCellEditor.prototype.installListeners; + mxCellEditor.prototype.installListeners = function(elt) + { + cellEditorInstallListeners.apply(this, arguments); + + // Adds a reference from the clone to the original node, recursively + function reference(node, clone) + { + clone.originalNode = node; + + node = node.firstChild; + var child = clone.firstChild; + + while (node != null && child != null) + { + reference(node, child); + node = node.nextSibling; + child = child.nextSibling; + } + + return clone; + }; + + // Checks the given node for new nodes, recursively + function checkNode(node, clone) + { + if (node != null) + { + if (clone.originalNode != node) + { + cleanNode(node); + } + else + { + node = node.firstChild; + clone = clone.firstChild; + + while (node != null) + { + var nextNode = node.nextSibling; + + if (clone == null) + { + cleanNode(node); + } + else + { + checkNode(node, clone); + clone = clone.nextSibling; + } + + node = nextNode; + } + } + } + }; + + // Removes unused DOM nodes and attributes, recursively + function cleanNode(node) + { + var child = node.firstChild; + + while (child != null) + { + var next = child.nextSibling; + cleanNode(child); + child = next; + } + + if ((node.nodeType != 1 || (node.nodeName !== 'BR' && node.firstChild == null)) && + (node.nodeType != 3 || mxUtils.trim(mxUtils.getTextContent(node)).length == 0)) + { + node.parentNode.removeChild(node); + } + else + { + // Removes linefeeds + if (node.nodeType == 3) + { + mxUtils.setTextContent(node, mxUtils.getTextContent(node).replace(/\n|\r/g, '')); + } + + // Removes CSS classes and styles (for Word and Excel) + if (node.nodeType == 1) + { + node.removeAttribute('style'); + node.removeAttribute('class'); + node.removeAttribute('width'); + node.removeAttribute('cellpadding'); + node.removeAttribute('cellspacing'); + node.removeAttribute('border'); + } + } + }; + + // Handles paste from Word, Excel etc by removing styles, classnames and unused nodes + // LATER: Fix undo/redo for paste + if (document.documentMode !== 7 && document.documentMode !== 8) + { + mxEvent.addListener(this.textarea, 'paste', mxUtils.bind(this, function(evt) + { + var clone = reference(this.textarea, this.textarea.cloneNode(true)); + + window.setTimeout(mxUtils.bind(this, function() + { + if (this.textarea != null) + { + // Paste from Word or Excel + if (this.textarea.innerHTML.indexOf('') >= 0 || + this.textarea.innerHTML.indexOf(' + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-bold.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-bold.svg new file mode 100644 index 0000000..d0aeceb --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-bold.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-bottom.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-bottom.svg new file mode 100644 index 0000000..fd16bb5 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-bottom.svg @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-center.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-center.svg new file mode 100644 index 0000000..8e53158 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-center.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-code.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-code.svg new file mode 100644 index 0000000..120ae11 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-code.svg @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-connection.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-connection.svg new file mode 100644 index 0000000..2d69f9e --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-connection.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-curved.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-curved.svg new file mode 100644 index 0000000..769bfbf --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-curved.svg @@ -0,0 +1,13 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-delete.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-delete.svg new file mode 100644 index 0000000..071fa43 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-delete.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-dots.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-dots.svg new file mode 100644 index 0000000..944f52a --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-dots.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-entity.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-entity.svg new file mode 100644 index 0000000..9aeebd4 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-entity.svg @@ -0,0 +1,13 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-fit.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-fit.svg new file mode 100644 index 0000000..45b3e2c --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-fit.svg @@ -0,0 +1,45 @@ + + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-fontbackground.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-fontbackground.svg new file mode 100644 index 0000000..c068dc8 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-fontbackground.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-fontcolor.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-fontcolor.svg new file mode 100644 index 0000000..13a6d06 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-fontcolor.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-formatpanel.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-formatpanel.svg new file mode 100644 index 0000000..50f04c4 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-formatpanel.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-horizontalelbow.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-horizontalelbow.svg new file mode 100644 index 0000000..ba4c53e --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-horizontalelbow.svg @@ -0,0 +1,45 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-horizontalisometric.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-horizontalisometric.svg new file mode 100644 index 0000000..9bd95bf --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-horizontalisometric.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-horizontalrule.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-horizontalrule.svg new file mode 100644 index 0000000..0372f6b --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-horizontalrule.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-indent.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-indent.svg new file mode 100644 index 0000000..b38419e --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-indent.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-italic.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-italic.svg new file mode 100644 index 0000000..f5d1050 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-italic.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-justifyfull.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-justifyfull.svg new file mode 100644 index 0000000..a78cad0 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-justifyfull.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-left.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-left.svg new file mode 100644 index 0000000..12fbac7 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-left.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-link.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-link.svg new file mode 100644 index 0000000..4eac588 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-link.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-linkedge.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-linkedge.svg new file mode 100644 index 0000000..b3ed4b8 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-linkedge.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-middle.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-middle.svg new file mode 100644 index 0000000..c9425eb --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-middle.svg @@ -0,0 +1,40 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-orderedlist.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-orderedlist.svg new file mode 100644 index 0000000..e7828ab --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-orderedlist.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-orthogonal.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-orthogonal.svg new file mode 100644 index 0000000..64de585 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-orthogonal.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-outdent.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-outdent.svg new file mode 100644 index 0000000..dfe17a8 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-outdent.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-plus.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-plus.svg new file mode 100644 index 0000000..37b9b74 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-plus.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-redo.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-redo.svg new file mode 100644 index 0000000..3c353fd --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-redo.svg @@ -0,0 +1,45 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-removeformat.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-removeformat.svg new file mode 100644 index 0000000..78bd454 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-removeformat.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-right.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-right.svg new file mode 100644 index 0000000..ff375c6 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-right.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-shadow.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-shadow.svg new file mode 100644 index 0000000..7113884 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-shadow.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-simplearrow.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-simplearrow.svg new file mode 100644 index 0000000..4259b39 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-simplearrow.svg @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-straight.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-straight.svg new file mode 100644 index 0000000..fd2ec9b --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-straight.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-strokecolor.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-strokecolor.svg new file mode 100644 index 0000000..cd3cc12 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-strokecolor.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-subscript.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-subscript.svg new file mode 100644 index 0000000..197492b --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-subscript.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-superscript.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-superscript.svg new file mode 100644 index 0000000..d678c32 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-superscript.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-table.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-table.svg new file mode 100644 index 0000000..00fa4aa --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-table.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-toback.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-toback.svg new file mode 100644 index 0000000..c8d108e --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-toback.svg @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-tofront.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-tofront.svg new file mode 100644 index 0000000..0d4a95a --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-tofront.svg @@ -0,0 +1,43 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-top.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-top.svg new file mode 100644 index 0000000..dfde68f --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-top.svg @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-underline.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-underline.svg new file mode 100644 index 0000000..e6b807b --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-underline.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-undo.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-undo.svg new file mode 100644 index 0000000..7100b88 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-undo.svg @@ -0,0 +1,45 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-unorderedlist.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-unorderedlist.svg new file mode 100644 index 0000000..b6fb870 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-unorderedlist.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-vertical.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-vertical.svg new file mode 100644 index 0000000..b9b08c1 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-vertical.svg @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-verticalelbow.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-verticalelbow.svg new file mode 100644 index 0000000..3b6fa63 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-verticalelbow.svg @@ -0,0 +1,40 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-verticalisometric.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-verticalisometric.svg new file mode 100644 index 0000000..6de0229 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-verticalisometric.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-zoomin.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-zoomin.svg new file mode 100644 index 0000000..751ecce --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-zoomin.svg @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-zoomout.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-zoomout.svg new file mode 100644 index 0000000..5eb98b5 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-zoomout.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-zz-填充色_icon.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-zz-填充色_icon.svg new file mode 100644 index 0000000..58c8837 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-zz-填充色_icon.svg @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-zz-复选框.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-zz-复选框.svg new file mode 100644 index 0000000..b72daf6 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-zz-复选框.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-zz-查看画图2.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-zz-查看画图2.svg new file mode 100644 index 0000000..50f04c4 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-zz-查看画图2.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/oaweb/public/cherry/drawio/font/svg/geSprite-zz-线条颜色_icon.svg b/oaweb/public/cherry/drawio/font/svg/geSprite-zz-线条颜色_icon.svg new file mode 100644 index 0000000..cd3cc12 --- /dev/null +++ b/oaweb/public/cherry/drawio/font/svg/geSprite-zz-线条颜色_icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/oaweb/public/cherry/drawio/fonts/ArchitectsDaughter-Regular.ttf b/oaweb/public/cherry/drawio/fonts/ArchitectsDaughter-Regular.ttf new file mode 100644 index 0000000..421f247 Binary files /dev/null and b/oaweb/public/cherry/drawio/fonts/ArchitectsDaughter-Regular.ttf differ diff --git a/oaweb/public/cherry/drawio/grapheditor.css b/oaweb/public/cherry/drawio/grapheditor.css new file mode 100644 index 0000000..22c088a --- /dev/null +++ b/oaweb/public/cherry/drawio/grapheditor.css @@ -0,0 +1,2101 @@ +:root { + --panel-color: #f1f3f4; + --border-color: #dadce0; + --text-color: #707070; +} +.geEditor *, div.mxWindow, .mxWindowTitle, +.geEditor .geToolbarContainer .geColorButton { + border-color:var(--border-color); +} +html div.mxWindow, .geDialog, .geSketch .geToolbarContainer { + border-radius: 5px; + box-shadow: 0px 0px 2px #C0C0C0; +} +div td.mxWindowTitle { + border-bottom-style:solid; + border-bottom-width:1px; + font-size: 13px; + height: 22px; +} +.mxWindowTitle > div > img { + padding: 4px; +} +.geEditor { + font-family:-apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", system-ui, ui-sans-serif, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size:14px; + border:none; + margin:0px; +} +.geBackground { + background:white; +} +.geEditor input[type=text]::-ms-clear { + display: none; +} +.geEditor input, .geEditor select, +.geEditor textarea, .geEditor button { + font-size: inherit; +} +.geMenubarContainer, .geToolbarContainer, .geHsplit, +.geVsplit, .mxWindowTitle, .geSidebarContainer, +.geEditor .geTabItem { + background:var(--panel-color); +} +.geButtonContainer { + display: inline-flex; + align-items: center; + padding: 0 32px 0 0; + margin-left: auto; +} +.geEditor .geTabItem { + border-width:1px; + border-top-style:solid; +} +.geEditor .geTabItem { + cursor:default; + user-select:none; +} +.geEditor div.mxTooltip { + background: var(--panel-color); + font-size: 11px; + color: black; + padding:6px; +} +.geDragPreview { + border: 1px dashed black; +} +html > body > div > div.geToolbarContainer.geSimpleMainMenu, +html > body > div > div.geToolbarContainer.geSimpleMainMenu .geToolbarContainer{ + border-top: none !important; + border-left: none !important; + border-right: none !important; +} +html > body > div > div.geToolbarContainer.geSimpleMainMenu .geToolbarContainer{ + border:none !important; +} +.geMenubarContainer { + display:inline-flex; + align-items:center; +} +.geMenubarContainer .geItem, .geToolbar .geButton, .geToolbar .geLabel { + cursor:pointer !important; +} +.geSidebarContainer .geTitle { + cursor:default !important; +} +.geBackgroundPage { + box-shadow:0px 0px 2px 1px #d1d1d1; +} +.geSidebarContainer a, .geMenubarContainer a, .geToolbar a { + color:#000000; + text-decoration:none; +} +.geMenubarContainer, .geToolbarContainer, .geDiagramContainer, .geSidebarContainer, .geFooterContainer, .geHsplit, .geVsplit { + overflow:hidden; + position:absolute; + cursor:default; +} +.geFormatContainer { + overflow-x:hidden !important; + overflow-y:auto !important; + font-size:12px; + border-left:1px solid #dadce0; + transition:width 0.3s; +} +.geFormatContainer button:not(.geColorBtn), .geFormatContainer select { + border-radius:4px; + padding:2px; +} +.geSidebarFooter { + border-top:1px solid #dadce0; +} +.geFormatSection { + border-bottom:1px solid #dadce0; + border-color:#dadce0; +} +.geDiagramContainer { + background-color:#ffffff; + font-size:0px; + outline:none; +} +.geMenubar, .geToolbar { + white-space:nowrap; + display:block; + width:100%; +} +.geMenubarContainer .geItem, .geToolbar .geButton, .geToolbar .geLabel, +.geSidebar, .geSidebar .geItem, .mxPopupMenuItem { + -webkit-transition: all 0.1s ease-in-out; + -moz-transition: all 0.1s ease-in-out; + -o-transition: all 0.1s ease-in-out; + -ms-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; +} +.geHint { + background-color: #ffffff; + border: 1px solid gray; + padding: 4px 16px 4px 16px; + border-radius:3px; + -webkit-box-shadow: 1px 1px 2px 0px #ddd; + -moz-box-shadow: 1px 1px 2px 0px #ddd; + box-shadow: 1px 1px 2px 0px #ddd; + opacity:0.8; + font-size:9pt; +} +.geHint img { + opacity: 0.7; +} +.geHint img:hover +{ + opacity: 1; +} +.geUser { + color:var(--text-color); + display:inline-block; + cursor:pointer; + font-size:10px; +} +.geStatus > * { + overflow:hidden; + white-space:nowrap; + vertical-align:middle; + display:inline-block; + font-size:12px; + color:var(--text-color); +} +a.geStatus { + display:inline-flex; + align-items:center; + white-space:nowrap; + min-width:0; + height:100%; +} +.geStatus *[data-action] { + cursor:pointer; +} +.geStatus img { + max-width:16px; + vertical-align:bottom; +} +a.geStatus div + div { + margin-left:8px; +} +a.geStatus .geStatusBox { + border-style: solid; + border-width: 1px; + border-radius: 3px; + font-size: 10px; + padding: 3px; +} +a.geStatus .geStatusAlert { + padding:4px 8px; + background-color:#eacccc; + border:1px solid #dca4ad; + color:#b62623 !important; + border-radius:3px; +} +a.geStatus .geStatusAlertOrange { + padding:4px 8px; + background-color:rgb(242, 147, 30); + border:rgb(240, 135, 5); + color:#000000 !important; + border-radius:3px; +} +html body div.geSmallBanner { + background-color: #F2931E; + background-image: linear-gradient(#F2931E 0px,#F08707 100%); + border: 1px solid #F08707; + color: #000; +} +html body div.geSmallBanner:hover:not([disabled]) { + background-color: #ffb75e; + background-image: linear-gradient(#ffb75e 0px,#F2931E 100%); + border: 1px solid #F08707; + color: #000; +} +a.geStatus .geStatusMessage { + padding:4px 6px 4px 6px; + font-size:12px; + background: -webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%); + background: -o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%); + background: -webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc)); + background: linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%); + background-repeat: repeat-x; + border:1px solid #b2dba1; + border-radius:3px; + color:#3c763d !important; +} +.geAlert { + position:absolute; + white-space:nowrap; + padding:14px; + background-color:#f2dede; + border:1px solid #ebccd1; + color:#a94442; + border-radius:3px; + -webkit-box-shadow: 2px 2px 3px 0px #ddd; + -moz-box-shadow: 2px 2px 3px 0px #ddd; + box-shadow: 2px 2px 3px 0px #ddd; +} +.geEditor input, .geEditor button, .geEditor select, .geColorBtn { + border: 1px solid #d8d8d8; + border-radius: 4px; +} +.geEditor button, .geEditor select, .geColorBtn { + background:#eee; +} +.geEditor button:hover:not([disabled], .geBigButton, .geShareBtn), +.geEditor select:hover:not([disabled]), .geColorBtn:hover:not([disabled]) { + background:#e5e5e5; +} +.geColorDropper { + cursor:pointer; + opacity:0.7; +} +.geColorDropper:hover { + opacity:1; +} +.geBtn, .mxWindow .geBtn { + font-size: 13px; + font-weight: 500; + border-radius: 4px; + height: 30px; + margin: 0 0 0 8px; + min-width: 72px; + outline: 0; + padding: 0 8px; +} +.geBtn:hover:not([disabled]), .geBtn:focus { + -webkit-box-shadow: 0px 1px 1px rgba(0,0,0,0.1); + -moz-box-shadow: 0px 1px 1px rgba(0,0,0,0.1); + box-shadow: 0px 1px 1px rgba(0,0,0,0.1); + color: #111; +} +.geToolbarContainer { + border-width:1px; + border-color:lightgray; + border-style:none none solid none; + box-shadow:none; + z-index:1; +} +.geShapePicker { + position:absolute; + border-radius:10px; + border-style:solid; + border-width:1px; + padding:6px 0 8px 0; + text-align:center; + box-shadow:0px 0px 3px 1px #d1d1d1; +} +.geBtnStepper { + border-radius:3px; + border-style:solid; + border-width:1px; +} +.geBtnUp { + background-image: url(data:image/gif;base64,R0lGODlhCgAGAJECAGZmZtXV1f///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0QzM3ODJERjg4NUQxMUU0OTFEQ0E2MzRGQzcwNUY3NCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0QzM3ODJFMDg4NUQxMUU0OTFEQ0E2MzRGQzcwNUY3NCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjRDMzc4MkREODg1RDExRTQ5MURDQTYzNEZDNzA1Rjc0IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRDMzc4MkRFODg1RDExRTQ5MURDQTYzNEZDNzA1Rjc0Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEAQAAAgAsAAAAAAoABgAAAg6UjwiQBhGYglCKhXFLBQA7); + background-position: center center; + background-repeat: no-repeat; + border-bottom-style:solid; + border-width:1px; +} +.geBtnUp:active { + background-color: #4d90fe; + background-image: linear-gradient(#4d90fe 0px,#357ae8 100%); +} +.geBtnDown { + background-image: url(data:image/gif;base64,R0lGODlhCgAGAJECANXV1WZmZv///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0QzM3ODJEQjg4NUQxMUU0OTFEQ0E2MzRGQzcwNUY3NCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0QzM3ODJEQzg4NUQxMUU0OTFEQ0E2MzRGQzcwNUY3NCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjRDMzc4MkQ5ODg1RDExRTQ5MURDQTYzNEZDNzA1Rjc0IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjRDMzc4MkRBODg1RDExRTQ5MURDQTYzNEZDNzA1Rjc0Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEAQAAAgAsAAAAAAoABgAAAg6UjxLLewEiCAnOZBzeBQA7); + background-position: center center; + background-repeat: no-repeat; + border-style:solid; + border-width:1px; +} +.geBtnDown:active { + background-color: #4d90fe; + background-image: linear-gradient(#4d90fe 0px,#357ae8 100%); +} +html .geColorBtn { + padding: 0px; +} +html .geColorBtn:disabled { + opacity: 0.5; +} +html .gePrimaryBtn { + background-color: #4d90fe; + background-image: linear-gradient(#4d90fe 0px,#4787ed 100%); + border: 1px solid #3079ed; + color: #fff; +} +html .gePrimaryBtn:hover:not([disabled]), html .gePrimaryBtn:focus { + background-color: #357ae8; + background-image: linear-gradient(#4d90fe 0px,#357ae8 100%); + border: 1px solid #2f5bb7; + color: #fff; +} +.gePrimaryBtn:disabled { + opacity: .5; +} +button.geShareBtn { + background-color: #F2931E; + border-color: #F08705; + color:#000000; +} +.geAlertLink { + color:#843534; + font-weight:700; + text-decoration:none; +} +.geMenubar { + padding:0px 2px 0px 2px; + display:inline-flex; + align-items:center; +} +.geMenubarContainer .geItem, .geToolbar .geItem { + padding:6px 6px 6px 9px; + cursor:default; +} +.geItem:hover:not([disabled]), .geToolbarButton:hover:not([disabled]), +.geBtn:hover:not([disabled]) { + opacity: 1 !important; +} +.geItem:active:not([disabled]):not(.geStatus), .geBtn:active:not([disabled]), +.geStatus div[data-action]:active:not([disabled]), +html .geToolbarButton:active:not([disabled]) { + opacity: 0.7 !important; +} +.geBtn:disabled { + opacity: 0.5; +} +.geMenubarContainer .geItem:active { + background: #F8C382; +} +.geToolbarContainer .geButton:hover { + opacity:1; + background: #eee; + border-radius:2px; +} +.geToolbarContainer .geButton:active, .geToolbarContainer .geLabel:active { + background: #F8C382; +} +.geToolbarContainer .geLabel:hover { + background: #eee; + border-radius:2px; +} +.geActiveButton:hover { + opacity: 0.7; +} +.geActiveButton:active { + opacity: 0.3; +} +.geToolbarButton { + opacity: 0.6; +} +.geToolbarButton:active { + opacity: 0.2 !important; +} +.mxDisabled:hover { + background:inherit !important; +} +.geMenubar a.geStatus { + color:#888888; + padding:0px 0px 0px 10px; + display:inline-flex; + align-items:center; + cursor:default !important; +} +.geMenubar a.geStatus:hover { + background:transparent; +} +.geSidebarContainer .geToolbarContainer { + background:transparent; + border-bottom:none; +} +.geSidebarContainer button { + text-overflow:ellipsis; + overflow:hidden; +} +.geToolbar { + padding:5px 0px 0px 6px; + border-top:1px solid #dadce0; + -webkit-box-shadow: inset 0 1px 0 0 #fff; + -moz-box-shadow: inset 0 1px 0 0 #fff; + box-shadow: inset 0 1px 0 0 #fff; +} +.geToolbarContainer .geSeparator { + float:left; + width:1px; + height:20px; + background:#e5e5e5; + margin-left:6px; + margin-right:6px; + margin-top:4px; +} +.geToolbarContainer .geButton { + float:left; + width:20px; + height:20px; + padding:0px 2px 4px 2px; + margin:2px; + border:1px solid transparent; + cursor:pointer; + opacity:0.6; +} +div.mxWindow .geButton { + margin: -1px 2px 2px 2px; + padding: 1px 2px 2px 1px; +} +.geToolbarContainer .geLabel { + float:left; + margin:2px; + cursor:pointer; + padding:3px 5px 3px 5px; + border:1px solid transparent; +} +.geToolbarContainer .mxDisabled:hover { + border:1px solid transparent !important; + opacity:0.2 !important; +} +.geDiagramBackdrop { + background-color: #fbfbfb; +} +.geSidebarContainer { + position:absolute; + overflow-x:hidden; + overflow-y:auto; +} +.mxWindowPane .geSidebarContainer { + width:100%; + height:100%; +} +.geEditor > div > .geMenubarContainer { + border-bottom-style:solid; + border-bottom-width:1px; +} +.geTabContainer { + border-width:1px; + border-top-style:solid; + border-left-style:solid; + border-right-style:solid; + display:flex; + white-space:nowrap; + overflow:hidden; + position:absolute; + z-index:1; +} +.geTabScroller { + display:inline-block; + position:relative; + max-width:calc(100% - 90px); + white-space:nowrap; + overflow:hidden; + overflow-x:auto; + -ms-overflow-style: none; + scrollbar-width: none; + left:0px; +} +.geTabScroller::-webkit-scrollbar { + display: none; +} +.geToggleItem { + padding:4px; + border-radius:8px; +} +.geActiveItem { + background-color:var(--border-color); +} +html body div.geActivePage, .geRuler { + background:#fff; +} +.geInactivePage:hover, .geControlTab:hover { + opacity:0.5; +} +.geTabMenuButton { + width:14px; + height:14px; + margin-left:4px; + margin-right:-6px; + cursor:pointer; +} +.geInactivePage .geTabMenuButton { + display:none; +} +.geTabMenuButton { + display:inline-block; + opacity:1; +} +.geTabMenuButton:hover { + opacity:0.7; +} +.geTabContainer > :first-child { + border-left-style:none; +} +.geTabContainer > :first-child > :first-child { + border-left-style:none; +} +.geTab { + height: 100%; + border-left-width:1px; + border-left-style:solid; + text-overflow:ellipsis; + border-color:#dadce0; + color:rgb(112, 112, 112); + font-size:12px; + font-weight: 600; + display: inline-flex; + align-items: center; +} +.gePageTab { + padding: 0px 12px 0px 12px; +} +.geSidebar { + border-bottom:1px solid #e5e5e5; + padding:6px; + padding-left:10px; + padding-bottom:6px; + overflow:hidden; +} +.geSidebarContainer .geTitle { + display:block; + font-size:13px; + border-color: #e5e5e5; + border-bottom:1px solid #e5e5e5; + font-weight:500; + padding:8px 0px 8px 20px; + margin:0px; + cursor:default; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + line-height:1.4em; +} +.geSidebarContainer .geTitle:hover { + background: #eee; + border-radius:2px; +} +.geSidebarContainer .geTitle:active { + background-color:#F8C382; +} +.geSidebarContainer .geDropTarget { + border-radius:10px; + border:2px dotted #b0b0b0; + text-align:center; + padding:6px; + margin:6px; + color:#a0a0a0; + font-size:13px; +} +.geTitle img { + opacity:0.5; +} +.geTitle img:hover { + opacity:1; +} +.geTitle .geButton { + border:1px solid transparent; + padding:3px; + border-radius:2px; +} +.geTitle .geButton:hover { + border:1px solid gray; +} +.geSidebar .geItem { + display:inline-block; + background-repeat:no-repeat; + background-position:50% 50%; + border-radius: 8px; +} +.geSidebar .geItem, .geShapePicker .geItem { + transition: transform 100ms ease-out; +} +.geSidebar .geItem:hover { + background-color:#e0e0e0; +} +.geSidebar .geItem:active, .geShapePicker .geItem:active { + transform: scale(0.8,0.8); +} +.geItem { + vertical-align: top; + display: inline-block; +} +.geSidebarTooltip { + position:absolute; + background:#fbfbfb; + overflow:hidden; + box-shadow:0 2px 6px 2px rgba(60,64,67,.15); + border-radius:6px; +} +.geFooterContainer { + background:#e5e5e5; + border-top:1px solid #c0c0c0; +} +.geFooterContainer a { + display:inline-block; + box-sizing:border-box; + width:100%; + white-space:nowrap; + font-size:14px; + color:#235695; + font-weight:bold; + text-decoration:none; +} +.geFooterContainer table { + border-collapse:collapse; + margin:0 auto; +} +.geFooterContainer td { + border-left:1px solid #c0c0c0; + border-right:1px solid #c0c0c0; +} +.geToolbarButton { + border-color:#333333; +} +.geFooterContainer td:hover { + background-color: #b3b3b3; +} +.geHsplit { + cursor:col-resize; +} +.geVsplit { + font-size:1pt; + cursor:row-resize; +} +.geHsplit { + border-left:1px solid var(--border-color); + background-color:transparent; +} +.geVSplit { + border-top:1px solid var(--border-color); + border-bottom:1px solid var(--border-color); +} +.geHsplit:hover, .geVsplit:hover { + background-color:var(--border-color); + opacity:0.7; +} +.mxWindow { + background:var(--panel-color); +} +.geDialog { + position:absolute; + background:white; + line-height:1em; + overflow:hidden; + padding:30px; + border:1px solid #acacac; + -webkit-box-shadow:0px 0px 2px 2px #d5d5d5; + -moz-box-shadow:0px 0px 2px 2px #d5d5d5; + box-shadow:0px 0px 2px 2px #d5d5d5; + z-index: 2; +} +.geTransDialog { + position:absolute; + overflow:hidden; + z-index: 2; +} +.geDialogClose { + position:absolute; + width:9px; + height:9px; + opacity:0.5; + cursor:pointer; +} +.geDialogClose:hover { + opacity:1; +} +.geDialogTitle { + box-sizing:border-box; + white-space:nowrap; + background:rgb(229, 229, 229); + border-bottom:1px solid rgb(192, 192, 192); + font-size:15px; + font-weight:bold; + text-align:center; + color:rgb(35, 86, 149); +} +.geDialogFooter { + background:whiteSmoke; + white-space:nowrap; + text-align:right; + box-sizing:border-box; + border-top:1px solid #e5e5e5; + color:darkGray; +} +.geSprite { + background:url('data:image/png;base64,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') no-repeat; + width:21px; + height:21px; +} +.geEditor .geBaseButton { + padding:10px; + border-radius:6px; + border:1px solid #c0c0c0; + cursor:pointer; + background-color:#ececec; + background-image:linear-gradient(#ececec 0%, #fcfcfc 100%); +} +.geEditor .geBaseButton:hover { + background:#ececec; +} +.geEditor .geBigButton { + color:#ffffff; + border: none; + padding:4px 10px; + font-size:14px; + white-space: nowrap; + border-radius:3px; + background-color:#0052cc; + cursor:pointer; + transition: background-color 0.1s ease-out; + overflow:hidden; + text-overflow: ellipsis; +} +.geEditor .geBigButton:hover { + background-color:#0065ff; +} +.geEditor .geBigButton:active { + background-color:#0747a6; + opacity:1; +} +html body .geBigStandardButton { + color: #344563; + background-color: rgba(9, 30, 66, 0.08); +} +html body .geBigStandardButton:hover { + background-color: rgba(9, 30, 66, 0.13); +} +html body .geBigStandardButton:active { + background-color: #F8C382; + color: #600000; +} +@media print { + div.geNoPrint { display: none !important; } +} +.geSprite-actualsize { background-position: 0 0; } +.geSprite-bold { background-position: 0 -46px; } +.geSprite-bottom { background-position: 0 -92px; } +.geSprite-center { background-position: 0 -138px; } +.geSprite-delete { background-position: 0 -184px; } +.geSprite-fillcolor { background-position: 0 -229px; } +.geSprite-fit { background-position: 0 -277px; } +.geSprite-fontcolor { background-position: 0 -322px; } +.geSprite-gradientcolor { background-position: 0 -368px; } +.geSprite-image { background-position: 0 -414px; } +.geSprite-italic { background-position: 0 -460px; } +.geSprite-left { background-position: 0 -505px; } +.geSprite-middle { background-position: 0 -552px; } +.geSprite-print { background-position: 0 -598px; } +.geSprite-redo { background-position: 0 -644px; } +.geSprite-right { background-position: 0 -689px; } +.geSprite-shadow { background-position: 0 -735px; } +.geSprite-strokecolor { background-position: 0 -782px; } +.geSprite-top { background-position: 0 -828px; } +.geSprite-underline { background-position: 0 -874px; } +.geSprite-undo { background-position: 0 -920px; } +.geSprite-zoomin { background-position: 0 -966px; } +.geSprite-zoomout { background-position: 0 -1012px; } +.geSprite-arrow { background-position: 0 -1059px; } +.geSprite-linkedge { background-position: 0 -1105px; } +.geSprite-straight { background-position: 0 -1150px; } +.geSprite-entity { background-position: 0 -1196px; } +.geSprite-orthogonal { background-position: 0 -1242px; } +.geSprite-curved { background-position: 0 -1288px; } +.geSprite-noarrow { background-position: 0 -1334px; } +.geSprite-endclassic { background-position: 0 -1380px; } +.geSprite-endopen { background-position: 0 -1426px; } +.geSprite-endblock { background-position: 0 -1472px; } +.geSprite-endoval { background-position: 0 -1518px; } +.geSprite-enddiamond { background-position: 0 -1564px; } +.geSprite-endthindiamond { background-position: 0 -1610px; } +.geSprite-endclassictrans { background-position: 0 -1656px; } +.geSprite-endblocktrans { background-position: 0 -1702px; } +.geSprite-endovaltrans { background-position: 0 -1748px; } +.geSprite-enddiamondtrans { background-position: 0 -1794px; } +.geSprite-endthindiamondtrans { background-position: 0 -1840px; } +.geSprite-startclassic { background-position: 0 -1886px; } +.geSprite-startopen { background-position: 0 -1932px; } +.geSprite-startblock { background-position: 0 -1978px; } +.geSprite-startoval { background-position: 0 -2024px; } +.geSprite-startdiamond { background-position: 0 -2070px; } +.geSprite-startthindiamond { background-position: 0 -2116px; } +.geSprite-startclassictrans { background-position: 0 -2162px; } +.geSprite-startblocktrans { background-position: 0 -2208px; } +.geSprite-startovaltrans { background-position: 0 -2254px; } +.geSprite-startdiamondtrans { background-position: 0 -2300px; } +.geSprite-startthindiamondtrans { background-position: 0 -2346px; } +.geSprite-globe { background-position: 0 -2392px; } +.geSprite-orderedlist { background-position: 0 -2438px; } +.geSprite-unorderedlist { background-position: 0 -2484px; } +.geSprite-horizontalrule { background-position: 0 -2530px; } +.geSprite-link { background-position: 0 -2576px; } +.geSprite-indent { background-position: 0 -2622px; } +.geSprite-outdent { background-position: 0 -2668px; } +.geSprite-code { background-position: 0 -2714px; } +.geSprite-fontbackground { background-position: 0 -2760px; } +.geSprite-removeformat { background-position: 0 -2806px; } +.geSprite-superscript { background-position: 0 -2852px; } +.geSprite-subscript { background-position: 0 -2898px; } +.geSprite-table { background-position: 0 -2944px; } +.geSprite-deletecolumn { background-position: 0 -2990px; } +.geSprite-deleterow { background-position: 0 -3036px; } +.geSprite-insertcolumnafter { background-position: 0 -3082px; } +.geSprite-insertcolumnbefore { background-position: 0 -3128px; } +.geSprite-insertrowafter { background-position: 0 -3174px; } +.geSprite-insertrowbefore { background-position: 0 -3220px; } +.geSprite-grid { background-position: 0 -3272px; } +.geSprite-guides { background-position: 0 -3324px; } +.geSprite-dots { background-position: 0 -3370px; } +.geSprite-alignleft { background-position: 0 -3416px; } +.geSprite-alignright { background-position: 0 -3462px; } +.geSprite-aligncenter { background-position: 0 -3508px; } +.geSprite-aligntop { background-position: 0 -3554px; } +.geSprite-alignbottom { background-position: 0 -3600px; } +.geSprite-alignmiddle { background-position: 0 -3646px; } +.geSprite-justifyfull { background-position: 0 -3692px; } +.geSprite-formatpanel { background-position: 0 -3738px; } +.geSprite-connection { background-position: 0 -3784px; } +.geSprite-vertical { background-position: 0 -3830px; } +.geSprite-simplearrow { background-position: 0 -3876px; } +.geSprite-plus { background-position: 0 -3922px; } +.geSprite-rounded { background-position: 0 -3968px; } +.geSprite-toback { background-position: 0 -4014px; } +.geSprite-tofront { background-position: 0 -4060px; } +.geSprite-duplicate { background-position: 0 -4106px; } +.geSprite-insert { background-position: 0 -4152px; } +.geSprite-endblockthin { background-position: 0 -4201px; } +.geSprite-endblockthintrans { background-position: 0 -4247px; } +.geSprite-enderone { background-position: 0 -4293px; } +.geSprite-enderonetoone { background-position: 0 -4339px; } +.geSprite-enderonetomany { background-position: 0 -4385px; } +.geSprite-endermany { background-position: 0 -4431px; } +.geSprite-enderoneopt { background-position: 0 -4477px; } +.geSprite-endermanyopt { background-position: 0 -4523px; } +.geSprite-endclassicthin { background-position: 0 -4938px; } +.geSprite-endclassicthintrans { background-position: 0 -4984px; } +.geSprite-enddash { background-position: 0 -5029px; } +.geSprite-endcircleplus { background-position: 0 -5075px; } +.geSprite-endcircle { background-position: 0 -5121px; } +.geSprite-endasync { background-position: 0 -5167px; } +.geSprite-endasynctrans { background-position: 0 -5213px; } +.geSprite-startblockthin { background-position: 0 -4569px; } +.geSprite-startblockthintrans { background-position: 0 -4615px; } +.geSprite-starterone { background-position: 0 -4661px; } +.geSprite-starteronetoone { background-position: 0 -4707px; } +.geSprite-starteronetomany { background-position: 0 -4753px; } +.geSprite-startermany { background-position: 0 -4799px; } +.geSprite-starteroneopt { background-position: 0 -4845px; } +.geSprite-startermanyopt { background-position: 0 -4891px; } +.geSprite-startclassicthin { background-position: 0 -5259px; } +.geSprite-startclassicthintrans { background-position: 0 -5305px; } +.geSprite-startdash { background-position: 0 -5351px; } +.geSprite-startcircleplus { background-position: 0 -5397px; } +.geSprite-startcircle { background-position: 0 -5443px; } +.geSprite-startasync { background-position: 0 -5489px; } +.geSprite-startasynctrans { background-position: 0 -5535px; } +.geSprite-startcross { background-position: 0 -5581px; } +.geSprite-startopenthin { background-position: 0 -5627px; } +.geSprite-startopenasync { background-position: 0 -5673px; } +.geSprite-endcross { background-position: 0 -5719px; } +.geSprite-endopenthin { background-position: 0 -5765px; } +.geSprite-endopenasync { background-position: 0 -5811px; } +.geSprite-verticalelbow { background-position: 0 -5857px; } +.geSprite-horizontalelbow { background-position: 0 -5903px; } +.geSprite-horizontalisometric { background-position: 0 -5949px; } +.geSprite-verticalisometric { background-position: 0 -5995px; } +.geSvgSprite { + background-position: center center; +} +.geFlipSprite { + transform:scaleX(-1); +} +.geSprite-box { + background-image: url("data:image/svg+xml;utf8,"); +} +.geSprite-halfCircle { + background-image: url("data:image/svg+xml;utf8,"); +} +html div.mxRubberband { + border-color:#0000DD; + background:#99ccff; +} +td.mxPopupMenuIcon div { + width:16px; + height:16px; +} +.geEditor div.mxPopupMenu { + box-shadow: 0px 0px 2px #C0C0C0; + background:var(--panel-color); + border-radius:4px; + border-style:solid; + border-width:1px; + border-color:lightgray; + padding:3px; +} +.geSearchSidebar { + padding: 14px 8px 0px 8px; + box-sizing: border-box; + min-height: 60px; + overflow: hidden; + width: 100%; +} +.geSearchSidebar input { + font-size: 12px; + box-sizing: border-box; + border: 1px solid #d5d5d5; + border-radius: 4px; + width: 100%; + outline: none; + padding: 6px 20px 6px 6px +} +html table.mxPopupMenu { + border-collapse:collapse; + margin:0px; +} +html td.mxPopupMenuItem { + padding:7px 30px 7px 30px; + font-family:-apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", system-ui, ui-sans-serif, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size:10pt; +} +html td.mxPopupMenuIcon { + background-color:transparent; + padding:0px; +} +td.mxPopupMenuIcon .geIcon { + padding:2px; + padding-bottom:4px; + margin:2px; + border:1px solid transparent; + opacity:0.5; +} +html tr.mxPopupMenuItemHover { + background-color: #e0e0e0; + color: black; +} +table.mxPopupMenu hr { + color:#cccccc; + background-color:#cccccc; + border:none; + height:1px; +} +table.mxPopupMenu tr { + font-size:4pt; +} +html td.mxWindowTitle { + color:rgb(112, 112, 112); + background:#f1f3f4; + padding:4px; +} +table.geProperties { + table-layout:fixed; +} +table.geProperties tr td { + height:21px; +} +.gePropHeader, .gePropRow { + border: 1px solid #e9e9e9; +} +.gePropRowDark { + border: 1px solid #4472C4; +} +.gePropHeader>.gePropHeaderCell { + border-top: 0; + border-bottom: 0; + text-align: left; + width: 50%; +} +.gePropHeader>.gePropHeaderCell:first-child { + border-left: none; +} +.gePropHeader>.gePropHeaderCell:last-child { + border-right: none; +} +.gePropHeader { + background: #e5e5e5; + color: black; +} +.gePropRowCell { + border-left: 1px solid #f3f3f3; + white-space:nowrap; + text-overflow:ellipsis; + overflow:hidden; + max-width: 50%; +} +.gePropRow>.gePropRowCell { + background: #fff; +} +.gePropRowAlt>.gePropRowCell { + background: #fcfcfc; +} +.gePropRowDark>.gePropRowCell { + background: #fff; + color: #305496; + font-weight: bold; +} +.gePropRowDarkAlt>.gePropRowCell { + background: #D9E1F2; + color: #305496; + font-weight: bold; +} +.gePropEditor input:invalid { + border: 1px solid red; +} +/* Templates dialog css */ +.geTemplateDlg { + width: 100%; + height: 100%; +} +.geTemplateDlg ::-webkit-scrollbar { + width:12px; + height:12px; +} +.geTemplateDlg ::-webkit-scrollbar-track { + background:whiteSmoke; + box-shadow:inset 0 0 4px rgba(0,0,0,0.1); +} +.geTemplateDlg ::-webkit-scrollbar-thumb { + background:#c5c5c5; + border-radius:10px; + border:whiteSmoke solid 3px; +} +.geTemplateDlg ::-webkit-scrollbar-thumb:hover { + background:#b5b5b5; +} + +.geTempDlgHeader { + box-sizing: border-box; + height: 62px; + width: 100%; + border: 1px solid #CCCCCC; + border-radius: 5px 5px 0 0; + background-color: #F5F5F5; +} +.geTempDlgHeaderLogo { + height: 34px; + margin: 14px 14px 14px 20px; +} +.geTempDlgSearchBox { + color:#888888; + background:url("/images/icon-search.svg") no-repeat; + background-color: #FFFFFF; + background-position: 15px; + height: 40px; + width: 40%; + max-width: 400px; + border: 1px solid #CCCCCC; + border-radius: 3px; + float:right; + font-family:Arial,Helvetica,sans-serif; + font-size:15px; + line-height:36px; + margin: 11px 36px 0 0; + outline:medium none; + padding:0 0 0 36px; + text-shadow:1px 1px 0 white; +} +.geTemplatesList { + box-sizing: border-box; + float: left; + height: calc(100% - 118px); + width: 20%; + border: 1px solid #CCCCCC; + background-color: #FFFFFF; + display: inline-block; + overflow-x: hidden; + overflow-y: auto; +} +.geTempDlgContent { + box-sizing: border-box; + float: right; + height: calc(100% - 118px); + width: 80%; + border: 1px solid #CCCCCC; + background-color: #FFFFFF; + display: inline-block; + overflow-x: hidden; + overflow-y: auto; + position: relative; +} +.geTempDlgFooter { + box-sizing: border-box; + height: 52px; + width: 100%; + border: 1px solid #CCCCCC; + border-radius: 0 0 5px 5px; + background-color: #F5F5F5; + text-align: right; + font-family: Helvetica; + font-size: 14px; + line-height: 17px; + padding-top: 11px; +} +.geTempDlgCreateBtn, .geTempDlgOpenBtn { + display: inline-block; + width: 67px; + border-radius: 3px; + background-color: #3D72AD; + padding: 6px; + text-align: center; + color: #fff; + cursor: pointer; + margin-left: 5px; +} +.geTempDlgCancelBtn { + display: inline-block; + width: 67px; + padding: 6px; + text-align: center; + color: #3D72AD; + cursor: pointer; +} +.geTempDlgCancelBtn:active, .geTempDlgCreateBtn:active, +.geTempDlgOpenBtn:active, .geTempDlgShowAllBtn:active { + transform: translateY(2px); +} +.geTempDlgBtnDisabled { + background-color: #9fbddd; +} +.geTempDlgBtnDisabled:active { + transform: translateY(0px); +} + +.geTempDlgBtnBusy { + background-image: url(/images/aui-wait.gif); + background-repeat: no-repeat; + background-position: 62px 7px; +} + +.geTempDlgBack { + height: 17px; + color: #333333; + font-family: Helvetica; + font-size: 14px; + font-weight: bold; + line-height: 17px; + padding: 25px 0 0 20px; + cursor: pointer; +} +.geTempDlgHLine { + height: 1px; + width: calc(100% - 22px); + background-color: #CCCCCC; + margin: 20px 0 0 11px; +} +.geTemplatesLbl { + height: 17px; + color: #6D6D6D; + font-family: Helvetica; + font-size: 14px; + font-weight: bold; + line-height: 17px; + text-transform: uppercase; + margin: 20px 0 3px 20px; +} +.geTemplateCatLink { + height: 17px; + color: #3D72AD; + font-family: Helvetica; + font-size: 14px; + line-height: 17px; + margin: 12px 0 0 20px; + cursor: pointer; +} +.geTempDlgNewDiagramCat { + height: 280px; + width: 100%; + background-color: #555555; +} +.geTempDlgNewDiagramCatLbl { + height: 17px; + color: #FFFFFF; + font-family: Helvetica; + font-size: 14px; + font-weight: bold; + line-height: 17px; + padding: 25px 0 0 20px; + text-transform: uppercase; +} +.geTempDlgNewDiagramCatList { + width: 100%; + height: 190px; + padding-left: 9px; + box-sizing: border-box; + overflow-y: auto; + overflow-x: hidden; +} +.geTempDlgNewDiagramCatFooter { + width: 100%; +} +.geTempDlgShowAllBtn { + width: 78px; + border: 1px solid #777777; + border-radius: 3px; + cursor: pointer; + text-align: center; + color: #DDDDDD; + font-family: Helvetica; + font-size: 14px; + line-height: 17px; + padding: 4px; + float: right; + margin-right: 30px; +} +.geTempDlgNewDiagramCatItem { + height: 155px; + width: 134px; + padding: 18px 6px 0 9px; + display: inline-block; +} + +.geTempDlgNewDiagramCatItemImg { + box-sizing: border-box; + height: 134px; + width: 134px; + border: 1px solid #CCCCCC; + border-radius: 3px; + background-color: #FFFFFF; + display:table-cell; + vertical-align:middle; + text-align:center; + cursor: pointer; +} + +.geTempDlgNewDiagramCatItemActive > .geTempDlgNewDiagramCatItemImg { + border: 4px solid #3D72AD; +} + +.geTempDlgNewDiagramCatItemLbl { + height: 17px; + width: 100%; + color: #FFFFFF; + font-family: Helvetica; + font-size: 14px; + line-height: 17px; + text-align: center; + padding-top: 4px; + cursor: pointer; +} + +.geTempDlgDiagramsList { + box-sizing: border-box; + width: 100%; + min-height: calc(100% - 280px); + padding-left: 9px; + box-sizing: border-box; + background-color: #E5E5E5; +} + +.geTempDlgDiagramsListHeader { + width: 100%; + height: 45px; + padding: 18px 20px 0 11px; + box-sizing: border-box; +} +.geTempDlgDiagramsListTitle { + box-sizing: border-box; + height: 17px; + color: #666666; + font-family: Helvetica; + font-size: 14px; + font-weight: bold; + line-height: 17px; + text-transform: uppercase; + padding-top: 5px; + display: inline-block; +} +.geTempDlgDiagramsListBtns { + float: right; + margin-top: -9px; +} +.geTempDlgRadioBtn { + box-sizing: border-box; + border: 1px solid #CCCCCC; + border-radius: 3px; + background-color: #FFFFFF; + color: #333333; + display: inline-block; + font-family: Helvetica; + font-size: 14px; + line-height: 17px; + text-align: center; + padding: 4px; + cursor: pointer; +} +.geTempDlgRadioBtnActive { + background-color: #555555; + color: #FFFFFF; +} +.geTempDlgRadioBtnLarge { + height: 27px; + width: 120px; +} +/* TODO is there a better way for these buttons */ +.geTempDlgRadioBtnSmall { + position: relative; + top: 9px; + height: 27px; + width: 27px; +} +.geTempDlgRadioBtnSmall img { + position: absolute; + top: 6px; + left: 6px; + height: 13px; + width: 13px; +} +.geTempDlgSpacer { + display: inline-block; + width: 10px; +} + +.geTempDlgDiagramsListGrid { + width: 100%; + white-space: nowrap; + font-size: 13px; + padding: 0px 20px 20px 10px; + box-sizing: border-box; + border-spacing: 0; +} + +.geTempDlgDiagramsListGrid tr { + height: 40px; +} + +.geTempDlgDiagramsListGrid th { + background-color: #E5E5E5; + color: #8E8E8E; + font-weight: bold; + text-align: left; + padding: 5px; + border-bottom: 1px solid #CCCCCC; + font-size: 14px; +} + +.geTempDlgDiagramsListGrid td { + background-color: #FFFFFF; + color: #888888; + padding: 5px; + border-bottom: 1px solid #CCCCCC; + overflow: hidden; +} + +.geTempDlgDiagramsListGridActive td { + border-bottom: 2px solid #3D72AD; + border-top: 2px solid #3D72AD; +} + +.geTempDlgDiagramsListGridActive td:first-child { + border-left: 2px solid #3D72AD; +} + +.geTempDlgDiagramsListGridActive td:last-child { + border-right: 2px solid #3D72AD; +} + +.geTempDlgDiagramTitle { + font-weight: bold; + color: #666666 !important; +} + +.geTempDlgDiagramsTiles { + position: relative; + min-height: 100px; +} + +.geTempDlgDiagramTile { + height: 152px; + width: 130px; + padding: 20px 7px 0 10px; + display: inline-block; + position: relative; +} + +.geTempDlgDiagramTileImg { + box-sizing: border-box; + height: 130px; + width: 130px; + border: 1px solid #CCCCCC; + border-radius: 3px; + background-color: #FFFFFF; + display:table-cell; + vertical-align:middle; + text-align:center; +} + +.geTempDlgDiagramTileImgLoading { + background-image: url(/images/aui-wait.gif); + background-repeat: no-repeat; + background-position: center; +} + +.geTempDlgDiagramTileImgError { + background-image: url(/images/broken.png); + background-repeat: no-repeat; + background-position: center; + background-color: #be3730; +} + +.geTempDlgDiagramTileImg img{ + max-width: 117px; + max-height: 117px; + cursor: pointer; +} + +.geTempDlgDiagramTileActive > .geTempDlgDiagramTileImg{ + border: 4px solid #3D72AD; +} + +.geTempDlgDiagramTileLbl { + height: 17px; + width: 100%; + color: #333333; + font-family: Helvetica; + font-size: 14px; + line-height: 17px; + text-align: center; + padding-top: 5px; + cursor: pointer; +} + +.geTempDlgDiagramPreviewBtn { + position: absolute; + top: 28px; + right: 15px; + cursor: pointer; +} + +.geTempDlgDiagramListPreviewBtn { + cursor: pointer; + padding-left: 5px; + padding-right: 15px; +} + +.geTempDlgDiagramPreviewBox { + position: absolute; + top: 3%; + left: 10%; + width: 80%; + height: 94%; + background: white; + border: 4px solid #3D72AD; + border-radius: 6px; + box-sizing: border-box; + display:table-cell; + vertical-align:middle; + text-align:center; + z-index: 2; +} + +.geTempDlgDialogMask { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; +} + +.geTempDlgDiagramPreviewBox img { + max-width: 95%; + max-height: 95%; + vertical-align: middle; +} + +.geTempDlgPreviewCloseBtn { + position: absolute; + top: 5px; + right: 5px; + cursor: pointer; +} + +.geTempDlgLinkToDiagramHint { + color: #555; +} + +.geTempDlgLinkToDiagramBtn { + color: #555; + margin: 0 10px 0 10px; + height: 27px; + font-size: 14px; +} + +.geTempDlgErrMsg { + display: none; + color: red; + position: absolute; + width: 100%; + text-align: center; +} + +.geTempDlgImportCat { + font-weight: bold; + background: #f9f9f9; + padding: 5px 0px; + padding: 10px; + margin: 10px 10px 0px 0px; +} +/* Comments CSS */ +.geCommentsWin { + user-select: none; + border: 1px solid whiteSmoke; + height: 100%; + margin-bottom: 10px; + overflow: auto; +} + +.geCommentsToolbar { + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; + overflow: hidden; + border-width: 1px 0px 0px 0px; + border-color: #c3c3c3; + border-style: solid; + display: block; + white-space: nowrap; +} + +.geCommentsList { + position: absolute; + overflow: auto; + left: 0px; + right: 0px; + top: 0px; +} + +.geCommentContainer { + position: relative; + padding: 12px; + margin: 5px; + min-height: 50px; + display: block; + background-color: white; + border-width: 0px 0px 1px 0px; + border-color: #c3c3c3; + border-style: solid; + border-radius: 10px; + white-space: nowrap; + box-shadow: 2px 2px 6px rgba(60,64,67,.15); + color: #3C4043; +} + +.geCommentHeader { + width: 100%; + height: 32px; +} + +.geCommentUserImg { + width: 32px; + height: 32px; + border-radius: 50%; + float: left; + background-color: whitesmoke; +} + +.geCommentHeaderTxt { + overflow: hidden; + height: 32px; + padding-left: 5px; +} + +.geCommentUsername { + overflow: hidden; + height: 18px; + font-size: 15px; + font-weight: bold; + text-overflow: ellipsis; +} + +.geCommentDate { + color: #707070; + overflow: hidden; + height: 14px; + font-size: 11px; + text-overflow: ellipsis; +} + +.geCommentDate::first-letter { + text-transform: uppercase; +} + +.geCommentTxt { + font-size: 14px; + padding-top: 5px; + white-space: normal; + min-height: 12px; +} + +.geCommentEditTxtArea { + margin-top: 5px; + font-size: 14px !important; + min-height: 12px; + max-width: 100%; + min-width: 100%; + width: 100%; + box-sizing: border-box; +} + +.geCommentEditBtns { + width: 100%; + box-sizing: border-box; + padding-top: 5px; + height: 20px; +} + +.geCommentEditBtn { + padding: 3px 8px 3px 8px !important; + float: right !important; + margin-left: 5px; +} + +.geCommentActions { + color: #707070; + font-size: 12px; +} + +.geCommentActionsList { + list-style-type: disc; + margin: 0px; + padding: 10px 0 0 0; +} + +.geCommentAction { + display: inline-block; + padding: 0; +} + +.geCommentAction:before { + content: "\2022"; + padding: 5px; +} + +.geCommentAction:first-child:before { + content: ""; + padding: 0; +} + +.geCommentActionLnk { + cursor: pointer; + color: #707070; + text-decoration: none; +} + +.geCommentActionLnk:hover { + text-decoration: underline; +} + +.geCheckedBtn { + background-color: #ccc; + border-top: 1px solid black !important; + border-left: 1px solid black !important; +} + +.geCommentBusyImg { + position: absolute; + top: 5px; + right: 5px; +} + +.geAspectDlgListItem +{ + width : 120px; + height : 120px; + display : inline-block; + border: 3px solid #F0F0F0; + border-radius: 5px; + padding: 5px; + margin : 2px 2px 20px 2px; +} + +.geAspectDlgListItem:hover +{ + border: 3px solid #c5c5c5; +} + +.geAspectDlgListItemSelected +{ + border: 3px solid #3b73af; +} + +.geAspectDlgListItemSelected:hover +{ + border: 3px solid #405a86; +} + +.geAspectDlgListItemText +{ + text-overflow: ellipsis; + max-width: 100%; + min-height : 2em; + overflow : hidden; + text-align : center; + margin-top : 10px; +} + +.geAspectDlgList +{ + min-height: 184px; + white-space: nowrap; +} + +.geStripedTable +{ + border-collapse: collapse; + width: 100%; + table-layout: fixed; +} + +.geStripedTable td, .geStripedTable th +{ + border: 1px solid #ddd; + text-align: left; + padding: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.geStripedTable tr:nth-child(odd){background-color: #f2f2f2;} + +.geStripedTable tr:hover {background-color: #ddd;} + +.geStripedTable th { + padding-top: 4px; + padding-bottom: 4px; + background-color: #bbb; +} + +.geNotification-box { + display:flex; + text-align: center; + position: relative; + cursor: pointer; + width: 20px; +} +.geNotification-bell { + animation: geBellAnim 1s 1s both; +} +.geNotification-bell * { + display: block; + margin: 0 auto; + background-color: #656565; +} + +.geBell-top { + width: 2px; + height: 2px; + border-radius: 1px 1px 0 0; +} +.geBell-middle { + width: 12px; + height: 12px; + margin-top: -1px; + border-radius: 7px 7px 0 0; +} +.geBell-bottom { + position: relative; + z-index: 0; + width: 16px; + height: 1px; +} +.geBell-bottom::before, +.geBell-bottom::after { + content: ''; + position: absolute; + top: -4px; +} +.geBell-bottom::before { + left: 1px; + border-bottom-width: 4px; + border-right: 0 solid transparent; + border-left: 4px solid transparent; +} +.geBell-bottom::after { + right: 1px; + border-bottom-width: 4px; + border-right: 4px solid transparent; + border-left: 0 solid transparent; +} +.geBell-rad { + width: 3px; + height: 2px; + margin-top: 0.5px; + border-radius: 0 0 2px 2px; + animation: geRadAnim 1s 2s both; +} +.geNotification-count { + position: absolute; + z-index: 1; + top: -5px; + right: -4px; + width: 13px; + height: 13px; + line-height: 13px; + font-size: 8px; + border-radius: 50%; + background-color: #ff4927; + color: #FFF; + animation: geZoomAnim 1s 1s both; +} +@keyframes geBellAnim { + 0% { transform: rotate(0); } + 10% { transform: rotate(30deg); } + 20% { transform: rotate(0); } + 80% { transform: rotate(0); } + 90% { transform: rotate(-30deg); } + 100% { transform: rotate(0); } +} +@keyframes geRadAnim { + 0% { transform: translateX(0); } + 10% { transform: translateX(5px); } + 20% { transform: translateX(0); } + 80% { transform: translateX(0); } + 90% { transform: translateX(-5px); } + 100% { transform: translateX(0); } +} +@keyframes geZoomAnim { + 0% { opacity: 0; transform: scale(0); } + 50% { opacity: 1; transform: scale(1); } + 100% { opacity: 1; } +} + +.geNotifPanel { + height: 300px; + width: 300px; + background: #fff; + border-radius: 3px; + overflow: hidden; + -webkit-box-shadow: 10px 10px 15px 0 rgba(0, 0, 0, 0.3); + box-shadow: 10px 10px 15px 0 rgba(0, 0, 0, 0.3); + -webkit-transition: all .5s ease-in-out; + transition: all .5s ease-in-out; + position: absolute; + right: 100px; + top: 42px; + z-index: 150; +} +.geNotifPanel .header { + background: #cecece; + color: #707070; + font-size: 15px; +} +.geNotifPanel .header .title { + display: block; + text-align: center; + line-height: 30px; + font-weight: 600; +} +.geNotifPanel .header .closeBtn { + position: absolute; + line-height: 30px; + cursor: pointer; + right: 15px; + top: 0; +} +.geNotifPanel .notifications { + position: relative; + height: 270px; + overflow-x: hidden; + overflow-y: auto; +} +.geNotifPanel .notifications .line { + position: absolute; + top: 0; + left: 27px; + height: 100%; + width: 3px; + background: #EBEBEB; +} +.geNotifPanel .notifications .notification { + position: relative; + z-index: 2; + margin: 25px 20px 25px 43px; +} +.geNotifPanel .notifications .notification:nth-child(n+1) { + animation: geHere-am-i 0.5s ease-out 0.4s; + animation-fill-mode: both; +} +.geNotifPanel .notifications .notification:hover { + color: #1B95E0; + cursor: pointer; +} +.geNotifPanel .notifications .notification .circle { + -webkit-box-sizing: border-box; + box-sizing: border-box; + position: absolute; + height: 11px; + width: 11px; + background: #fff; + border: 2px solid #1B95E0; + -webkit-box-shadow: 0 0 0 3px #fff; + box-shadow: 0 0 0 3px #fff; + border-radius: 6px; + top: 0; + left: -20px; +} + +.geNotifPanel .notifications .notification .circle.active { + background: #1B95E0; +} + +.geNotifPanel .notifications .notification .time { + display: block; + font-size: 11px; + line-height: 11px; + margin-bottom: 2px; +} +.geNotifPanel .notifications .notification p { + font-size: 15px; + line-height: 20px; + margin: 0; +} +.geNotifPanel .notifications .notification p b { + font-weight: 600; +} +@-webkit-keyframes geHere-am-i { + from { + -webkit-transform: translate3d(0, 50px, 0); + transform: translate3d(0, 50px, 0); + opacity: 0; + } + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes geHere-am-i { + from { + -webkit-transform: translate3d(0, 50px, 0); + transform: translate3d(0, 50px, 0); + opacity: 0; + } + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +.geTempTree { + margin: 0; + padding: 0; +} + +.geTempTree, .geTempTreeActive, .geTempTreeNested { + list-style-type: none; + transition: all 0.5s; +} + +.geTempTreeCaret { + box-sizing: border-box; + cursor: pointer; + user-select: none; + padding: 6px; + width: 100%; + transition: all 0.5s; +} + +.geTempTreeCaret::before { + content: "\25B6"; + display: inline-block; + font-size: 10px; + margin-right: 6px; +} + +.geTempTreeCaret-down::before { + transform: rotate(90deg); +} + +.geTempTreeNested { + height: 0; + opacity: 0; +} + +.geTempTreeActive { + height: 100%; + opacity: 1; +} + +.geTempTreeActive, .geTempTreeNested { + padding-left: 15px; +} + +.geTempTreeActive > li, .geTempTreeNested > li { + box-sizing: border-box; + padding: 3px; + width: 100%; + cursor: pointer; + user-select: none; + transition: all 0.5s; +} + +/*Electron Window Controls*/ +#geWindow-controls { + display: grid; + grid-template-columns: repeat(3, 30px); + position: absolute; + top: 2px; + right: 3px; + height: 22px; + -webkit-app-region: no-drag; +} + +#geWindow-controls .button { + grid-row: 1 / span 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + user-select: none; +} +#min-button { + grid-column: 1; +} +#max-button, #restore-button { + grid-column: 2; +} +#close-button { + grid-column: 3; +} +#geWindow-controls .button.dark:hover { + background: rgba(255,255,255,0.1); +} +#geWindow-controls .button.dark:active { + background: rgba(255,255,255,0.2); +} + +#geWindow-controls .button.white:hover { + background: rgba(0,0,0,0.1); +} +#geWindow-controls .button.white:active { + background: rgba(0,0,0,0.2); +} + +#close-button:hover { + background: #E81123 !important; +} +#close-button:active { + background: #F1707A !important; +} + +#restore-button { + display: none !important; +} +/* +.geMaximized #titlebar { + width: 100%; + padding: 0; +} +*/ +.geMaximized #restore-button { + display: flex !important; +} + +.geMaximized #max-button { + display: none; +} +[draggable="true"] { + transform: translate(0, 0); + z-index: 0; +} diff --git a/oaweb/public/cherry/drawio/image/checkmark.gif b/oaweb/public/cherry/drawio/image/checkmark.gif new file mode 100644 index 0000000..b757a42 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/checkmark.gif differ diff --git a/oaweb/public/cherry/drawio/image/clear.gif b/oaweb/public/cherry/drawio/image/clear.gif new file mode 100644 index 0000000..e137972 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/clear.gif differ diff --git a/oaweb/public/cherry/drawio/image/close.png b/oaweb/public/cherry/drawio/image/close.png new file mode 100644 index 0000000..d319efb Binary files /dev/null and b/oaweb/public/cherry/drawio/image/close.png differ diff --git a/oaweb/public/cherry/drawio/image/collapsed.gif b/oaweb/public/cherry/drawio/image/collapsed.gif new file mode 100644 index 0000000..8442c35 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/collapsed.gif differ diff --git a/oaweb/public/cherry/drawio/image/dropdown.gif b/oaweb/public/cherry/drawio/image/dropdown.gif new file mode 100644 index 0000000..ea03c29 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/dropdown.gif differ diff --git a/oaweb/public/cherry/drawio/image/dropdown.png b/oaweb/public/cherry/drawio/image/dropdown.png new file mode 100644 index 0000000..53668d8 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/dropdown.png differ diff --git a/oaweb/public/cherry/drawio/image/edit.gif b/oaweb/public/cherry/drawio/image/edit.gif new file mode 100644 index 0000000..3c07607 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/edit.gif differ diff --git a/oaweb/public/cherry/drawio/image/expanded.gif b/oaweb/public/cherry/drawio/image/expanded.gif new file mode 100644 index 0000000..aece761 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/expanded.gif differ diff --git a/oaweb/public/cherry/drawio/image/grid.gif b/oaweb/public/cherry/drawio/image/grid.gif new file mode 100644 index 0000000..da699f8 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/grid.gif differ diff --git a/oaweb/public/cherry/drawio/image/handle-fixed.png b/oaweb/public/cherry/drawio/image/handle-fixed.png new file mode 100644 index 0000000..aed1cbe Binary files /dev/null and b/oaweb/public/cherry/drawio/image/handle-fixed.png differ diff --git a/oaweb/public/cherry/drawio/image/handle-main.png b/oaweb/public/cherry/drawio/image/handle-main.png new file mode 100644 index 0000000..e9a9962 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/handle-main.png differ diff --git a/oaweb/public/cherry/drawio/image/handle-rotate.png b/oaweb/public/cherry/drawio/image/handle-rotate.png new file mode 100644 index 0000000..4347ec0 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/handle-rotate.png differ diff --git a/oaweb/public/cherry/drawio/image/handle-secondary.png b/oaweb/public/cherry/drawio/image/handle-secondary.png new file mode 100644 index 0000000..b0626ef Binary files /dev/null and b/oaweb/public/cherry/drawio/image/handle-secondary.png differ diff --git a/oaweb/public/cherry/drawio/image/handle-terminal.png b/oaweb/public/cherry/drawio/image/handle-terminal.png new file mode 100644 index 0000000..c07c67e Binary files /dev/null and b/oaweb/public/cherry/drawio/image/handle-terminal.png differ diff --git a/oaweb/public/cherry/drawio/image/help.png b/oaweb/public/cherry/drawio/image/help.png new file mode 100644 index 0000000..c5dba91 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/help.png differ diff --git a/oaweb/public/cherry/drawio/image/hs.png b/oaweb/public/cherry/drawio/image/hs.png new file mode 100644 index 0000000..3d94486 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/hs.png differ diff --git a/oaweb/public/cherry/drawio/image/hv.png b/oaweb/public/cherry/drawio/image/hv.png new file mode 100644 index 0000000..1c5e01f Binary files /dev/null and b/oaweb/public/cherry/drawio/image/hv.png differ diff --git a/oaweb/public/cherry/drawio/image/locked.png b/oaweb/public/cherry/drawio/image/locked.png new file mode 100644 index 0000000..cfe371f Binary files /dev/null and b/oaweb/public/cherry/drawio/image/locked.png differ diff --git a/oaweb/public/cherry/drawio/image/logo.png b/oaweb/public/cherry/drawio/image/logo.png new file mode 100644 index 0000000..04fa132 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/logo.png differ diff --git a/oaweb/public/cherry/drawio/image/nocolor.png b/oaweb/public/cherry/drawio/image/nocolor.png new file mode 100644 index 0000000..697007b Binary files /dev/null and b/oaweb/public/cherry/drawio/image/nocolor.png differ diff --git a/oaweb/public/cherry/drawio/image/refresh.png b/oaweb/public/cherry/drawio/image/refresh.png new file mode 100644 index 0000000..e184a2c Binary files /dev/null and b/oaweb/public/cherry/drawio/image/refresh.png differ diff --git a/oaweb/public/cherry/drawio/image/round-drop.png b/oaweb/public/cherry/drawio/image/round-drop.png new file mode 100644 index 0000000..c78d5ab Binary files /dev/null and b/oaweb/public/cherry/drawio/image/round-drop.png differ diff --git a/oaweb/public/cherry/drawio/image/search.png b/oaweb/public/cherry/drawio/image/search.png new file mode 100644 index 0000000..251e345 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/search.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/arrows.xml b/oaweb/public/cherry/drawio/image/stencils/arrows.xml new file mode 100644 index 0000000..5e39b3c --- /dev/null +++ b/oaweb/public/cherry/drawio/image/stencils/arrows.xml @@ -0,0 +1,849 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/oaweb/public/cherry/drawio/image/stencils/basic.xml b/oaweb/public/cherry/drawio/image/stencils/basic.xml new file mode 100644 index 0000000..fe866c8 --- /dev/null +++ b/oaweb/public/cherry/drawio/image/stencils/basic.xml @@ -0,0 +1,895 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/oaweb/public/cherry/drawio/image/stencils/bpmn.xml b/oaweb/public/cherry/drawio/image/stencils/bpmn.xml new file mode 100644 index 0000000..af8918d --- /dev/null +++ b/oaweb/public/cherry/drawio/image/stencils/bpmn.xml @@ -0,0 +1,1162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Credit_Card_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Credit_Card_128x128.png new file mode 100644 index 0000000..33207ee Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Credit_Card_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Database_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Database_128x128.png new file mode 100644 index 0000000..8343951 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Database_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Doctor1_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Doctor1_128x128.png new file mode 100644 index 0000000..8a01fed Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Doctor1_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Earth_globe_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Earth_globe_128x128.png new file mode 100644 index 0000000..6ce1524 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Earth_globe_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Email_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Email_128x128.png new file mode 100644 index 0000000..f0daa2a Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Email_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Empty_Folder_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Empty_Folder_128x128.png new file mode 100644 index 0000000..dde142d Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Empty_Folder_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Firewall_02_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Firewall_02_128x128.png new file mode 100644 index 0000000..546232b Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Firewall_02_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Full_Folder_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Full_Folder_128x128.png new file mode 100644 index 0000000..12cd97e Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Full_Folder_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Gear_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Gear_128x128.png new file mode 100644 index 0000000..95f5f62 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Gear_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Graph_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Graph_128x128.png new file mode 100644 index 0000000..aa0af98 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Graph_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Laptop_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Laptop_128x128.png new file mode 100644 index 0000000..b750c70 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Laptop_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Lock_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Lock_128x128.png new file mode 100644 index 0000000..a4edcdd Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Lock_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/MacBook_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/MacBook_128x128.png new file mode 100644 index 0000000..6ec2ab2 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/MacBook_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Monitor_Tower_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Monitor_Tower_128x128.png new file mode 100644 index 0000000..de667f7 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Monitor_Tower_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Piggy_Bank_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Piggy_Bank_128x128.png new file mode 100644 index 0000000..b53aec1 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Piggy_Bank_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Pilot1_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Pilot1_128x128.png new file mode 100644 index 0000000..0e94003 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Pilot1_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Printer_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Printer_128x128.png new file mode 100644 index 0000000..3ba0f3c Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Printer_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Router_Icon_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Router_Icon_128x128.png new file mode 100644 index 0000000..66c5f2d Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Router_Icon_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Safe_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Safe_128x128.png new file mode 100644 index 0000000..85a3541 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Safe_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Security1_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Security1_128x128.png new file mode 100644 index 0000000..76d7a22 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Security1_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Server_Tower_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Server_Tower_128x128.png new file mode 100644 index 0000000..ebaa7a0 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Server_Tower_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Shopping_Cart_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Shopping_Cart_128x128.png new file mode 100644 index 0000000..6b0aa4d Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Shopping_Cart_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Software_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Software_128x128.png new file mode 100644 index 0000000..0e27a69 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Software_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Soldier1_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Soldier1_128x128.png new file mode 100644 index 0000000..c864949 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Soldier1_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Suit1_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Suit1_128x128.png new file mode 100644 index 0000000..87f28b6 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Suit1_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Suit2_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Suit2_128x128.png new file mode 100644 index 0000000..6126db7 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Suit2_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Suit3_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Suit3_128x128.png new file mode 100644 index 0000000..feaa45a Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Suit3_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Tech1_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Tech1_128x128.png new file mode 100644 index 0000000..74f1f2d Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Tech1_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Telesales1_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Telesales1_128x128.png new file mode 100644 index 0000000..18344b9 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Telesales1_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Virtual_Machine_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Virtual_Machine_128x128.png new file mode 100644 index 0000000..5698a79 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Virtual_Machine_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Virus_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Virus_128x128.png new file mode 100644 index 0000000..5516af7 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Virus_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Wireless_Router_N_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Wireless_Router_N_128x128.png new file mode 100644 index 0000000..469e662 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Wireless_Router_N_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Worker1_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Worker1_128x128.png new file mode 100644 index 0000000..60048eb Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Worker1_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/Workstation_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/Workstation_128x128.png new file mode 100644 index 0000000..c1a594a Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/Workstation_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/iMac_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/iMac_128x128.png new file mode 100644 index 0000000..4f7c4ef Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/iMac_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/clipart/iPad_128x128.png b/oaweb/public/cherry/drawio/image/stencils/clipart/iPad_128x128.png new file mode 100644 index 0000000..145d34b Binary files /dev/null and b/oaweb/public/cherry/drawio/image/stencils/clipart/iPad_128x128.png differ diff --git a/oaweb/public/cherry/drawio/image/stencils/flowchart.xml b/oaweb/public/cherry/drawio/image/stencils/flowchart.xml new file mode 100644 index 0000000..e6ec9d2 --- /dev/null +++ b/oaweb/public/cherry/drawio/image/stencils/flowchart.xml @@ -0,0 +1,920 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/oaweb/public/cherry/drawio/image/tooltip.png b/oaweb/public/cherry/drawio/image/tooltip.png new file mode 100644 index 0000000..bbe6e16 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/tooltip.png differ diff --git a/oaweb/public/cherry/drawio/image/transparent.gif b/oaweb/public/cherry/drawio/image/transparent.gif new file mode 100644 index 0000000..76040f2 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/transparent.gif differ diff --git a/oaweb/public/cherry/drawio/image/triangle-down.png b/oaweb/public/cherry/drawio/image/triangle-down.png new file mode 100644 index 0000000..b527b19 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/triangle-down.png differ diff --git a/oaweb/public/cherry/drawio/image/triangle-left.png b/oaweb/public/cherry/drawio/image/triangle-left.png new file mode 100644 index 0000000..dc8a3cb Binary files /dev/null and b/oaweb/public/cherry/drawio/image/triangle-left.png differ diff --git a/oaweb/public/cherry/drawio/image/triangle-right.png b/oaweb/public/cherry/drawio/image/triangle-right.png new file mode 100644 index 0000000..4c83b2c Binary files /dev/null and b/oaweb/public/cherry/drawio/image/triangle-right.png differ diff --git a/oaweb/public/cherry/drawio/image/triangle-up.png b/oaweb/public/cherry/drawio/image/triangle-up.png new file mode 100644 index 0000000..7975d05 Binary files /dev/null and b/oaweb/public/cherry/drawio/image/triangle-up.png differ diff --git a/oaweb/public/cherry/drawio/image/unlocked.png b/oaweb/public/cherry/drawio/image/unlocked.png new file mode 100644 index 0000000..707010a Binary files /dev/null and b/oaweb/public/cherry/drawio/image/unlocked.png differ diff --git a/oaweb/public/cherry/drawio/jscolor/arrow.gif b/oaweb/public/cherry/drawio/jscolor/arrow.gif new file mode 100644 index 0000000..246478a Binary files /dev/null and b/oaweb/public/cherry/drawio/jscolor/arrow.gif differ diff --git a/oaweb/public/cherry/drawio/jscolor/cross.gif b/oaweb/public/cherry/drawio/jscolor/cross.gif new file mode 100644 index 0000000..0ee9c7a Binary files /dev/null and b/oaweb/public/cherry/drawio/jscolor/cross.gif differ diff --git a/oaweb/public/cherry/drawio/jscolor/hs.png b/oaweb/public/cherry/drawio/jscolor/hs.png new file mode 100644 index 0000000..3d94486 Binary files /dev/null and b/oaweb/public/cherry/drawio/jscolor/hs.png differ diff --git a/oaweb/public/cherry/drawio/jscolor/hv.png b/oaweb/public/cherry/drawio/jscolor/hv.png new file mode 100644 index 0000000..1c5e01f Binary files /dev/null and b/oaweb/public/cherry/drawio/jscolor/hv.png differ diff --git a/oaweb/public/cherry/drawio/jscolor/jscolor.js b/oaweb/public/cherry/drawio/jscolor/jscolor.js new file mode 100644 index 0000000..7a7a54b --- /dev/null +++ b/oaweb/public/cherry/drawio/jscolor/jscolor.js @@ -0,0 +1,905 @@ +/** + * jscolor, JavaScript Color Picker (name changed to mxJSColor to avoid conflicts) + * + * @version 1.3.13 + * @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html + * @author Jan Odvarko, http://odvarko.cz + * @created 2008-06-15 + * @updated 2012-01-19 + * @link http://jscolor.com + */ + + +var mxJSColor = { + + bindClass : 'color', // class name + binding : true, // automatic binding via + preloading : true, // use image preloading? + + + install : function() { + //mxJSColor.addEvent(window, 'load', mxJSColor.init); + }, + + + init : function() { + if(mxJSColor.preloading) { + mxJSColor.preload(); + } + }, + + + getDir : function() { + return IMAGE_PATH + '/'; + }, + + + detectDir : function() { + var base = location.href; + + var e = document.getElementsByTagName('base'); + for(var i=0; i vs[a] ? + (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) : + tp[a], + -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ? + (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) : + (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c) + ]; + } + drawPicker(0, 0); + } + }; + + + this.importColor = function() { + if(!valueElement) { + this.exportColor(); + } else { + if(!this.adjust) { + if(!this.fromString(valueElement.value, leaveValue)) { + styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage; + styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor; + styleElement.style.color = styleElement.jscStyle.color; + this.exportColor(leaveValue | leaveStyle); + } + } else if(!this.required && /^\s*$/.test(valueElement.value)) { + valueElement.value = ''; + styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage; + styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor; + styleElement.style.color = styleElement.jscStyle.color; + this.exportColor(leaveValue | leaveStyle); + + } else if(this.fromString(valueElement.value)) { + // OK + } else { + this.exportColor(); + } + } + }; + + + this.exportColor = function(flags) { + if(!(flags & leaveValue) && valueElement) { + var value = this.toString(); + if(this.caps) { value = value.toUpperCase(); } + if(this.hash) { value = '#'+value; } + valueElement.value = value; + } + if(!(flags & leaveStyle) && styleElement) { + styleElement.style.backgroundImage = "none"; + styleElement.style.backgroundColor = + '#'+this.toString(); + styleElement.style.color = + 0.213 * this.rgb[0] + + 0.715 * this.rgb[1] + + 0.072 * this.rgb[2] + < 0.5 ? '#FFF' : '#000'; + } + if(!(flags & leavePad) && isPickerOwner()) { + redrawPad(); + } + if(!(flags & leaveSld) && isPickerOwner()) { + redrawSld(); + } + }; + + + this.fromHSV = function(h, s, v, flags) { // null = don't change + h<0 && (h=0) || h>6 && (h=6); + s<0 && (s=0) || s>1 && (s=1); + v<0 && (v=0) || v>1 && (v=1); + this.rgb = HSV_RGB( + h===null ? this.hsv[0] : (this.hsv[0]=h), + s===null ? this.hsv[1] : (this.hsv[1]=s), + v===null ? this.hsv[2] : (this.hsv[2]=v) + ); + this.exportColor(flags); + }; + + + this.fromRGB = function(r, g, b, flags) { // null = don't change + r<0 && (r=0) || r>1 && (r=1); + g<0 && (g=0) || g>1 && (g=1); + b<0 && (b=0) || b>1 && (b=1); + var hsv = RGB_HSV( + r===null ? this.rgb[0] : (this.rgb[0]=r), + g===null ? this.rgb[1] : (this.rgb[1]=g), + b===null ? this.rgb[2] : (this.rgb[2]=b) + ); + if(hsv[0] !== null) { + this.hsv[0] = hsv[0]; + } + if(hsv[2] !== 0) { + this.hsv[1] = hsv[1]; + } + this.hsv[2] = hsv[2]; + this.exportColor(flags); + }; + + + this.fromString = function(hex, flags) { + var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i); + if(!m) { + return false; + } else { + if(m[1].length === 6) { // 6-char notation + this.fromRGB( + parseInt(m[1].substr(0,2),16) / 255, + parseInt(m[1].substr(2,2),16) / 255, + parseInt(m[1].substr(4,2),16) / 255, + flags + ); + } else { // 3-char notation + this.fromRGB( + parseInt(m[1].charAt(0)+m[1].charAt(0),16) / 255, + parseInt(m[1].charAt(1)+m[1].charAt(1),16) / 255, + parseInt(m[1].charAt(2)+m[1].charAt(2),16) / 255, + flags + ); + } + return true; + } + }; + + + this.toString = function() { + return ( + (0x100 | Math.round(255*this.rgb[0])).toString(16).substr(1) + + (0x100 | Math.round(255*this.rgb[1])).toString(16).substr(1) + + (0x100 | Math.round(255*this.rgb[2])).toString(16).substr(1) + ); + }; + + + function RGB_HSV(r, g, b) { + var n = Math.min(Math.min(r,g),b); + var v = Math.max(Math.max(r,g),b); + var m = v - n; + if(m === 0) { return [ null, 0, v ]; } + var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); + return [ h===6?0:h, m/v, v ]; + } + + + function HSV_RGB(h, s, v) { + if(h === null) { return [ v, v, v ]; } + var i = Math.floor(h); + var f = i%2 ? h-i : 1-(h-i); + var m = v * (1 - s); + var n = v * (1 - s*f); + switch(i) { + case 6: + case 0: return [v,n,m]; + case 1: return [n,v,m]; + case 2: return [m,v,n]; + case 3: return [m,n,v]; + case 4: return [n,m,v]; + case 5: return [v,m,n]; + } + } + + + function removePicker() { + delete mxJSColor.picker.owner; + document.getElementsByTagName('body')[0].removeChild(mxJSColor.picker.boxB); + } + + + function drawPicker(x, y) { + if(!mxJSColor.picker) { + mxJSColor.picker = { + box : document.createElement('div'), + boxB : document.createElement('div'), + pad : document.createElement('div'), + padB : document.createElement('div'), + padM : document.createElement('div'), + sld : document.createElement('div'), + sldB : document.createElement('div'), + sldM : document.createElement('div'), + btn : document.createElement('div'), + btnS : document.createElement('span'), + btnT : document.createTextNode(THIS.pickerCloseText) + }; + for(var i=0,segSize=4; i{2},然后返回此标签以尝试再次保存。 +errorSendingFeedback=发送反馈出错。 +errorUpdatingPreview=更新预览出错。 +exit=退出 +exitGroup=退出编辑组 +expand=展开 +export=导出 +exporting=导出中 +exportAs=导出为 +exportAsPng=导出为png +exportAsXml=导出为xml +exportOptionsDisabled=已禁止导出 +exportOptionsDisabledDetails=所有者已禁止评论者及浏览者下载、打印或复制该文件。 +externalChanges=External Changes +extras=其它 +facebook=Facebook +failedToSaveTryReconnect=保存失败,正在尝试重新连接 +featureRequest=增加功能请求 +feedback=反馈 +feedbackSent=反馈发送成功 +floorplans=平面图 +file=文件 +fileChangedOverwriteDialog=文件已更改。是否覆盖更改? +fileChangedSyncDialog=The file has been modified. Do you want to synchronize those changes? +fileChangedSync=The file has been modified. Click here to synchronize. +overwrite=覆盖 +synchronize=Synchronize +filename=文件名 +fileExists=文件已存在 +fileNearlyFullSeeFaq=文件即将达到上限,请参阅常见问题一栏 +fileNotFound=未找到文件 +repositoryNotFound=未找到资源库 +fileNotFoundOrDenied=文件未找到。其不存在或您没有查阅权限。 +fileNotLoaded=文件无法加载 +fileNotSaved=文件无法保存 +fileOpenLocation=你想如何打开这些文件? +filetypeHtml=.html causes file to save as HTML with redirect to cloud URL +filetypePng=.png causes file to save as PNG with embedded data +filetypeSvg=.svg causes file to save as SVG with embedded data +fileWillBeSavedInAppFolder={1}将保存至应用软件文件夹 +fill=填充 +fillColor=填充色 +filterCards=Filter Cards +find=查找 +fit=适应大小 +fitContainer=调整容器尺寸 +fitIntoContainer=适应容器尺寸 +fitPage=整页显示 +fitPageWidth=适应页面宽度 +fitTo=适应 +fitToSheetsAcross=横向页面 +fitToBy=按 +fitToSheetsDown=纵向页面 +fitTwoPages=双页 +fitWindow=适应视窗大小 +flip=翻转 +flipH=水平翻转 +flipV=垂直翻转 +flowchart=流程图 +folder=文件夹 +font=字体 +fontColor=字体颜色 +fontFamily=字体 +fontSize=字体大小 +forbidden=您没有该文件的访问权限 +format=格式 +formatPanel=格式面板 +formatted=已格式化 +formattedText=已格式化文本 +formatPng=PNG +formatGif=GIF +formatJpg=JPEG +formatPdf=PDF +formatSql=SQL +formatSvg=SVG +formatHtmlEmbedded=HTML +formatSvgEmbedded=SVG(含XML) +formatVsdx=VSDX +formatVssx=VSSX +formatXmlPlain=XML(普通) +formatXml=XML +forum=讨论组/帮助论坛 +fromTemplate=从模板 +fromTemplateUrl=从模板URL地址 +fromText=从文本 +fromUrl=从URL地址 +fromThisPage=从当前页 +fullscreen=全屏 +gap=Gap +gcp=GCP +general=通用 +github=GitHub +gliffy=Gliffy +global=全局 +googleDocs=Google Docs +googleDrive=Google Drive +googleGadget=Google Gadget +googlePlus=Google+ +googleSharingNotAvailable=Sharing is only available via Google Drive. Please click Open below and share from the more actions menu: +googleSlides=Google Slides +googleSites=Google Sites +gradient=渐变 +gradientColor=颜色 +grid=网格 +gridColor=网格线颜色 +gridSize=网格大小 +group=组合 +guides=参考线 +hateApp=我讨厌draw.io +heading=标题 +height=高 +help=帮助 +helpTranslate=帮助我们翻译此应用 +hide=隐藏 +hideIt=隐藏{1} +hidden=已隐藏 +home=首页 +horizontal=水平 +horizontalFlow=水平流动 +horizontalTree=水平树状 +howTranslate=按您的母语水平评判,该翻译如何? +html=HTML +htmlText=HTML文本 +id=ID +iframe=IFrame +ignore=忽略 +image=图片 +imageUrl=图片URL地址 +images=图片 +imagePreviewError=无法预览图片。请检查URL地址。 +imageTooBig=图片太大 +imgur=Imgur +import=导入 +importFrom=从...导入 +includeCopyOfMyDiagram=包含图表副本 +increaseIndent=增加缩进 +decreaseIndent=减少缩进 +insert=插入 +insertColumnBefore=左边插入列 +insertColumnAfter=右边插入列 +insertEllipse=插入椭圆 +insertImage=插入图片 +insertHorizontalRule=插入水平标尺 +insertLink=插入链接 +insertPage=插入页面 +insertRectangle=插入矩形 +insertRhombus=Insert Rhombus +insertRowBefore=上方插入行 +insertRowAfter=下方插入行 +insertText=插入文本 +inserting=正在插入 +invalidFilename=图表名称不能包含以下特殊字符: \ / | : ; { } < > & + ? = " +invalidLicenseSeeThisPage=您的许可无效,请参阅此页面。 +invalidName=无效名称 +invalidOrMissingFile=无效或丢失的文件 +invalidPublicUrl=无效的公开URL地址 +isometric=等尺寸 +ios=iOS +italic=斜体 +kennedy=Kennedy +keyboardShortcuts=快捷键 +layers=图层 +landscape=横向 +language=语言 +leanMapping=价值流图 +lastChange={1}以前最新更改 +lessThanAMinute=一分钟以内 +licensingError=授权出错 +licenseHasExpired={1} 的许可证已于 {2} 过期。请点击此处。 +licenseWillExpire={1} 的许可证将于 {2} 过期。请点击此处。 +lineJumps=Line jumps +linkAccountRequired=如果图表未公开,则需要提供谷歌账户才能查看该链接。 +linkText=链接文本 +list=列表 +minute=分钟 +minutes=分钟 +hours=小时 +days=天 +months=月 +years=年 +restartForChangeRequired=更改将在页面刷新后生效。 +laneColor=泳道颜色 +lastModified=最近修改 +layout=布局 +left=左 +leftAlign=左对齐 +leftToRight=右对齐 +libraryTooltip=将图形拖放至此或单击+以插入。双击进行编辑。 +lightbox=光箱特效 +line=边框 +lineend=线末端 +lineheight=行距 +linestart=线始端 +linewidth=线宽 +link=链接 +links=链接 +loading=加载中 +lockUnlock=加锁/解锁 +loggedOut=登出 +logIn=登入 +loveIt=我爱{1} +lucidchart=Lucidchart +maps=Maps +mathematicalTypesetting=数学排版 +makeCopy=创建副本 +manual=手册 +microsoftExcel=Microsoft Excel +microsoftPowerPoint=Microsoft PowerPoint +microsoftWord=Microsoft Word +middle=垂直居中 +minimal=Minimal +misc=杂项 +mockups=实体模型 +modificationDate=修改日期 +modifiedBy=修改者 +more=更多 +moreResults=更多结果 +moreShapes=更多图形 +move=移动 +moveToFolder=移动至文件夹 +moving=移动中 +moveSelectionTo=将所选移至{1} +name=名称 +navigation=导航 +network=Network +networking=网络 +new=新增 +newLibrary=新增图库 +nextPage=下一页 +no=没有 +noPickFolder=No, pick folder +noAttachments=未找到附件 +noColor=无颜色 +noFiles=无文件 +noFileSelected=未选择文件 +noLibraries=未找到图库 +noMoreResults=无其他结果 +none=无 +noOtherViewers=无其他查阅者 +noPlugins=无插件 +noPreview=无预览 +noResponse=服务器无响应 +noResultsFor=未找到'{1}'的相关结果 +noRevisions=无修订 +noSearchResults=查询无结果 +noPageContentOrNotSaved=此页面上找不到锚点,或尚未保存 +normal=正常 +north=向上 +notADiagramFile=非图表文件 +notALibraryFile=非图库文件 +notAvailable=不可用 +notAUtf8File=非 UTF-8 格式文件 +notConnected=未连接 +note=备注 +notUsingService=未使用{1}? +numberedList=编号列表 +offline=离线 +ok=确定 +oneDrive=OneDrive +online=线上 +opacity=不透明度 +open=打开 +openArrow=开放的箭头 +openExistingDiagram=打开现有图表 +openFile=打开文件 +openFrom=从...打开 +openLibrary=打开图库 +openLibraryFrom=从...打开图库 +openLink=打开链接 +openInNewWindow=在新窗口打开 +openInThisWindow=在当前窗口打开 +openIt=打开{1} +openRecent=打开最近使用的文件 +openSupported=此软件支持的格式为从本软件(.xml), .vsdx 及 .gliffy存储的文件 +options=选项 +organic=力导向布局图 +orthogonal=正交 +otherViewer=其他查阅者 +otherViewers=其他查阅者 +outline=缩略图 +oval=椭圆形 +page=页面 +pageContent=页面内容 +pageNotFound=未找到页面 +pageWithNumber=第 {1} 页 +pages=页面 +pageView=页面视图 +pageSetup=页面设置 +pageScale=页面比例 +pan=移动画布 +panTooltip=按住空格键并拖拽以移动画布 +paperSize=页面尺寸 +pattern=样式 +paste=粘贴 +pasteHere=贴于此处 +pasteSize=Paste Size +pasteStyle=粘贴样式 +perimeter=周长 +permissionAnyone=任何人均可编辑 +permissionAuthor=只有本人可编辑 +pickFolder=选择文件夹 +pickLibraryDialogTitle=选择图库 +publicDiagramUrl=图表的公共URL地址 +placeholders=占位符 +plantUml=PlantUML +plugins=插件 +pluginUrl=插件URL地址 +pluginWarning=本页面已要求载入以下插件:\n \n {1}\n \n 是否现在载入这些插件?\n \n 备注:确保在完全理解与此相关的安全问题的情况下再允许这些插件运行。\n +plusTooltip=单击进行连接与复制(ctrl+单击进行复制,shift+单击进行连接)。拖拽进行连接(xtrl+拖拽进行复制)。 +portrait=竖向 +position=位置 +posterPrint=海报样式 +preferences=喜好 +preview=预览 +previousPage=上一页 +print=打印 +printAllPages=打印所有页 +procEng=工艺流程 +project=方案 +priority=优先级 +properties=属性 +publish=发布 +quickStart=快速入门视频 +rack=机架 +radialTree=径向树 +readOnly=只读 +reconnecting=重新连接 +recentlyUpdated=最近更新 +recentlyViewed=最近阅览 +rectangle=Rectangle +redirectToNewApp=该文件是在此应用软件的新版本中所创建或修改的。正在重新定向。 +realtimeTimeout=似乎您在离线状态下做过更改。对不起,这些更改不予保存。 +redo=重做 +refresh=刷新 +regularExpression=正则表达式 +relative=Relative +relativeUrlNotAllowed=Relative URL not allowed +rememberMe=记住我 +rememberThisSetting=记住此设置 +removeFormat=清除格式 +removeFromGroup=移出组 +removeIt=删除{1} +removeWaypoint=删除航点 +rename=重命名 +renamed=已重命名 +renameIt=重命名{1} +renaming=正在重命名 +replace=替换 +replaceIt={1}已经存在了。要替换它吗? +replaceExistingDrawing=替换当前图形 +required=必填 +reset=重置 +resetView=重置视图 +resize=调整大小 +resizeLargeImages=Do you want to resize large images to make the application run faster? +retina=Retina +responsive=响应式 +restore=恢复 +restoring=恢复中 +retryingIn={1}秒后重试 +retryingLoad=载入失败,正在重试... +retryingLogin=登录超时,正在重试... +reverse=翻转 +revision=修订 +revisionHistory=修订历史 +rhombus=Rhombus +right=右 +rightAlign=右对齐 +rightToLeft=从右至左 +rotate=旋转 +rotateTooltip=点选拖拽旋转,或点击选择90度 +rotation=旋转 +rounded=圆角 +save=保存 +saveAndExit=储存并退出 +saveAs=另存为 +saveAsXmlFile=另存为XML文件? +saved=已保存 +saveDiagramsTo=把图表存至 +saveLibrary403=没有足够的权限编辑此图库 +saveLibrary500=保存图库时出错 +saving=保存中 +scratchpad=便笺本 +scrollbars=滚动条 +search=搜索 +searchShapes=搜索图形 +selectAll=全选 +selectionOnly=仅所选内容 +selectCard=Select Card +selectEdges=选择边线 +selectFile=选择文件 +selectFolder=选择文件夹 +selectFont=选择字体 +selectNone=全不选 +selectTemplate=Select Template +selectVertices=选择顶点 +sendMessage=发送 +sendYourFeedbackToDrawIo=将您的反馈发送至draw.io +serviceUnavailableOrBlocked=服务无法使用或已被屏蔽 +sessionExpired=会话已过期,请刷新浏览器窗口。 +sessionTimeoutOnSave=会话已超时,您的Google Drive的连接已断开。按OK键登录并保存。 +setAsDefaultStyle=设置为默认样式 +shadow=阴影 +shape=形状 +shapes=形状 +share=共享 +shareLink=共享编辑的链接 +sharp=尖角 +show=显示 +showStartScreen=显示开始画面 +sidebarTooltip=单击以展开。将图形拖拽至图表中。Shift+单击以改变所选内容。Alt+单击以插入及连接。 +signs=标识 +signOut=登出 +simple=简单 +simpleArrow=简单箭头 +simpleViewer=Simple Viewer +size=大小 +solid=实线 +sourceSpacing=源距 +south=向下 +software=软件 +space=空间 +spacing=间距 +specialLink=特殊链接 +standard=标准 +starting=开启中 +straight=直线 +strikethrough=Strikethrough +strokeColor=线条颜色 +style=样式 +subscript=下标 +summary=概要 +superscript=上标 +support=支持 +sysml=SysML +tags=标签 +table=表格 +tables=Tables +takeOver=Take Over +targetSpacing=目标间距 +template=模板 +templates=模板 +text=文本 +textAlignment=文本对齐 +textOpacity=字体不透明度 +theme=主题 +timeout=超时 +title=标题 +to=至 +toBack=移至最后 +toFront=移至最前 +toolbar=Toolbar +tooltips=提示 +top=顶 +topAlign=向上对齐 +topLeft=左上 +topRight=右上 +transparent=透明 +transparentBackground=透明背景 +trello=Trello +tryAgain=重试 +tryOpeningViaThisPage=尝试通过此页面开启 +turn=旋转90° +type=类型 +twitter=Twitter +uml=UML +underline=下划线 +undo=取消操作 +ungroup=取消群组 +unsavedChanges=未保存的更改 +unsavedChangesClickHereToSave=修改未保存。点击此处保存。 +untitled=未命名 +untitledDiagram=未命名表单 +untitledLayer=未命名图层 +untitledLibrary=未命名图库 +unknownError=未知错误 +updateFile=更新{1} +updatingDocument=文件更新中。请稍候... +updatingPreview=预览更新中。请稍候... +updatingSelection=选择更新中。请稍候... +upload=上传 +url=URL地址 +useOffline=Use Offline +useRootFolder=Use root folder? +userManual=用户手册 +vertical=垂直 +verticalFlow=垂直流 +verticalTree=垂直树状 +view=查看 +viewerSettings=Viewer Settings +viewUrl=用于查看的链接:{1} +voiceAssistant=语音助手(测试版) +warning=警告 +waypoints=航点 +west=向左 +width=宽度 +wiki=Wiki +wordWrap=自动换行 +writingDirection=书写方向 +yes=是 +yourEmailAddress=您的电子邮件地址 +zoom=缩放 +zoomIn=放大 +zoomOut=缩小 +basic=基本图形 +businessprocess=业务流程图 +charts=图表 +engineering=工程图 +flowcharts=流程图 +gmdl=材料设计 +mindmaps=思维导图 +mockups=模型图 +networkdiagrams=网络结构图 +nothingIsSelected=未选择 +other=其他 +softwaredesign=软件设计图 +venndiagrams=文氏图 +webEmailOrOther=网站、电邮或其他网络地址 +webLink=Web链接 +wireframes=线框图 \ No newline at end of file diff --git a/oaweb/public/cherry/drawio/src/css/common.css b/oaweb/public/cherry/drawio/src/css/common.css new file mode 100644 index 0000000..37bfb0e --- /dev/null +++ b/oaweb/public/cherry/drawio/src/css/common.css @@ -0,0 +1,162 @@ +div.mxRubberband { + position: absolute; + overflow: hidden; + border-style: solid; + border-width: 1px; + border-color: #0000FF; + background: #0077FF; +} +.mxCellEditor { + background: url(data:image/gif;base64,R0lGODlhMAAwAIAAAP///wAAACH5BAEAAAAALAAAAAAwADAAAAIxhI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8egpAAA7); + _background: url('../images/transparent.gif'); + border-color: transparent; + border-style: solid; + display: inline-block; + position: absolute; + overflow: visible; + word-wrap: normal; + border-width: 0; + min-width: 1px; + resize: none; + padding: 0px; + margin: 0px; +} +.mxPlainTextEditor * { + padding: 0px; + margin: 0px; +} +div.mxWindow { + -webkit-box-shadow: 3px 3px 12px #C0C0C0; + -moz-box-shadow: 3px 3px 12px #C0C0C0; + box-shadow: 3px 3px 12px #C0C0C0; + background: url('../images/window.gif'); + border:1px solid #c3c3c3; + position: absolute; + overflow: hidden; + z-index: 1; +} +table.mxWindow { + border-collapse: collapse; + table-layout: fixed; + font-family: Arial; + font-size: 8pt; +} +td.mxWindowTitle { + background: url('../images/window-title.gif') repeat-x; + text-overflow: ellipsis; + white-space: nowrap; + text-align: center; + font-weight: bold; + overflow: hidden; + height: 13px; + padding: 2px; + padding-top: 4px; + padding-bottom: 6px; + color: black; +} +td.mxWindowPane { + vertical-align: top; + padding: 0px; +} +div.mxWindowPane { + overflow: hidden; + position: relative; +} +td.mxWindowPane td { + font-family: Arial; + font-size: 8pt; +} +td.mxWindowPane input, td.mxWindowPane select, td.mxWindowPane textarea, td.mxWindowPane radio { + border-color: #8C8C8C; + border-style: solid; + border-width: 1px; + font-family: Arial; + font-size: 8pt; + padding: 1px; +} +td.mxWindowPane button { + background: url('../images/button.gif') repeat-x; + font-family: Arial; + font-size: 8pt; + padding: 2px; + float: left; +} +img.mxToolbarItem { + margin-right: 6px; + margin-bottom: 6px; + border-width: 1px; +} +select.mxToolbarCombo { + vertical-align: top; + border-style: inset; + border-width: 2px; +} +div.mxToolbarComboContainer { + padding: 2px; +} +img.mxToolbarMode { + margin: 2px; + margin-right: 4px; + margin-bottom: 4px; + border-width: 0px; +} +img.mxToolbarModeSelected { + margin: 0px; + margin-right: 2px; + margin-bottom: 2px; + border-width: 2px; + border-style: inset; +} +div.mxTooltip { + -webkit-box-shadow: 3px 3px 12px #C0C0C0; + -moz-box-shadow: 3px 3px 12px #C0C0C0; + box-shadow: 3px 3px 12px #C0C0C0; + background: #FFFFCC; + border-style: solid; + border-width: 1px; + border-color: black; + font-family: Arial; + font-size: 8pt; + position: absolute; + cursor: default; + padding: 4px; + color: black; +} +div.mxPopupMenu { + -webkit-box-shadow: 3px 3px 12px #C0C0C0; + -moz-box-shadow: 3px 3px 12px #C0C0C0; + box-shadow: 3px 3px 12px #C0C0C0; + background: url('../images/window.gif'); + position: absolute; + border-style: solid; + border-width: 1px; + border-color: black; +} +table.mxPopupMenu { + border-collapse: collapse; + margin-top: 1px; + margin-bottom: 1px; +} +tr.mxPopupMenuItem { + color: black; + cursor: pointer; +} +tr.mxPopupMenuItemHover { + background-color: #000066; + color: #FFFFFF; + cursor: pointer; +} +td.mxPopupMenuItem { + padding: 2px 30px 2px 10px; + white-space: nowrap; + font-family: Arial; + font-size: 8pt; +} +td.mxPopupMenuIcon { + background-color: #D0D0D0; + padding: 2px 4px 2px 4px; +} +.mxDisabled { + opacity: 0.2 !important; + cursor:default !important; +} diff --git a/oaweb/public/cherry/drawio/src/css/explorer.css b/oaweb/public/cherry/drawio/src/css/explorer.css new file mode 100644 index 0000000..50e704f --- /dev/null +++ b/oaweb/public/cherry/drawio/src/css/explorer.css @@ -0,0 +1,18 @@ +div.mxTooltip { + filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4, + Color='#A2A2A2', Positive='true'); +} +div.mxPopupMenu { + filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4, + Color='#C0C0C0', Positive='true'); +} +div.mxWindow { + _filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4, + Color='#C0C0C0', Positive='true'); +} +td.mxWindowTitle { + _height: 23px; +} +.mxDisabled { + filter:alpha(opacity=20) !important; +} diff --git a/oaweb/public/cherry/drawio/src/grapheditor.less b/oaweb/public/cherry/drawio/src/grapheditor.less new file mode 100644 index 0000000..4034008 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/grapheditor.less @@ -0,0 +1,1530 @@ +@iconfont-path : '../../../'; +@import '../../../iconfont/graph.iconfont.less'; + +.msgbox { + z-index: 100; +} + +.geEditor { + font-family: Helvetica Neue, Helvetica, Arial Unicode MS, Arial; + font-size: 10pt; + border: none; + margin: 0px; +} + +.geEditor input[type=text]::-ms-clear { + display: none; +} + +.geItem, +.geToolbar .geButton, +.geToolbar .geLabel, +.geSidebarContainer .geTitle { + cursor: pointer !important; +} + +.geBackgroundPage { + -webkit-box-shadow: 0px 0px 3px 0px #d9d9d9; + -moz-box-shadow: 0px 0px 3px 0px #d9d9d9; + box-shadow: 0px 0px 3px 0px #d9d9d9; +} + +.geSidebarContainer a, +a, +.geToolbar a { + color: #182b50; + text-decoration: none; +} + +.geMenubarContainer, +.geToolbarContainer, +.geDiagramContainer, +.geSidebarContainer, +.geFooterContainer, +.geHsplit, +.geVsplit { + overflow: hidden; + position: absolute; + cursor: default; +} +.geMenubarContainer { + background-color:#fbfbfb !important; + height: 32px; +} +.geFormatContainer { + background-color: whiteSmoke !important; + overflow-x: hidden !important; + overflow-y: auto !important; + font-size: 12px; +} +div.geFormatContainer .geColorBtn.geButton>.geSprite{ + padding-left: 2.5px; + padding-top: 1.5px; +} +div.geFormatContainer button{ + background-color: #f3f4f6; + color:#3582fb; + border: 1px solid #e8eaee; + height: 30px; +} +div.geFormatContainer button{ + background: #f3f4f6; +} +div.geFormatContainer input{ + border-top: 1px solid #e8eaee; + border-bottom: 1px solid #e8eaee; + border-left: 1px solid #e8eaee; + color: #8c95a8; +} +div.geFormatContainer { + span{ + position: absolute; + margin-top: -2px; + } + input[value="landscape"]{ + margin-left: 35px!important; + } + div[style="margin-left: 4px; width: 210px; height: 24px;"]{ + span{ + margin-top: 1px; + } + } +} +div[style="right: 0px; z-index: 1; top: 35px; width: 240px; bottom: 28px;"]{ + background: #fff!important; +} +button[title="Copy Size (Alt+Shit+X)"]{ + background: red; +} +div[style="display: inline; z-index: 10006; left: 898px; top: 130px;"],div[style="display: inline; z-index: 10006; left: 77.6719px; top: 29px;"],div[style="display: inline; z-index: 10006; left: 1696px; top: 130px;"],div[style="display: inline; z-index: 10006; left: 898px; top: 158px;"]{ + table.mxPopupMenu{ + tr:nth-last-child(1){ + display: none + } + tr:nth-last-child(2){ + display: none + } + } + +} +.geDiagramContainer::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.geDiagramContainer::-webkit-scrollbar-thumb { + border-radius: 10px; + background: #bac0cb; +} + +.geDiagramContainer::-webkit-scrollbar-track { + border-radius: 10px; + background: #fff +} +div[style="text-align: center; font-weight: bold; overflow: hidden; display: inline-block; padding-top: 8px; height: 25px; width: 100%;"]{ + text-indent: 18px; + font-size: 14px; + text-align: left!important; +} +div.geToolbar{ + a[title="字体"]{ + div{ + margin-top: 2px + } + } + a[title="插入"]{ + div{ + margin-top: -1px!important; + } + } +} +div[style="white-space: nowrap; color: rgb(63, 74, 86); text-align: left; cursor: default; border-bottom: none;"]{ + div[style="padding: 2px 0px 4px 18px; border-bottom: 1px solid rgb(223, 230, 238);"]{ + div{ + margin: 5px 0; + } + } +} +div[tietle="自动调整 (Ctrl+Shift+Y)"]{ + margin-top: -1px; +} +div[style="border: 20px; position: absolute; margin-top: 2px; right: 20px;"]{ + div.geBtnUp,div.geBtnDown{ + width: 12px; + height: 12px; + } +} +button[title="设置为默认样式 (Ctrl+Shift+D)"]{ + display: none; +} +div[style="padding: 10px 0px 10px 18px; border-bottom: 1px solid rgb(223, 230, 238);"]{ + button{ + cursor: pointer; + } +} +button[style="line-height: normal; margin-top: 4px; margin-bottom: 8px;"]{ + line-height: normal; + margin-top: 20px; + margin-bottom: 8px; + height: 30px; + width: 120px; + font-size: 12px; + background-color: #f3f4f6; + color: #3582fb; + border: 1px solid #e8eaee; + cursor: pointer; +} +// div[style="white-space: nowrap; color: rgb(63, 74, 86); text-align: left; cursor: default;"]{ +// a[class="geButton geColorBtn"]{ +// position: relative; +// width: 50px!important; +// div:first-child{ +// position: absolute; +// left: 0; +// } +// div:last-child{ +// position: absolute; +// right: 0; +// } + +// } +// } +div[class="geSidebarContainer geFormatContainer"]{ + button{ + cursor: pointer; + } +} +.geDiagramContainer { + background-color: #ebebeb; + border: 1px solid #e5e5e5; + font-size: 0px; + outline: none; + min-width: 100px; + table{ + min-width: 100px; + } +} + +.geMenubar, +.geToolbar { + white-space: nowrap; + display: block; + width: 100%; +} + +.geItem, +.geToolbar .geButton, +.geToolbar .geLabel, +.geSidebar, +.geSidebarContainer .geTitle, +.geSidebar .geItem, +.mxPopupMenuItem { + -webkit-transition: all 0.1s ease-in-out; + -moz-transition: all 0.1s ease-in-out; + -o-transition: all 0.1s ease-in-out; + -ms-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; +} + +.geHint { + background-color: #ffffff; + border: 1px solid gray; + padding: 4px 16px 4px 16px; + border-radius: 3px; + -webkit-box-shadow: 1px 1px 2px 0px #ddd; + -moz-box-shadow: 1px 1px 2px 0px #ddd; + box-shadow: 1px 1px 2px 0px #ddd; + opacity: 0.8; + filter: alpha(opacity=80); + font-size: 9pt; +} + +.geStatusAlert { + white-space: nowrap; + margin-top: -5px; + font-size: 12px; + padding: 4px 6px 4px 6px; + background-color: #f2dede; + border: 1px solid #ebccd1; + color: #a94442 !important; + border-radius: 3px; +} + +.geStatusAlert:hover { + background-color: #f1d8d8; + border-color: #d6b2b8; +} + +.geStatusMessage { + white-space: nowrap; + margin-top: -5px; + padding: 4px 6px 4px 6px; + font-size: 12px; + background-image: -webkit-linear-gradient(top, #dff0d8 0, #c8e5bc 100%); + background-image: -o-linear-gradient(top, #dff0d8 0, #c8e5bc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); + background-image: linear-gradient(to bottom, #dff0d8 0, #c8e5bc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); + background-repeat: repeat-x; + border: 1px solid #b2dba1; + border-radius: 3px; + color: #3c763d !important; +} + +.geStatusMessage:hover { + background: #c8e5bc; + border-color: #b2dba1; +} + +.geAlert { + position: absolute; + white-space: nowrap; + padding: 14px; + background-color: #f2dede; + border: 1px solid #ebccd1; + color: #a94442; + border-radius: 3px; + -webkit-box-shadow: 2px 2px 3px 0px #ddd; + -moz-box-shadow: 2px 2px 3px 0px #ddd; + box-shadow: 2px 2px 3px 0px #ddd; +} + +.geBtn, +.mxWindow .geBtn { + background-image: none; + background-color: #fff; + border-radius: 2px; + border: 1px solid #d8d8d8; + color: #333; + font-size: 11px; + font-weight: bold; + height: 29px; + line-height: 27px; + margin: 0 0 0 8px; + min-width: 72px; + outline: 0; + padding: 0 8px; + cursor: pointer; +} + +.geBtn:hover, +.geBtn:focus { + -webkit-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1); + border: 1px solid #c6c6c6; + background-color: #f3f4f6; + background-image: linear-gradient(#f3f4f6 0px, #f1f1f1 100%); + color: #111; +} + +.geBtn:disabled { + opacity: .5; +} + +.geBtnUp {//上箭头 + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkQ2RDQyMEMzQkYzQzExRTlCNzY1RkIxNzk1N0NBRTIxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkQ2RDQyMEM0QkYzQzExRTlCNzY1RkIxNzk1N0NBRTIxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RDZENDIwQzFCRjNDMTFFOUI3NjVGQjE3OTU3Q0FFMjEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RDZENDIwQzJCRjNDMTFFOUI3NjVGQjE3OTU3Q0FFMjEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5GWB76AAAAS0lEQVR42mL8//8/AzGABcbYuPsYjNkKpatBhL+rFapCKAgA4ioo+zQQb4BJMCEpUgfiRUj8RVAxDIXrgJgXic8LFQMDRmI9AxBgAHIsD1ZPQxaeAAAAAElFTkSuQmCC); + background-position: center center; + background-repeat: no-repeat; +} + +.geBtnUp:active { + background-color: #4d90fe; + background-image: linear-gradient(#4d90fe 0px, #357ae8 100%); +} + +.geBtnDown {//下箭头 + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkM1RjQ1RDYzQkYzQzExRTk5QUUxOTk1NzhDMkFFNTkzIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkM1RjQ1RDY0QkYzQzExRTk5QUUxOTk1NzhDMkFFNTkzIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QzVGNDVENjFCRjNDMTFFOTlBRTE5OTU3OEMyQUU1OTMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QzVGNDVENjJCRjNDMTFFOTlBRTE5OTU3OEMyQUU1OTMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz51P1COAAAASklEQVR42mL8//8/AzGABcbYuPvYVSClhSZ/zd/VShvEYEISDALiz0j8z1AxBnSFN4E4DokfBxVDtRoKNgBxGxIbDhiJ9QxAgAEAyjMR5zuovT4AAAAASUVORK5CYII=); + background-position: center center; + background-repeat: no-repeat; +} + +.geBtnDown:active { + background-color: #4d90fe; + background-image: linear-gradient(#4d90fe 0px, #357ae8 100%); +} + +.geColorBtn { + background-color: #f3f4f6!important; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.5); + color: #333; + margin: 0px; + outline: 0; + padding: 0px; + cursor: pointer; +} +div.geToolbarContainer{ + a[class="geButton geColorBtn"]:hover { + background-color: #eaf2ff!important; + color: #3582fb; + } + .geColorBtn:checked { + background-color: #3582fb!important; + } + + a[class="geButton geColorBtn"]:active { + background-color: #3582fb!important; + } + + .geColorBtn:disabled { + opacity: .5; + } +} + + +.gePrimaryBtn, +.mxWindow .gePrimaryBtn { + background-color: #3582fb; + background-image: linear-gradient(#3582fb 0px, #3582fb 100%); + border: 1px solid #3582fb; + color: #fff; + font-weight: normal; +} + +.gePrimaryBtn:hover, +.gePrimaryBtn:focus { + background-color: #5d9bfc; + background-image: linear-gradient(#5d9bfc 0px, #5d9bfc 100%); + border: 1px solid #5d9bfc; + color: #fff; + font-weight: normal; +} + +.gePrimaryBtn:disabled { + opacity: .5; +} + +.geAlertLink { + color: #843534; + font-weight: 700; + text-decoration: none; +} + +.geMenubar { + padding: 0px 2px 0px 2px; + vertical-align: middle; +} + +.geItem, +.geToolbar .geItem { + padding: 6px 8px 6px 8px; + cursor: default; +} + +.geItem:hover { + background: #eeeeee; +} + +.mxDisabled:hover { + background: inherit !important; +} + +.geMenubar a.geStatus { + color: #b3b3b3; + padding-left: 6px; + display: inline-block; + cursor: default !important; +} + +.geMenubar a.geStatus:hover { + background: transparent; +} + +.geMenubarMenu { + border: 1px solid #d5d5d5 !important; +} + +.geToolbarContainer { + background: #fff!important; + border-bottom: 1px solid #e0e0e0; +} + +.geSidebarContainer .geToolbarContainer { + background: transparent; + border-bottom: none; +} + +.geSidebarContainer button { + text-overflow: ellipsis; + overflow: hidden; +} + +.geToolbar { + padding: 1px 0px 0px 6px; + border-top: 1px solid #e0e0e0; + -webkit-box-shadow: inset 0 1px 0 0 #fff; + -moz-box-shadow: inset 0 1px 0 0 #fff; + box-shadow: inset 0 1px 0 0 #fff; +} + +.geToolbarContainer .geSeparator { + float: left; + width: 1px; + height: 40px; + background: #e5e5e5; + margin-left: 6px; + margin-right: 6px; + margin-top: -2px; +} + +.geToolbarContainer .geButton { + float: left; + width: 20px; + height: 20px; + padding: 5px 0px 3px 6px; + margin: 2px; + border: 1px solid transparent; + cursor: pointer; + opacity: 0.6; + filter: alpha(opacity=60); +} + +.geToolbarContainer .geButton:hover { + border-radius: 2px; + opacity: 1; + filter: none !important; +} + +.geToolbarContainer .geButton:active { + border: 1px solid black; +} + +div.mxWindow .geButton { + margin: -1px 2px 2px 2px; + padding: 1px 2px 2px 1px; +} + +.geToolbarContainer .geLabel { + float: left; + cursor: pointer; + // padding:7px 5px 3px 6px; + padding: 2px 5px 0px 6px; + border: 1px solid transparent; + opacity: 0.6; + filter: alpha(opacity=60); + margin: 2px; + margin-top: 3px; +} + +.geToolbarContainer .geLabel:hover { + border: 1px solid gray; + border-radius: 2px; + opacity: 0.9; + filter: alpha(opacity=90) !important; +} + +.geToolbarContainer .geLabel:active { + border: 1px solid black; + opacity: 1; + filter: none !important; +} + +.geToolbarContainer .mxDisabled:hover { + border: 1px solid transparent !important; + opacity: 0.2 !important; + filter: alpha(opacity=20) !important; +} + +.geToolbarMenu { + border: 1px solid #DFDFDF !important; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: #DFDFDF 5px 5px 10px 1px !important; + filter: none !important; +} + +.geDiagramBackdrop { + background-color: #ebebeb; +} + +.geSidebarContainer { + background: #ffffff; + overflow: hidden; + position: absolute; + border-top: 1px solid #e5e5e5; + overflow: auto; +} +input[type=checkbox] { + position: relative; + width: 12px; + height: 12px; + -webkit-apprarence: none; +} +input[type=checkbox]::before{ + content: ''; + position: absolute; + top: -11px; + width: 12px; + height: 0; + text-align: center; + font-size: 16px; + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMi4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i5Zu+5b2iIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDg1MC40IDg1MC40IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA4NTAuNCA4NTAuNDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCgkuc3Qwe2ZpbGw6I0IwQkFDNTt9DQo8L3N0eWxlPg0KPGc+DQoJPHBhdGggY2xhc3M9InN0MCIgZD0iTTgwOC40LDg1MC40SDQyYy0yMy4yLDAtNDItMTguOC00Mi00MlY0MkMwLDE4LjgsMTguOCwwLDQyLDBoNzY2LjRjMjMuMiwwLDQyLDE4LjgsNDIsNDJ2NzY2LjQNCgkJQzg1MC40LDgzMS42LDgzMS42LDg1MC40LDgwOC40LDg1MC40eiBNNjAsNzkwLjRoNzMwLjRWNjBINjBWNzkwLjR6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==); + border-radius: 2px; + background-repeat: no-repeat; + overflow: hidden; +} +input[type=checkbox]:checked::before { + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMi4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i5Zu+5b2iIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDg1MC40IDg1MC40IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA4NTAuNCA4NTAuNDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCgkuc3Qwe2ZpbGw6IzM1ODJGQjt9DQo8L3N0eWxlPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTc5My43LDBoLTczN0MyNS41LDAsMCwyNS41LDAsNTYuN3Y3MzdjMCwzMS4yLDI1LjUsNTYuNyw1Ni43LDU2LjdoNzM3YzMxLjIsMCw1Ni43LTI1LjUsNTYuNy01Ni43di03MzcNCglDODUwLjQsMjUuNSw4MjQuOSwwLDc5My43LDB6IE03MDAsMjgzLjZMMzc3LjQsNjA5Yy01LjYsNS43LTEzLjMsOC45LTIxLjMsOC45cy0xNS43LTMuMi0yMS4zLTguOWwtMTg0LjMtMTg2DQoJYy0xMS43LTExLjgtMTEuNi0zMC44LDAuMi00Mi40YzExLjgtMTEuNywzMC44LTExLjYsNDIuNCwwLjJsMTYzLDE2NC41bDMwMS4zLTMwMy45YzExLjctMTEuOCwzMC43LTExLjksNDIuNC0wLjINCglDNzExLjUsMjUyLjgsNzExLjYsMjcxLjgsNzAwLDI4My42eiIvPg0KPC9zdmc+DQo=); + background-repeat: no-repeat; +} + +input[type=radio] { + position: relative; + width: 10px; + height: 0px; +} +input[type=radio]::before{ + content: ''; + position: absolute; + top: -9px; + width: 14px; + height: 14px; + text-align: center; + font-size: 16px; + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMi4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i5Zu+5b2iIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iOTAuMjg4IDU4LjgwOSA4NTAuMzk0IDg1MC4zOTQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgOTAuMjg4IDU4LjgwOSA4NTAuMzk0IDg1MC4zOTQiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZmlsbD0iI0IwQkFDNSIgZD0iTTUxNS40ODUsOTA3LjA4NmMtNTcuMzY4LDAtMTEzLjAzOC0xMS4xODMtMTY1LjQ2Mi0zMy4yMzdjLTUwLjYzMS0yMS4zMDEtOTYuMDk5LTUxLjc5MS0xMzUuMTQxLTkwLjYyMw0KCQljLTM5LjA1My0zOC44NDQtNjkuNzE4LTg0LjA4OC05MS4xNDYtMTM0LjQ3NmMtMjIuMTk1LTUyLjE5NC0zMy40NDktMTA3LjYyMi0zMy40NDktMTY0Ljc0NQ0KCQljMC01Ny4xMjIsMTEuMjU0LTExMi41NSwzMy40NDktMTY0Ljc0NGMyMS40MjctNTAuMzg4LDUyLjA5My05NS42MzIsOTEuMTQ2LTEzNC40NzZjMzkuMDQyLTM4LjgzMiw4NC41MDktNjkuMzIyLDEzNS4xNDEtOTAuNjIzDQoJCWM1Mi40MjQtMjIuMDU1LDEwOC4wOTQtMzMuMjM4LDE2NS40NjItMzMuMjM4czExMy4wMzgsMTEuMTgzLDE2NS40NjIsMzMuMjM4YzUwLjYzMiwyMS4zLDk2LjEsNTEuNzkxLDEzNS4xNDEsOTAuNjIzDQoJCWMzOS4wNTMsMzguODQzLDY5LjcxOSw4NC4wODgsOTEuMTQ2LDEzNC40NzZjMjIuMTk1LDUyLjE5NCwzMy40NDksMTA3LjYyMiwzMy40NDksMTY0Ljc0NGMwLDU3LjEyMy0xMS4yNTQsMTEyLjU1LTMzLjQ0OSwxNjQuNzQ1DQoJCWMtMjEuNDI3LDUwLjM4OC01Mi4wOTMsOTUuNjMyLTkxLjE0NiwxMzQuNDc2Yy0zOS4wNDEsMzguODMyLTg0LjUwOSw2OS4zMjItMTM1LjE0MSw5MC42MjMNCgkJQzYyOC41MjMsODk1LjkwNCw1NzIuODUzLDkwNy4wODYsNTE1LjQ4NSw5MDcuMDg2eiBNNTE1LjQ4NSwxMjAuOTI2Yy0yMDEuMzcsMC0zNjUuMTk3LDE2Mi44NzctMzY1LjE5NywzNjMuMDgNCgkJYzAsMjAwLjIwNCwxNjMuODI3LDM2My4wODEsMzY1LjE5NywzNjMuMDgxczM2NS4xOTctMTYyLjg3NywzNjUuMTk3LTM2My4wODFDODgwLjY4MiwyODMuODAzLDcxNi44NTUsMTIwLjkyNiw1MTUuNDg1LDEyMC45MjZ6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==); + border-radius: 2px; + background-repeat: no-repeat; +} +input[type=radio]:checked::before { + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMi4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i5Zu+5b2iIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iOTAuMjg4IDU4LjgwOSA4NTAuMzk0IDg1MC4zOTQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgOTAuMjg4IDU4LjgwOSA4NTAuMzk0IDg1MC4zOTQiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZmlsbD0iIzM1ODJGQiIgZD0iTTUxNS40ODUsNTguODA5Yy0yMzQuODMsMC00MjUuMTk3LDE5MC4zNjctNDI1LjE5Nyw0MjUuMTk3czE5MC4zNjcsNDI1LjE5Nyw0MjUuMTk3LDQyNS4xOTcNCglzNDI1LjE5Ny0xOTAuMzY3LDQyNS4xOTctNDI1LjE5N1M3NTAuMzE1LDU4LjgwOSw1MTUuNDg1LDU4LjgwOXogTTUxNS40ODUsNjgyLjQzMWMtMTA5LjU4NywwLTE5OC40MjUtODguODM4LTE5OC40MjUtMTk4LjQyNQ0KCXM4OC44MzgtMTk4LjQyNSwxOTguNDI1LTE5OC40MjVTNzEzLjkxLDM3NC40MTksNzEzLjkxLDQ4NC4wMDZTNjI1LjA3Miw2ODIuNDMxLDUxNS40ODUsNjgyLjQzMXoiLz4NCjwvc3ZnPg0K); + background-repeat: no-repeat; +} +input[type=radio]:hover::before { + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMi4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i5Zu+5b2iIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iOTAuMjg4IDU4LjgwOSA4NTAuMzk0IDg1MC4zOTQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgOTAuMjg4IDU4LjgwOSA4NTAuMzk0IDg1MC4zOTQiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZmlsbD0iIzM1ODJGQiIgZD0iTTUxNS40ODUsOTA3LjA4NmMtNTcuMzY4LDAtMTEzLjAzOC0xMS4xODMtMTY1LjQ2Mi0zMy4yMzdjLTUwLjYzMS0yMS4zMDEtOTYuMDk5LTUxLjc5MS0xMzUuMTQxLTkwLjYyMw0KCQljLTM5LjA1My0zOC44NDQtNjkuNzE4LTg0LjA4OC05MS4xNDYtMTM0LjQ3NmMtMjIuMTk1LTUyLjE5NC0zMy40NDktMTA3LjYyMi0zMy40NDktMTY0Ljc0NQ0KCQljMC01Ny4xMjIsMTEuMjU0LTExMi41NSwzMy40NDktMTY0Ljc0NGMyMS40MjctNTAuMzg4LDUyLjA5My05NS42MzIsOTEuMTQ2LTEzNC40NzZjMzkuMDQyLTM4LjgzMiw4NC41MDktNjkuMzIyLDEzNS4xNDEtOTAuNjIzDQoJCWM1Mi40MjQtMjIuMDU1LDEwOC4wOTQtMzMuMjM4LDE2NS40NjItMzMuMjM4czExMy4wMzgsMTEuMTgzLDE2NS40NjIsMzMuMjM4YzUwLjYzMiwyMS4zLDk2LjEsNTEuNzkxLDEzNS4xNDEsOTAuNjIzDQoJCWMzOS4wNTMsMzguODQzLDY5LjcxOSw4NC4wODgsOTEuMTQ2LDEzNC40NzZjMjIuMTk1LDUyLjE5NCwzMy40NDksMTA3LjYyMiwzMy40NDksMTY0Ljc0NGMwLDU3LjEyMy0xMS4yNTQsMTEyLjU1LTMzLjQ0OSwxNjQuNzQ1DQoJCWMtMjEuNDI3LDUwLjM4OC01Mi4wOTMsOTUuNjMyLTkxLjE0NiwxMzQuNDc2Yy0zOS4wNDEsMzguODMyLTg0LjUwOSw2OS4zMjItMTM1LjE0MSw5MC42MjMNCgkJQzYyOC41MjMsODk1LjkwNCw1NzIuODUzLDkwNy4wODYsNTE1LjQ4NSw5MDcuMDg2eiBNNTE1LjQ4NSwxMjAuOTI2Yy0yMDEuMzcsMC0zNjUuMTk3LDE2Mi44NzctMzY1LjE5NywzNjMuMDgNCgkJYzAsMjAwLjIwNCwxNjMuODI3LDM2My4wODEsMzY1LjE5NywzNjMuMDgxczM2NS4xOTctMTYyLjg3NywzNjUuMTk3LTM2My4wODFDODgwLjY4MiwyODMuODAzLDcxNi44NTUsMTIwLjkyNiw1MTUuNDg1LDEyMC45MjZ6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==); + background-repeat: no-repeat; +} +input[type=radio]:checked:hover::before { + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMi4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i5Zu+5b2iIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iOTAuMjg4IDU4LjgwOSA4NTAuMzk0IDg1MC4zOTQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgOTAuMjg4IDU4LjgwOSA4NTAuMzk0IDg1MC4zOTQiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZmlsbD0iIzM1ODJGQiIgZD0iTTUxNS40ODUsNTguODA5Yy0yMzQuODMsMC00MjUuMTk3LDE5MC4zNjctNDI1LjE5Nyw0MjUuMTk3czE5MC4zNjcsNDI1LjE5Nyw0MjUuMTk3LDQyNS4xOTcNCglzNDI1LjE5Ny0xOTAuMzY3LDQyNS4xOTctNDI1LjE5N1M3NTAuMzE1LDU4LjgwOSw1MTUuNDg1LDU4LjgwOXogTTUxNS40ODUsNjgyLjQzMWMtMTA5LjU4NywwLTE5OC40MjUtODguODM4LTE5OC40MjUtMTk4LjQyNQ0KCXM4OC44MzgtMTk4LjQyNSwxOTguNDI1LTE5OC40MjVTNzEzLjkxLDM3NC40MTksNzEzLjkxLDQ4NC4wMDZTNjI1LjA3Miw2ODIuNDMxLDUxNS40ODUsNjgyLjQzMXoiLz4NCjwvc3ZnPg0K); + background-repeat: no-repeat; +} +.geSidebar { + background: #fff!important; + border-bottom: 1px solid #e5e5e5; + padding: 5px; + _padding: 1px; + padding-bottom: 12px; + overflow: hidden; +} + +.geSidebarContainer .geTitle { + display: block; + font-size: 9pt; + border-bottom: 1px solid #e5e5e5; + font-weight: normal; + padding: 6px 0px 6px 14px; + margin: 0px; + cursor: default; + background: #eeeeee; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.4em; + text-indent: 18px; + background-position: 6% 50%!important; + color:#182b50; +} +div[style="position: relative; cursor: pointer; margin-top: -3px; border: 0px; left: 52px; opacity: 0.5;"]{ + margin-top: -1px; +} +.geSidebarContainer .geTitle:hover { + background: #e5e5e5; +} + +.geTitle img { + opacity: 0.5; + _filter: alpha(opacity=50); +} + +.geTitle img:hover { + opacity: 1; + _filter: alpha(opacity=100); +} + +.geTitle .geButton { + border: 1px solid transparent; + padding: 3px; + border-radius: 2px; +} + +.geTitle .geButton:hover { + border: 1px solid gray; +} + +.geSidebar .geItem { + display: inline-block; + background-repeat: no-repeat; + background-position: 50% 50%; + border: 1px solid transparent; + border-radius: 2px; + cursor: move; +} + +.geSidebar .geItem:hover { + border: 1px solid gray !important; +} + +.geItem { + vertical-align: top; + display: inline-block; +} + +.geSidebarTooltip { + position: absolute; + background: white; + overflow: hidden; + border: 1px solid gray; + border-radius: 8px; + -webkit-box-shadow: 0px 0px 2px 2px #d5d5d5; + -moz-box-shadow: 0px 0px 2px 2px #d5d5d5; + box-shadow: 0px 0px 2px 2px #d5d5d5; + _filter: progid:DXImageTransform.Microsoft.DropShadow(OffX=2, OffY=2, Color='#d5d5d5', Positive='true'); +} + +.geFooterContainer { + background: #e5e5e5; + border-top: 1px solid #c0c0c0; +} + +.geFooterContainer a { + display: inline-block; + box-sizing: border-box; + width: 100%; + white-space: nowrap; + font-size: 14px; + color: #235695; + font-weight: bold; + text-decoration: none; +} + +.geFooterContainer table { + border-collapse: collapse; + margin: 0 auto; +} + +.geFooterContainer td { + border-left: 1px solid #c0c0c0; + border-right: 1px solid #c0c0c0; +} + +.geFooterContainer td:hover { + background-color: #b3b3b3; +} + +.geHsplit { + cursor: col-resize; + background-color: #e8eaee; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAHBAMAAADdS/HjAAAAGFBMVEUzMzP///9tbW1QUFCKiopBQUF8fHxfX1/IXlmXAAAAHUlEQVQImWMQEGAQFWUQFmYQF2cQEmIQE2MQEQEACy4BF67hpEwAAAAASUVORK5CYII=); + background-repeat: no-repeat; + background-position: center center; +} + +.geVsplit { + font-size: 1pt; + cursor: row-resize; + background-color: #e5e5e5; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAEBAMAAACw6DhOAAAAGFBMVEUzMzP///9tbW1QUFCKiopBQUF8fHxfX1/IXlmXAAAAFElEQVQImWNgNVdzYBAUFBRggLMAEzYBy29kEPgAAAAASUVORK5CYII=); + background-repeat: no-repeat; + background-position: center center; +} + +.geHsplit:hover, +.geVsplit:hover { + background-color: #d5d5d5; +} + +.geDialog { + position: absolute; + background: white; + line-height: 1em; + overflow: hidden; + padding: 30px; + border: 1px solid #acacac; + -webkit-box-shadow: 0px 0px 2px 2px #d5d5d5; + -moz-box-shadow: 0px 0px 2px 2px #d5d5d5; + box-shadow: 0px 0px 2px 2px #d5d5d5; + _filter: progid:DXImageTransform.Microsoft.DropShadow(OffX=2, OffY=2, Color='#d5d5d5', Positive='true'); + z-index: 2; +} + +.geDialogClose { + position: absolute; + width: 9px; + height: 9px; + opacity: 0.5; + cursor: pointer; + _filter: alpha(opacity=50); +} + +.geDialogClose:hover { + opacity: 1; +} + +.geDialogTitle { + box-sizing: border-box; + white-space: nowrap; + background: rgb(229, 229, 229); + border-bottom: 1px solid rgb(192, 192, 192); + font-size: 15px; + font-weight: bold; + text-align: center; + color: rgb(35, 86, 149); +} + +.geDialogFooter { + background: whiteSmoke; + white-space: nowrap; + text-align: right; + box-sizing: border-box; + border-top: 1px solid #e5e5e5; + color: darkGray; +} + +.geBaseButton { + padding: 10px; + border-radius: 6px; + border: 1px solid #c0c0c0; + cursor: pointer; + background-color: #ececec; + background-image: linear-gradient(#ececec 0%, #fcfcfc 100%); +} + +.geBaseButton:hover { + background: #ececec; +} + +.geBigButton { + color: #ffffff; + border: none; + padding: 10px; + font-size: 14pt; + white-space: nowrap; + border-radius: 6px; + text-shadow: rgb(41, 89, 137) 0px 1px 0px; + background-color: #428bca; + background-image: linear-gradient(rgb(70, 135, 206) 0px, rgb(48, 104, 162) 100%); + -webkit-box-shadow: rgba(255, 255, 255, 0.0980392) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.2) 0px 1px 1px 0px; + -moz-box-shadow: rgba(255, 255, 255, 0.0980392) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.2) 0px 1px 1px 0px; + box-shadow: rgba(255, 255, 255, 0.0980392) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.2) 0px 1px 1px 0px; +} + +.geBigButton:hover { + background-color: #2d6ca2; + background-image: linear-gradient(rgb(90, 148, 211) 0px, rgb(54, 115, 181) 100%); +} + +.geBigButton:active { + background-color: rgb(54, 115, 181); + background-image: none; +} + +@media print { + div.geNoPrint { + display: none !important; + } +} + +.font-graph-geSprite-actualsize { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 0; +} + +.font-graph-geSprite-fillcolor { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -229px; + margin-top: -3px; +} + +.font-graph-geSprite-gradientcolor { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -368px; +} + +.font-graph-geSprite-image { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -414px; +} + +.font-graph-geSprite-print { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -598px; +} + +.font-graph-geSprite-noarrow { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1334px; +} + +.font-graph-geSprite-endclassic { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1380px; +} + +.font-graph-geSprite-endopen { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1426px; +} + +.font-graph-geSprite-endblock { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1472px; +} + +.font-graph-geSprite-endoval { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1518px; +} + +.font-graph-geSprite-enddiamond { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1564px; +} + +.font-graph-geSprite-endthindiamond { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1610px; +} + +.font-graph-geSprite-endclassictrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1656px; +} + +.font-graph-geSprite-endblocktrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1702px; +} + +.font-graph-geSprite-endovaltrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1748px; +} + +.font-graph-geSprite-enddiamondtrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1794px; +} + +.font-graph-geSprite-endthindiamondtrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1840px; +} + +.font-graph-geSprite-startclassic { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1886px; +} + +.font-graph-geSprite-startopen { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1932px; +} + +.font-graph-geSprite-startblock { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -1978px; +} + +.font-graph-geSprite-startoval { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2024px; +} + +.font-graph-geSprite-startdiamond { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2070px; +} + +.font-graph-geSprite-startthindiamond { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2116px; +} + +.font-graph-geSprite-startclassictrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2162px; +} + +.font-graph-geSprite-startblocktrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2208px; +} + +.font-graph-geSprite-startovaltrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2254px; +} + +.font-graph-geSprite-startdiamondtrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2300px; +} + +.font-graph-geSprite-startthindiamondtrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2346px; +} + +.font-graph-geSprite-globe { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2392px; +} + +.font-graph-geSprite-deletecolumn { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -2990px; +} + +.font-graph-geSprite-deleterow { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3036px; +} + +.font-graph-geSprite-insertcolumnafter { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3082px; +} + +.font-graph-geSprite-insertcolumnbefore { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3128px; +} + +.font-graph-geSprite-insertrowafter { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3174px; +} + +.font-graph-geSprite-insertrowbefore { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3220px; +} + +.font-graph-geSprite-grid { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3272px; +} + +.font-graph-geSprite-guides { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3324px; +} + +.font-graph-geSprite-alignleft { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3416px; +} + +.font-graph-geSprite-alignright { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3462px; +} + +.font-graph-geSprite-aligncenter { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3508px; +} +.font-graph-geSprite-aligntop { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3554px; +} + +.font-graph-geSprite-alignbottom { + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAABeQCAMAAAByzXKPAAAA1VBMVEUAAAAzMzIzMzIzMzE1NTUzMzIzMzIzMzEzMzIyMjIzMzIzMzIzMzIzMzE6OjoyMjIzMzIzMzIzMzEzMzI0NDA0NDIwMDAyMjEzMzIzMzEzMzE0NDAzMzMzMzIyMjIzMzI0NDE0NDQyMjIzMzIzMzIzMzIzMzM5OTkAAAC1tbX///9mZmYwMDAQEBAmJiZTiPJ7e3v/Z2RwcHAgICB+fn5hYWFzc3NAQECoqKiampqMjIxTU1NGRkY3NzcbGxsNDQ3w8PBQUFDg4ODAwMCwsLCgoKBPT09W+xp5AAAAKHRSTlMAh4DDEd187+mljnhvVge5nmhMQi4hA8uxk2IlHOOYc102M/jW088J/2liTQAACt5JREFUeNrs3O1um0AQheG5nIPCoIq1Q7+r/Mn9X1K9xtEAM9RLQmODzyO1cV+NtzjShhjHkRkAgn9DMCaYnRXIGOQdtWmk1zRWm0vNN6az+Y+v2fXZ5iTX/kZ4ZGvUNT6/RJuCM/VVRKFB7YBwtnY1k3h2wboaHy8RFQC2VGuc1D730WWLS2GIZ27aM4yUT+9tXaLNw9m0mI9uokepRLeC+ZyCfFCoqwckqV3WBHFZaxHLFrNxVpgUVRWjg/jli2WbfH4WyxYnWcVV0Rz7FYj26X9ezQcuE3VYkXzFSYpm9eq6pqSiuNYAaj+r0GiFxKv5tEOqxVfV0utrCkZF/HCXTuOdGz2IHKbDR5wdJ6PdeRmIsfviED0C5ZV7ojf487v59fPHsT18//a1Yn1HJdoSjIUXBG5XgaBejhMYVmT9B1dRUONj4KtyRHNmNkULDYeD2CGVjiZ0paOK1jVuYKIpjJTO7m9dovvEn3bz/DH4440f2+d9fvlVh+4LLuK6cM8GVcKKkmq3ZqqBfbhaef4n+pijBudNZH3H+OW3No+rbTmVXjfciPbs/GVQ7QU6tWqUW5lonefHfn5f6xI9DAxdqQU3n55uU821mj2ZuUq0SYo2ivA5tThxk3rK6u+urYtZiqKuGiWMEkbp4/OFLITIvyrfekt35WGvjvP6OlERwP6+VU39z+ansp+0SThP8jk+0SeoAehRjObmdha0f2YwfucMss7uaPVF3rThN/EH6fH3YhEt3bD1zMlwGtu8/8a5zrHJuR6NyrkKMK5N5qqf9ev6Y4hfGfSPrc7ZWZqPQrQPa+x5Cfe8FO55m3Xrcs8T3TFUYQ0zqj5jTPoMmagQz4brFh8D3x5L9OngyRy+IEa0JgBRlaBWAIJYyVYjYDF47PwNV/TwAIRfB+BiFcyeolchija6+P7xd//IveI+JlpwnrdZP7xexdD6/4N/PDzP06NZdc8vP4H7WKFXLbqjQcU9T7QZvFhHdN/+sndmy6lCQRQ9KOA8Ro1TxjttYuUlX5BK5f+/6YJIWjlNFREkpLJXGQuWO01e2mPaU4pA17qH7iEeKfRsrrqh4/t0hJQPEJSokULPFpJse0Iu0PNQNVSNnOu8ZHPWZc8TUhkBgECRikZMrp4Xq9W1NPubkIIUm4hnrtyikSIjq+jck3bOBQkpnSBrkU97ALl73pJqXfFc5AlJqN3cXvoTEKIzJcu5PSEFqHiGp6ahz+33Z3rWtpzhEfK16DO8XXi3S2vIvfUCHnpWrcsZHiHVAFUG0KQJoEgjGjGRFG1l9bq25gyPkIoANBcEab9DEPf27iCk40VbWa0uP86WkMsTQHPQHBSnJJHCytp1dW9Uz2cBQoo0PEqVes/r2bM0131CLtLzUCVUidw9n6uuaPY8IdnUYMet2BTccUtIfShnz60mBe65JaTunL/tVqTAbbeE1ImCc3vl16McIEiWc3tCClD5DM9Ak7ZFZCBkZEVzhkfI5/n6Hbdp+wF33BJSH8rZc6vISB/gnltCas/Z225FStdz2y0hZXE19lrt5p177NyR11+OHb/THhzJP86wP2uYrjvz1h92eTseNEzDbB2nd/OY1Py9WNw6/qjnN+fmvnmwnYkxjf1t+mAW7XlsbzaJ3a7DzH1sf3Udp7m/dcOf615sW26SdfvGrCaxbV4l9nEan0X0xqEaRrbvmnlrGFu3PTN3ndUoLOuapW8ODLzudLVomMHA71z/MwmT9mTmN+bOZnS9NcJDs+V53t+WPzQnbNa9/nRoCPl2AKqObKFvltEBoPvcVwNwmavxOy3IDwFAlkWCWPBqhJDC8GtsCPlGYI8ciQyRI+3/CLHHscysXvf0ynzWIOQsPr3wWllkxNQskD+b82/Ihi8qCCm150XpObXnc2RFs+cJqRhAE5AHpI8BOZbH5TQdlXB8JAUEIC4AvkFPSMEl/dQ+v74+2/bl6enFtm/v72+W/c/eHSW3CUNRGNZyjgiZNHE6fW2b6f63VGScCHSvI7CxjfH/tYnTU43CywFiAfnT/On+lunH3274R5G2zbv03rTj9F+z92+U7pqPX52PZjdM35uf6vxs3ofp799Kulf2B8CEc2JVjvJm6OIT5CO9PekvD/8T767XgTc2z1umnEdggyT5eX2s+k9yGpvH1kqvI523IVfSAzdlW2gbu3zn5+6j/JFcfIft0lBOi4/J6cbmBTZJTdPo9fV1/3pamqTUFCalOVkunVdNTU5bSa2Nn7ULjl0A7o5/aGt6Z6TKUpVC7/VLSrWzqTo7b+yzO+9i28shHtugl5cXXS9NLnyYHVZ+Nz79UG7y4in7Aqza9po+tBsXP1B8wCW3m01yVqq9G3w3q/X+/1lpZ5WEbKdOTnNsxiYtd+ngjl28Fw+zhwGwLA0/mDeIS46AxWnO2MVnUFD627+sasuAxyJpTsp3A+6WgnplGpILpL1JR21lZt5k2ZSrFPE41AteEy+Q9qrn58qW77lNM877sXXq+fcGLp/2giETZO5tTYumHObxKDQ0aezhxb2feNE0MWlvqZS1SzwsJZdslu3x9fYae8HFXYeAKcIVejxwzgwc0YE6jTzuWOAuqOS+GTY3rc+rmDxAKn7VEPAt+amm/7Yu8ev0gfnkHckljT8nSoaf/RkeNgUejaKburGiYt696FNIcXrt/3yJh8Qba+advg0BwJCk66Qamp3WvxtX1gMzSDZya+BU8y2OR+ogyk7w5h+Nox+/6S04pNunwKqpt9Db9yemA2GokjqThGR+ms1JeYMe92hu5y2Zbs5O5be7mkru2Hlpgc5j434M1NPiq0qqgXoaDkwqTU21V0s7dgY/TfLX9XMT1udx56RJqTqf5aqlR3vVqtOWaYpbp4NtW2tmPaXbwAZI0U1zroI/tiRW9oBVOtL5QT6uu2TH0nlgnXRSqsLkGXg+JnBTCjopVeGceXk2FnAtColkUynGMn1SrHXejq3Py4U5wHXZvtluJtXO27H+vFyMB9xElFIxNQ6fUmFj9Xm6Sr67SrA6b94Gfp4HLsb2rSM53VSMpseK1c7bsXbechs4zgO3EiUvs6kK7tgsUmlglaLkZiZVStzOBzoPrJHOS1VgVR5YuTPXz1VgXR5YM/+u9xglkyZRT2WqghlbnVc8CA+4mtxRp5vB7XxW7bw3LwvywI0pr5MN6HNNrUi/fUZO4o6tzxuiuO0GuLzUUdu3GOV3M6raeTu2Nm8aR+eBlVD1BnoVjowt0HBgnY51PrqdT/9yxtJ5YHWkeGKswoxJuMcGuIYoKdowKOQ4Xxov+4Zb/khU8Mf681a3gZ/0gYt1PjGdV1J2/mBS5z/58zrbQOeBE6zpGZgA7smRzgc6D2xKlORlCiUVKrfSslMAVulI56PX+XHv81g6D6yPFM+MVWCJDliLJdfoMhVYowPWYsk1ukwF1uiAu1R/Hh5v2wNb8r+9O0pBGAaiKLqdm/1vUCiFSCdhTIjWhntAwefY5sMxRW1ToJnZ89KjQTnuGzGUWB1DLkJtul0ofocn3SOf5wMu3mu97q30GN2e99he2gKEpj6Dkvwjj4tebb5d8EBAuhuUJJ87f96f4aT/1OlNz7GRfgg+WheCUFsfE16cpEFNx5exIUmHx09zd34AaRdACCDp+TVLSlFv1blzgKR2egx5zx/see0pn+djbR4FIVofz4/UeV67G3+vr3niC+H04Oz/nbwA7lqtm+wByfQAAAAASUVORK5CYII=') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3600px; +} + +.font-graph-geSprite-alignmiddle { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3646px; +} + + +.font-graph-geSprite-rounded { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3968px; +} + +.font-graph-geSprite-duplicate { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4106px; +} + +.font-graph-geSprite-insert { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4152px; +} + +.font-graph-geSprite-endblockthin { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4201px; +} + +.font-graph-geSprite-endblockthintrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4247px; +} + +.font-graph-geSprite-enderone { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4293px; +} + +.font-graph-geSprite-enderonetoone { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4339px; +} + +.font-graph-geSprite-enderonetomany { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4385px; +} + +.font-graph-geSprite-endermany { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4431px; +} + +.font-graph-geSprite-enderoneopt { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4477px; +} + +.font-graph-geSprite-endermanyopt { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4523px; +} + +.font-graph-geSprite-endclassicthin { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4938px; +} + +.font-graph-geSprite-endclassicthintrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4984px; +} + +.font-graph-geSprite-enddash { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5029px; +} + +.font-graph-geSprite-endcircleplus { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5075px; +} + +.font-graph-geSprite-endcircle { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5121px; +} + +.font-graph-geSprite-endasync { + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAABeQCAMAAAByzXKPAAAA1VBMVEUAAAAzMzIzMzIzMzE1NTUzMzIzMzIzMzEzMzIyMjIzMzIzMzIzMzIzMzE6OjoyMjIzMzIzMzIzMzEzMzI0NDA0NDIwMDAyMjEzMzIzMzEzMzE0NDAzMzMzMzIyMjIzMzI0NDE0NDQyMjIzMzIzMzIzMzIzMzM5OTkAAAC1tbX///9mZmYwMDAQEBAmJiZTiPJ7e3v/Z2RwcHAgICB+fn5hYWFzc3NAQECoqKiampqMjIxTU1NGRkY3NzcbGxsNDQ3w8PBQUFDg4ODAwMCwsLCgoKBPT09W+xp5AAAAKHRSTlMAh4DDEd187+mljnhvVge5nmhMQi4hA8uxk2IlHOOYc102M/jW088J/2liTQAACt5JREFUeNrs3O1um0AQheG5nIPCoIq1Q7+r/Mn9X1K9xtEAM9RLQmODzyO1cV+NtzjShhjHkRkAgn9DMCaYnRXIGOQdtWmk1zRWm0vNN6az+Y+v2fXZ5iTX/kZ4ZGvUNT6/RJuCM/VVRKFB7YBwtnY1k3h2wboaHy8RFQC2VGuc1D730WWLS2GIZ27aM4yUT+9tXaLNw9m0mI9uokepRLeC+ZyCfFCoqwckqV3WBHFZaxHLFrNxVpgUVRWjg/jli2WbfH4WyxYnWcVV0Rz7FYj26X9ezQcuE3VYkXzFSYpm9eq6pqSiuNYAaj+r0GiFxKv5tEOqxVfV0utrCkZF/HCXTuOdGz2IHKbDR5wdJ6PdeRmIsfviED0C5ZV7ojf487v59fPHsT18//a1Yn1HJdoSjIUXBG5XgaBejhMYVmT9B1dRUONj4KtyRHNmNkULDYeD2CGVjiZ0paOK1jVuYKIpjJTO7m9dovvEn3bz/DH4440f2+d9fvlVh+4LLuK6cM8GVcKKkmq3ZqqBfbhaef4n+pijBudNZH3H+OW3No+rbTmVXjfciPbs/GVQ7QU6tWqUW5lonefHfn5f6xI9DAxdqQU3n55uU821mj2ZuUq0SYo2ivA5tThxk3rK6u+urYtZiqKuGiWMEkbp4/OFLITIvyrfekt35WGvjvP6OlERwP6+VU39z+ansp+0SThP8jk+0SeoAehRjObmdha0f2YwfucMss7uaPVF3rThN/EH6fH3YhEt3bD1zMlwGtu8/8a5zrHJuR6NyrkKMK5N5qqf9ev6Y4hfGfSPrc7ZWZqPQrQPa+x5Cfe8FO55m3Xrcs8T3TFUYQ0zqj5jTPoMmagQz4brFh8D3x5L9OngyRy+IEa0JgBRlaBWAIJYyVYjYDF47PwNV/TwAIRfB+BiFcyeolchija6+P7xd//IveI+JlpwnrdZP7xexdD6/4N/PDzP06NZdc8vP4H7WKFXLbqjQcU9T7QZvFhHdN/+sndmy6lCQRQ9KOA8Ro1TxjttYuUlX5BK5f+/6YJIWjlNFREkpLJXGQuWO01e2mPaU4pA17qH7iEeKfRsrrqh4/t0hJQPEJSokULPFpJse0Iu0PNQNVSNnOu8ZHPWZc8TUhkBgECRikZMrp4Xq9W1NPubkIIUm4hnrtyikSIjq+jck3bOBQkpnSBrkU97ALl73pJqXfFc5AlJqN3cXvoTEKIzJcu5PSEFqHiGp6ahz+33Z3rWtpzhEfK16DO8XXi3S2vIvfUCHnpWrcsZHiHVAFUG0KQJoEgjGjGRFG1l9bq25gyPkIoANBcEab9DEPf27iCk40VbWa0uP86WkMsTQHPQHBSnJJHCytp1dW9Uz2cBQoo0PEqVes/r2bM0131CLtLzUCVUidw9n6uuaPY8IdnUYMet2BTccUtIfShnz60mBe65JaTunL/tVqTAbbeE1ImCc3vl16McIEiWc3tCClD5DM9Ak7ZFZCBkZEVzhkfI5/n6Hbdp+wF33BJSH8rZc6vISB/gnltCas/Z225FStdz2y0hZXE19lrt5p177NyR11+OHb/THhzJP86wP2uYrjvz1h92eTseNEzDbB2nd/OY1Py9WNw6/qjnN+fmvnmwnYkxjf1t+mAW7XlsbzaJ3a7DzH1sf3Udp7m/dcOf615sW26SdfvGrCaxbV4l9nEan0X0xqEaRrbvmnlrGFu3PTN3ndUoLOuapW8ODLzudLVomMHA71z/MwmT9mTmN+bOZnS9NcJDs+V53t+WPzQnbNa9/nRoCPl2AKqObKFvltEBoPvcVwNwmavxOy3IDwFAlkWCWPBqhJDC8GtsCPlGYI8ciQyRI+3/CLHHscysXvf0ynzWIOQsPr3wWllkxNQskD+b82/Ihi8qCCm150XpObXnc2RFs+cJqRhAE5AHpI8BOZbH5TQdlXB8JAUEIC4AvkFPSMEl/dQ+v74+2/bl6enFtm/v72+W/c/eHSW3CUNRGNZyjgiZNHE6fW2b6f63VGScCHSvI7CxjfH/tYnTU43CywFiAfnT/On+lunH3274R5G2zbv03rTj9F+z92+U7pqPX52PZjdM35uf6vxs3ofp799Kulf2B8CEc2JVjvJm6OIT5CO9PekvD/8T767XgTc2z1umnEdggyT5eX2s+k9yGpvH1kqvI523IVfSAzdlW2gbu3zn5+6j/JFcfIft0lBOi4/J6cbmBTZJTdPo9fV1/3pamqTUFCalOVkunVdNTU5bSa2Nn7ULjl0A7o5/aGt6Z6TKUpVC7/VLSrWzqTo7b+yzO+9i28shHtugl5cXXS9NLnyYHVZ+Nz79UG7y4in7Aqza9po+tBsXP1B8wCW3m01yVqq9G3w3q/X+/1lpZ5WEbKdOTnNsxiYtd+ngjl28Fw+zhwGwLA0/mDeIS46AxWnO2MVnUFD627+sasuAxyJpTsp3A+6WgnplGpILpL1JR21lZt5k2ZSrFPE41AteEy+Q9qrn58qW77lNM877sXXq+fcGLp/2giETZO5tTYumHObxKDQ0aezhxb2feNE0MWlvqZS1SzwsJZdslu3x9fYae8HFXYeAKcIVejxwzgwc0YE6jTzuWOAuqOS+GTY3rc+rmDxAKn7VEPAt+amm/7Yu8ev0gfnkHckljT8nSoaf/RkeNgUejaKburGiYt696FNIcXrt/3yJh8Qba+advg0BwJCk66Qamp3WvxtX1gMzSDZya+BU8y2OR+ogyk7w5h+Nox+/6S04pNunwKqpt9Db9yemA2GokjqThGR+ms1JeYMe92hu5y2Zbs5O5be7mkru2Hlpgc5j434M1NPiq0qqgXoaDkwqTU21V0s7dgY/TfLX9XMT1udx56RJqTqf5aqlR3vVqtOWaYpbp4NtW2tmPaXbwAZI0U1zroI/tiRW9oBVOtL5QT6uu2TH0nlgnXRSqsLkGXg+JnBTCjopVeGceXk2FnAtColkUynGMn1SrHXejq3Py4U5wHXZvtluJtXO27H+vFyMB9xElFIxNQ6fUmFj9Xm6Sr67SrA6b94Gfp4HLsb2rSM53VSMpseK1c7bsXbechs4zgO3EiUvs6kK7tgsUmlglaLkZiZVStzOBzoPrJHOS1VgVR5YuTPXz1VgXR5YM/+u9xglkyZRT2WqghlbnVc8CA+4mtxRp5vB7XxW7bw3LwvywI0pr5MN6HNNrUi/fUZO4o6tzxuiuO0GuLzUUdu3GOV3M6raeTu2Nm8aR+eBlVD1BnoVjowt0HBgnY51PrqdT/9yxtJ5YHWkeGKswoxJuMcGuIYoKdowKOQ4Xxov+4Zb/khU8Mf681a3gZ/0gYt1PjGdV1J2/mBS5z/58zrbQOeBE6zpGZgA7smRzgc6D2xKlORlCiUVKrfSslMAVulI56PX+XHv81g6D6yPFM+MVWCJDliLJdfoMhVYowPWYsk1ukwF1uiAu1R/Hh5v2wNb8r+9O0pBGAaiKLqdm/1vUCiFSCdhTIjWhntAwefY5sMxRW1ToJnZ89KjQTnuGzGUWB1DLkJtul0ofocn3SOf5wMu3mu97q30GN2e99he2gKEpj6Dkvwjj4tebb5d8EBAuhuUJJ87f96f4aT/1OlNz7GRfgg+WheCUFsfE16cpEFNx5exIUmHx09zd34AaRdACCDp+TVLSlFv1blzgKR2egx5zx/see0pn+djbR4FIVofz4/UeV67G3+vr3niC+H04Oz/nbwA7lqtm+wByfQAAAAASUVORK5CYII=') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5167px; +} + +.font-graph-geSprite-endasynctrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5213px; +} + +.font-graph-geSprite-startblockthin { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4569px; +} + +.font-graph-geSprite-startblockthintrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4615px; +} + +.font-graph-geSprite-starterone { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4661px; +} + +.font-graph-geSprite-starteronetoone { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4707px; +} + +.font-graph-geSprite-starteronetomany { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4753px; +} + +.font-graph-geSprite-startermany { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4799px; +} + +.font-graph-geSprite-starteroneopt { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4845px; +} + +.font-graph-geSprite-startermanyopt { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4891px; +} + +.font-graph-geSprite-startclassicthin { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5259px; +} + +.font-graph-geSprite-startclassicthintrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5305px; +} + +.font-graph-geSprite-startdash { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5351px; +} + +.font-graph-geSprite-startcircleplus { + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAABeQCAMAAAByzXKPAAAA1VBMVEUAAAAzMzIzMzIzMzE1NTUzMzIzMzIzMzEzMzIyMjIzMzIzMzIzMzIzMzE6OjoyMjIzMzIzMzIzMzEzMzI0NDA0NDIwMDAyMjEzMzIzMzEzMzE0NDAzMzMzMzIyMjIzMzI0NDE0NDQyMjIzMzIzMzIzMzIzMzM5OTkAAAC1tbX///9mZmYwMDAQEBAmJiZTiPJ7e3v/Z2RwcHAgICB+fn5hYWFzc3NAQECoqKiampqMjIxTU1NGRkY3NzcbGxsNDQ3w8PBQUFDg4ODAwMCwsLCgoKBPT09W+xp5AAAAKHRSTlMAh4DDEd187+mljnhvVge5nmhMQi4hA8uxk2IlHOOYc102M/jW088J/2liTQAACt5JREFUeNrs3O1um0AQheG5nIPCoIq1Q7+r/Mn9X1K9xtEAM9RLQmODzyO1cV+NtzjShhjHkRkAgn9DMCaYnRXIGOQdtWmk1zRWm0vNN6az+Y+v2fXZ5iTX/kZ4ZGvUNT6/RJuCM/VVRKFB7YBwtnY1k3h2wboaHy8RFQC2VGuc1D730WWLS2GIZ27aM4yUT+9tXaLNw9m0mI9uokepRLeC+ZyCfFCoqwckqV3WBHFZaxHLFrNxVpgUVRWjg/jli2WbfH4WyxYnWcVV0Rz7FYj26X9ezQcuE3VYkXzFSYpm9eq6pqSiuNYAaj+r0GiFxKv5tEOqxVfV0utrCkZF/HCXTuOdGz2IHKbDR5wdJ6PdeRmIsfviED0C5ZV7ojf487v59fPHsT18//a1Yn1HJdoSjIUXBG5XgaBejhMYVmT9B1dRUONj4KtyRHNmNkULDYeD2CGVjiZ0paOK1jVuYKIpjJTO7m9dovvEn3bz/DH4440f2+d9fvlVh+4LLuK6cM8GVcKKkmq3ZqqBfbhaef4n+pijBudNZH3H+OW3No+rbTmVXjfciPbs/GVQ7QU6tWqUW5lonefHfn5f6xI9DAxdqQU3n55uU821mj2ZuUq0SYo2ivA5tThxk3rK6u+urYtZiqKuGiWMEkbp4/OFLITIvyrfekt35WGvjvP6OlERwP6+VU39z+ansp+0SThP8jk+0SeoAehRjObmdha0f2YwfucMss7uaPVF3rThN/EH6fH3YhEt3bD1zMlwGtu8/8a5zrHJuR6NyrkKMK5N5qqf9ev6Y4hfGfSPrc7ZWZqPQrQPa+x5Cfe8FO55m3Xrcs8T3TFUYQ0zqj5jTPoMmagQz4brFh8D3x5L9OngyRy+IEa0JgBRlaBWAIJYyVYjYDF47PwNV/TwAIRfB+BiFcyeolchija6+P7xd//IveI+JlpwnrdZP7xexdD6/4N/PDzP06NZdc8vP4H7WKFXLbqjQcU9T7QZvFhHdN/+sndmy6lCQRQ9KOA8Ro1TxjttYuUlX5BK5f+/6YJIWjlNFREkpLJXGQuWO01e2mPaU4pA17qH7iEeKfRsrrqh4/t0hJQPEJSokULPFpJse0Iu0PNQNVSNnOu8ZHPWZc8TUhkBgECRikZMrp4Xq9W1NPubkIIUm4hnrtyikSIjq+jck3bOBQkpnSBrkU97ALl73pJqXfFc5AlJqN3cXvoTEKIzJcu5PSEFqHiGp6ahz+33Z3rWtpzhEfK16DO8XXi3S2vIvfUCHnpWrcsZHiHVAFUG0KQJoEgjGjGRFG1l9bq25gyPkIoANBcEab9DEPf27iCk40VbWa0uP86WkMsTQHPQHBSnJJHCytp1dW9Uz2cBQoo0PEqVes/r2bM0131CLtLzUCVUidw9n6uuaPY8IdnUYMet2BTccUtIfShnz60mBe65JaTunL/tVqTAbbeE1ImCc3vl16McIEiWc3tCClD5DM9Ak7ZFZCBkZEVzhkfI5/n6Hbdp+wF33BJSH8rZc6vISB/gnltCas/Z225FStdz2y0hZXE19lrt5p177NyR11+OHb/THhzJP86wP2uYrjvz1h92eTseNEzDbB2nd/OY1Py9WNw6/qjnN+fmvnmwnYkxjf1t+mAW7XlsbzaJ3a7DzH1sf3Udp7m/dcOf615sW26SdfvGrCaxbV4l9nEan0X0xqEaRrbvmnlrGFu3PTN3ndUoLOuapW8ODLzudLVomMHA71z/MwmT9mTmN+bOZnS9NcJDs+V53t+WPzQnbNa9/nRoCPl2AKqObKFvltEBoPvcVwNwmavxOy3IDwFAlkWCWPBqhJDC8GtsCPlGYI8ciQyRI+3/CLHHscysXvf0ynzWIOQsPr3wWllkxNQskD+b82/Ihi8qCCm150XpObXnc2RFs+cJqRhAE5AHpI8BOZbH5TQdlXB8JAUEIC4AvkFPSMEl/dQ+v74+2/bl6enFtm/v72+W/c/eHSW3CUNRGNZyjgiZNHE6fW2b6f63VGScCHSvI7CxjfH/tYnTU43CywFiAfnT/On+lunH3274R5G2zbv03rTj9F+z92+U7pqPX52PZjdM35uf6vxs3ofp799Kulf2B8CEc2JVjvJm6OIT5CO9PekvD/8T767XgTc2z1umnEdggyT5eX2s+k9yGpvH1kqvI523IVfSAzdlW2gbu3zn5+6j/JFcfIft0lBOi4/J6cbmBTZJTdPo9fV1/3pamqTUFCalOVkunVdNTU5bSa2Nn7ULjl0A7o5/aGt6Z6TKUpVC7/VLSrWzqTo7b+yzO+9i28shHtugl5cXXS9NLnyYHVZ+Nz79UG7y4in7Aqza9po+tBsXP1B8wCW3m01yVqq9G3w3q/X+/1lpZ5WEbKdOTnNsxiYtd+ngjl28Fw+zhwGwLA0/mDeIS46AxWnO2MVnUFD627+sasuAxyJpTsp3A+6WgnplGpILpL1JR21lZt5k2ZSrFPE41AteEy+Q9qrn58qW77lNM877sXXq+fcGLp/2giETZO5tTYumHObxKDQ0aezhxb2feNE0MWlvqZS1SzwsJZdslu3x9fYae8HFXYeAKcIVejxwzgwc0YE6jTzuWOAuqOS+GTY3rc+rmDxAKn7VEPAt+amm/7Yu8ev0gfnkHckljT8nSoaf/RkeNgUejaKburGiYt696FNIcXrt/3yJh8Qba+advg0BwJCk66Qamp3WvxtX1gMzSDZya+BU8y2OR+ogyk7w5h+Nox+/6S04pNunwKqpt9Db9yemA2GokjqThGR+ms1JeYMe92hu5y2Zbs5O5be7mkru2Hlpgc5j434M1NPiq0qqgXoaDkwqTU21V0s7dgY/TfLX9XMT1udx56RJqTqf5aqlR3vVqtOWaYpbp4NtW2tmPaXbwAZI0U1zroI/tiRW9oBVOtL5QT6uu2TH0nlgnXRSqsLkGXg+JnBTCjopVeGceXk2FnAtColkUynGMn1SrHXejq3Py4U5wHXZvtluJtXO27H+vFyMB9xElFIxNQ6fUmFj9Xm6Sr67SrA6b94Gfp4HLsb2rSM53VSMpseK1c7bsXbechs4zgO3EiUvs6kK7tgsUmlglaLkZiZVStzOBzoPrJHOS1VgVR5YuTPXz1VgXR5YM/+u9xglkyZRT2WqghlbnVc8CA+4mtxRp5vB7XxW7bw3LwvywI0pr5MN6HNNrUi/fUZO4o6tzxuiuO0GuLzUUdu3GOV3M6raeTu2Nm8aR+eBlVD1BnoVjowt0HBgnY51PrqdT/9yxtJ5YHWkeGKswoxJuMcGuIYoKdowKOQ4Xxov+4Zb/khU8Mf681a3gZ/0gYt1PjGdV1J2/mBS5z/58zrbQOeBE6zpGZgA7smRzgc6D2xKlORlCiUVKrfSslMAVulI56PX+XHv81g6D6yPFM+MVWCJDliLJdfoMhVYowPWYsk1ukwF1uiAu1R/Hh5v2wNb8r+9O0pBGAaiKLqdm/1vUCiFSCdhTIjWhntAwefY5sMxRW1ToJnZ89KjQTnuGzGUWB1DLkJtul0ofocn3SOf5wMu3mu97q30GN2e99he2gKEpj6Dkvwjj4tebb5d8EBAuhuUJJ87f96f4aT/1OlNz7GRfgg+WheCUFsfE16cpEFNx5exIUmHx09zd34AaRdACCDp+TVLSlFv1blzgKR2egx5zx/see0pn+djbR4FIVofz4/UeV67G3+vr3niC+H04Oz/nbwA7lqtm+wByfQAAAAASUVORK5CYII=') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5397px; +} + +.font-graph-geSprite-startcircle { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5443px; +} + +.font-graph-geSprite-startasync { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5489px; +} + +.font-graph-geSprite-startasynctrans { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5535px; +} + +.font-graph-geSprite-startcross { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5581px; +} + +.font-graph-geSprite-startopenthin { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5627px; +} + +.font-graph-geSprite-startopenasync { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5673px; +} + +.font-graph-geSprite-endcross { + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAABeQCAMAAAByzXKPAAAA1VBMVEUAAAAzMzIzMzIzMzE1NTUzMzIzMzIzMzEzMzIyMjIzMzIzMzIzMzIzMzE6OjoyMjIzMzIzMzIzMzEzMzI0NDA0NDIwMDAyMjEzMzIzMzEzMzE0NDAzMzMzMzIyMjIzMzI0NDE0NDQyMjIzMzIzMzIzMzIzMzM5OTkAAAC1tbX///9mZmYwMDAQEBAmJiZTiPJ7e3v/Z2RwcHAgICB+fn5hYWFzc3NAQECoqKiampqMjIxTU1NGRkY3NzcbGxsNDQ3w8PBQUFDg4ODAwMCwsLCgoKBPT09W+xp5AAAAKHRSTlMAh4DDEd187+mljnhvVge5nmhMQi4hA8uxk2IlHOOYc102M/jW088J/2liTQAACt5JREFUeNrs3O1um0AQheG5nIPCoIq1Q7+r/Mn9X1K9xtEAM9RLQmODzyO1cV+NtzjShhjHkRkAgn9DMCaYnRXIGOQdtWmk1zRWm0vNN6az+Y+v2fXZ5iTX/kZ4ZGvUNT6/RJuCM/VVRKFB7YBwtnY1k3h2wboaHy8RFQC2VGuc1D730WWLS2GIZ27aM4yUT+9tXaLNw9m0mI9uokepRLeC+ZyCfFCoqwckqV3WBHFZaxHLFrNxVpgUVRWjg/jli2WbfH4WyxYnWcVV0Rz7FYj26X9ezQcuE3VYkXzFSYpm9eq6pqSiuNYAaj+r0GiFxKv5tEOqxVfV0utrCkZF/HCXTuOdGz2IHKbDR5wdJ6PdeRmIsfviED0C5ZV7ojf487v59fPHsT18//a1Yn1HJdoSjIUXBG5XgaBejhMYVmT9B1dRUONj4KtyRHNmNkULDYeD2CGVjiZ0paOK1jVuYKIpjJTO7m9dovvEn3bz/DH4440f2+d9fvlVh+4LLuK6cM8GVcKKkmq3ZqqBfbhaef4n+pijBudNZH3H+OW3No+rbTmVXjfciPbs/GVQ7QU6tWqUW5lonefHfn5f6xI9DAxdqQU3n55uU821mj2ZuUq0SYo2ivA5tThxk3rK6u+urYtZiqKuGiWMEkbp4/OFLITIvyrfekt35WGvjvP6OlERwP6+VU39z+ansp+0SThP8jk+0SeoAehRjObmdha0f2YwfucMss7uaPVF3rThN/EH6fH3YhEt3bD1zMlwGtu8/8a5zrHJuR6NyrkKMK5N5qqf9ev6Y4hfGfSPrc7ZWZqPQrQPa+x5Cfe8FO55m3Xrcs8T3TFUYQ0zqj5jTPoMmagQz4brFh8D3x5L9OngyRy+IEa0JgBRlaBWAIJYyVYjYDF47PwNV/TwAIRfB+BiFcyeolchija6+P7xd//IveI+JlpwnrdZP7xexdD6/4N/PDzP06NZdc8vP4H7WKFXLbqjQcU9T7QZvFhHdN/+sndmy6lCQRQ9KOA8Ro1TxjttYuUlX5BK5f+/6YJIWjlNFREkpLJXGQuWO01e2mPaU4pA17qH7iEeKfRsrrqh4/t0hJQPEJSokULPFpJse0Iu0PNQNVSNnOu8ZHPWZc8TUhkBgECRikZMrp4Xq9W1NPubkIIUm4hnrtyikSIjq+jck3bOBQkpnSBrkU97ALl73pJqXfFc5AlJqN3cXvoTEKIzJcu5PSEFqHiGp6ahz+33Z3rWtpzhEfK16DO8XXi3S2vIvfUCHnpWrcsZHiHVAFUG0KQJoEgjGjGRFG1l9bq25gyPkIoANBcEab9DEPf27iCk40VbWa0uP86WkMsTQHPQHBSnJJHCytp1dW9Uz2cBQoo0PEqVes/r2bM0131CLtLzUCVUidw9n6uuaPY8IdnUYMet2BTccUtIfShnz60mBe65JaTunL/tVqTAbbeE1ImCc3vl16McIEiWc3tCClD5DM9Ak7ZFZCBkZEVzhkfI5/n6Hbdp+wF33BJSH8rZc6vISB/gnltCas/Z225FStdz2y0hZXE19lrt5p177NyR11+OHb/THhzJP86wP2uYrjvz1h92eTseNEzDbB2nd/OY1Py9WNw6/qjnN+fmvnmwnYkxjf1t+mAW7XlsbzaJ3a7DzH1sf3Udp7m/dcOf615sW26SdfvGrCaxbV4l9nEan0X0xqEaRrbvmnlrGFu3PTN3ndUoLOuapW8ODLzudLVomMHA71z/MwmT9mTmN+bOZnS9NcJDs+V53t+WPzQnbNa9/nRoCPl2AKqObKFvltEBoPvcVwNwmavxOy3IDwFAlkWCWPBqhJDC8GtsCPlGYI8ciQyRI+3/CLHHscysXvf0ynzWIOQsPr3wWllkxNQskD+b82/Ihi8qCCm150XpObXnc2RFs+cJqRhAE5AHpI8BOZbH5TQdlXB8JAUEIC4AvkFPSMEl/dQ+v74+2/bl6enFtm/v72+W/c/eHSW3CUNRGNZyjgiZNHE6fW2b6f63VGScCHSvI7CxjfH/tYnTU43CywFiAfnT/On+lunH3274R5G2zbv03rTj9F+z92+U7pqPX52PZjdM35uf6vxs3ofp799Kulf2B8CEc2JVjvJm6OIT5CO9PekvD/8T767XgTc2z1umnEdggyT5eX2s+k9yGpvH1kqvI523IVfSAzdlW2gbu3zn5+6j/JFcfIft0lBOi4/J6cbmBTZJTdPo9fV1/3pamqTUFCalOVkunVdNTU5bSa2Nn7ULjl0A7o5/aGt6Z6TKUpVC7/VLSrWzqTo7b+yzO+9i28shHtugl5cXXS9NLnyYHVZ+Nz79UG7y4in7Aqza9po+tBsXP1B8wCW3m01yVqq9G3w3q/X+/1lpZ5WEbKdOTnNsxiYtd+ngjl28Fw+zhwGwLA0/mDeIS46AxWnO2MVnUFD627+sasuAxyJpTsp3A+6WgnplGpILpL1JR21lZt5k2ZSrFPE41AteEy+Q9qrn58qW77lNM877sXXq+fcGLp/2giETZO5tTYumHObxKDQ0aezhxb2feNE0MWlvqZS1SzwsJZdslu3x9fYae8HFXYeAKcIVejxwzgwc0YE6jTzuWOAuqOS+GTY3rc+rmDxAKn7VEPAt+amm/7Yu8ev0gfnkHckljT8nSoaf/RkeNgUejaKburGiYt696FNIcXrt/3yJh8Qba+advg0BwJCk66Qamp3WvxtX1gMzSDZya+BU8y2OR+ogyk7w5h+Nox+/6S04pNunwKqpt9Db9yemA2GokjqThGR+ms1JeYMe92hu5y2Zbs5O5be7mkru2Hlpgc5j434M1NPiq0qqgXoaDkwqTU21V0s7dgY/TfLX9XMT1udx56RJqTqf5aqlR3vVqtOWaYpbp4NtW2tmPaXbwAZI0U1zroI/tiRW9oBVOtL5QT6uu2TH0nlgnXRSqsLkGXg+JnBTCjopVeGceXk2FnAtColkUynGMn1SrHXejq3Py4U5wHXZvtluJtXO27H+vFyMB9xElFIxNQ6fUmFj9Xm6Sr67SrA6b94Gfp4HLsb2rSM53VSMpseK1c7bsXbechs4zgO3EiUvs6kK7tgsUmlglaLkZiZVStzOBzoPrJHOS1VgVR5YuTPXz1VgXR5YM/+u9xglkyZRT2WqghlbnVc8CA+4mtxRp5vB7XxW7bw3LwvywI0pr5MN6HNNrUi/fUZO4o6tzxuiuO0GuLzUUdu3GOV3M6raeTu2Nm8aR+eBlVD1BnoVjowt0HBgnY51PrqdT/9yxtJ5YHWkeGKswoxJuMcGuIYoKdowKOQ4Xxov+4Zb/khU8Mf681a3gZ/0gYt1PjGdV1J2/mBS5z/58zrbQOeBE6zpGZgA7smRzgc6D2xKlORlCiUVKrfSslMAVulI56PX+XHv81g6D6yPFM+MVWCJDliLJdfoMhVYowPWYsk1ukwF1uiAu1R/Hh5v2wNb8r+9O0pBGAaiKLqdm/1vUCiFSCdhTIjWhntAwefY5sMxRW1ToJnZ89KjQTnuGzGUWB1DLkJtul0ofocn3SOf5wMu3mu97q30GN2e99he2gKEpj6Dkvwjj4tebb5d8EBAuhuUJJ87f96f4aT/1OlNz7GRfgg+WheCUFsfE16cpEFNx5exIUmHx09zd34AaRdACCDp+TVLSlFv1blzgKR2egx5zx/see0pn+djbR4FIVofz4/UeV67G3+vr3niC+H04Oz/nbwA7lqtm+wByfQAAAAASUVORK5CYII=') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5719px; +} + +.font-graph-geSprite-endopenthin { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5765px; +} + +.font-graph-geSprite-endopenasync { + background: url('data:image/png;base64,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') no-repeat; + width: 21px; + height: 21px; + background-position: 0 -5811px; +} + + +.geSprite-delete { + background: url(data:image/png;base64,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) no-repeat; + width: 21px; + height: 21px; + background-position: 0 -184px; +} + +.geSprite-insert { + background: url(data:image/png;base64,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) no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4152px; +} + +.geSprite-dots { + background: url(data:image/png;base64,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) no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3370px; +} + +.geSprite-duplicate { + background: url(data:image/png;base64,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) no-repeat; + width: 21px; + height: 21px; + background-position: 0 -4106px; +} + +.geSprite-plus { + background: url(data:image/png;base64,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) no-repeat; + width: 21px; + height: 21px; + background-position: 0 -3922px; +} + +html div.mxRubberband { + border-color: #0000DD; + background: #99ccff; +} + +html div.mxPopupMenu { + -webkit-box-shadow: 2px 2px 3px #d5d5d5; + -moz-box-shadow: 2px 2px 3px #d5d5d5; + box-shadow: 2px 2px 3px #d5d5d5; + _filter: progid:DXImageTransform.Microsoft.DropShadow(OffX=2, OffY=2, Color='#d0d0d0', Positive='true'); + background: white; + position: absolute; + border: 3px solid #e7e7e7; +} + +html table.mxPopupMenu { + border-collapse: collapse; + margin: 0px; + width: 100%; +} + +html td.mxPopupMenuItem { + padding: 7px 30px 7px 30px; + font-family: Helvetica Neue, Helvetica, Arial Unicode MS, Arial; + font-size: 10pt; +} + +html td.mxPopupMenuIcon { + background-color: white; + padding: 2px; +} + +td.mxPopupMenuIcon .geIcon { + border: 1px solid transparent; + opacity: 0.5; + display: inline-block; + padding: 5px 12px; + font-size: 16px; +} + +td.mxPopupMenuIcon .geIcon:hover { + border-radius: 2px; + opacity: 0.9; +} + +html tr.mxPopupMenuItem { + color: #182b50; + + td { + color: #182b50; + + span { + color: gray + } + } +} + +html tr.mxPopupMenuItemHover { + background-color: #FFF; + color: black; +} + +html tr.mxPopupMenuItemHover { + td { + // color:#5382FB; + color: #5382FB; + background: #d7e6fe; + + span { + color: #5382FB; + background: #d7e6fe; + } + } +} + +table.mxPopupMenu hr { + color: #d1d5dc; + background-color: #d1d5dc; + border: none; + height: 1px; +} + +table.mxPopupMenu tr { + font-size: 4pt; +} + +html td.mxWindowTitle { + font-family: Helvetica Neue, Helvetica, Arial Unicode MS, Arial; + text-align: left; + font-size: 12px; + color: rgb(112, 112, 112); + padding: 4px; +} \ No newline at end of file diff --git a/oaweb/public/cherry/drawio/src/images/button.gif b/oaweb/public/cherry/drawio/src/images/button.gif new file mode 100644 index 0000000..ad55cab Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/button.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/close.gif b/oaweb/public/cherry/drawio/src/images/close.gif new file mode 100644 index 0000000..1069e94 Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/close.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/collapsed.gif b/oaweb/public/cherry/drawio/src/images/collapsed.gif new file mode 100644 index 0000000..0276444 Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/collapsed.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/error.gif b/oaweb/public/cherry/drawio/src/images/error.gif new file mode 100644 index 0000000..14e1aee Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/error.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/expanded.gif b/oaweb/public/cherry/drawio/src/images/expanded.gif new file mode 100644 index 0000000..3767b0b Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/expanded.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/maximize.gif b/oaweb/public/cherry/drawio/src/images/maximize.gif new file mode 100644 index 0000000..e27cf3e Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/maximize.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/minimize.gif b/oaweb/public/cherry/drawio/src/images/minimize.gif new file mode 100644 index 0000000..1e95e7c Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/minimize.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/normalize.gif b/oaweb/public/cherry/drawio/src/images/normalize.gif new file mode 100644 index 0000000..34a8d30 Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/normalize.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/point.gif b/oaweb/public/cherry/drawio/src/images/point.gif new file mode 100644 index 0000000..9074c39 Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/point.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/resize.gif b/oaweb/public/cherry/drawio/src/images/resize.gif new file mode 100644 index 0000000..ff558db Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/resize.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/separator.gif b/oaweb/public/cherry/drawio/src/images/separator.gif new file mode 100644 index 0000000..5c1b895 Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/separator.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/submenu.gif b/oaweb/public/cherry/drawio/src/images/submenu.gif new file mode 100644 index 0000000..ffe7617 Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/submenu.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/transparent.gif b/oaweb/public/cherry/drawio/src/images/transparent.gif new file mode 100644 index 0000000..76040f2 Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/transparent.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/warning.gif b/oaweb/public/cherry/drawio/src/images/warning.gif new file mode 100644 index 0000000..705235f Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/warning.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/warning.png b/oaweb/public/cherry/drawio/src/images/warning.png new file mode 100644 index 0000000..2f78789 Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/warning.png differ diff --git a/oaweb/public/cherry/drawio/src/images/window-title.gif b/oaweb/public/cherry/drawio/src/images/window-title.gif new file mode 100644 index 0000000..231def8 Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/window-title.gif differ diff --git a/oaweb/public/cherry/drawio/src/images/window.gif b/oaweb/public/cherry/drawio/src/images/window.gif new file mode 100644 index 0000000..6631c4f Binary files /dev/null and b/oaweb/public/cherry/drawio/src/images/window.gif differ diff --git a/oaweb/public/cherry/drawio/src/js/editor/mxDefaultKeyHandler.js b/oaweb/public/cherry/drawio/src/js/editor/mxDefaultKeyHandler.js new file mode 100644 index 0000000..237dea4 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/editor/mxDefaultKeyHandler.js @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxDefaultKeyHandler + * + * Binds keycodes to actionnames in an editor. This aggregates an internal + * and extends the implementation of to not + * only cancel the editing, but also hide the properties dialog and fire an + * event via . An instance of this class is created + * by and stored in . + * + * Example: + * + * Bind the delete key to the delete action in an existing editor. + * + * (code) + * var keyHandler = new mxDefaultKeyHandler(editor); + * keyHandler.bindAction(46, 'delete'); + * (end) + * + * Codec: + * + * This class uses the to read configuration + * data into an existing instance. See for a + * description of the configuration format. + * + * Keycodes: + * + * See . + * + * An event is fired via the editor if the escape key is + * pressed. + * + * Constructor: mxDefaultKeyHandler + * + * Constructs a new default key handler for the in the + * given . (The editor may be null if a prototypical instance for + * a is created.) + * + * Parameters: + * + * editor - Reference to the enclosing . + */ +function mxDefaultKeyHandler(editor) +{ + if (editor != null) + { + this.editor = editor; + this.handler = new mxKeyHandler(editor.graph); + + // Extends the escape function of the internal key + // handle to hide the properties dialog and fire + // the escape event via the editor instance + var old = this.handler.escape; + + this.handler.escape = function(evt) + { + old.apply(this, arguments); + editor.hideProperties(); + editor.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt)); + }; + } +}; + +/** + * Variable: editor + * + * Reference to the enclosing . + */ +mxDefaultKeyHandler.prototype.editor = null; + +/** + * Variable: handler + * + * Holds the for key event handling. + */ +mxDefaultKeyHandler.prototype.handler = null; + +/** + * Function: bindAction + * + * Binds the specified keycode to the given action in . The + * optional control flag specifies if the control key must be pressed + * to trigger the action. + * + * Parameters: + * + * code - Integer that specifies the keycode. + * action - Name of the action to execute in . + * control - Optional boolean that specifies if control must be pressed. + * Default is false. + */ +mxDefaultKeyHandler.prototype.bindAction = function (code, action, control) +{ + var keyHandler = mxUtils.bind(this, function() + { + this.editor.execute(action); + }); + + // Binds the function to control-down keycode + if (control) + { + this.handler.bindControlKey(code, keyHandler); + } + + // Binds the function to the normal keycode + else + { + this.handler.bindKey(code, keyHandler); + } +}; + +/** + * Function: destroy + * + * Destroys the associated with this object. This does normally + * not need to be called, the is destroyed automatically when the + * window unloads (in IE) by . + */ +mxDefaultKeyHandler.prototype.destroy = function () +{ + this.handler.destroy(); + this.handler = null; +}; diff --git a/oaweb/public/cherry/drawio/src/js/editor/mxDefaultPopupMenu.js b/oaweb/public/cherry/drawio/src/js/editor/mxDefaultPopupMenu.js new file mode 100644 index 0000000..2f2e6e7 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/editor/mxDefaultPopupMenu.js @@ -0,0 +1,306 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxDefaultPopupMenu + * + * Creates popupmenus for mouse events. This object holds an XML node + * which is a description of the popup menu to be created. In + * , the configuration is applied to the context and + * the resulting menu items are added to the menu dynamically. See + * for a description of the configuration format. + * + * This class does not create the DOM nodes required for the popup menu, it + * only parses an XML description to invoke the respective methods on an + * each time the menu is displayed. + * + * Codec: + * + * This class uses the to read configuration + * data into an existing instance, however, the actual parsing is done + * by this class during program execution, so the format is described + * below. + * + * Constructor: mxDefaultPopupMenu + * + * Constructs a new popupmenu-factory based on given configuration. + * + * Paramaters: + * + * config - XML node that contains the configuration data. + */ +function mxDefaultPopupMenu(config) +{ + this.config = config; +}; + +/** + * Variable: imageBasePath + * + * Base path for all icon attributes in the config. Default is null. + */ +mxDefaultPopupMenu.prototype.imageBasePath = null; + +/** + * Variable: config + * + * XML node used as the description of new menu items. This node is + * used in to dynamically create the menu items if their + * respective conditions evaluate to true for the given arguments. + */ +mxDefaultPopupMenu.prototype.config = null; + +/** + * Function: createMenu + * + * This function is called from to add items to the + * given menu based on . The config is a sequence of + * the following nodes and attributes. + * + * Child Nodes: + * + * add - Adds a new menu item. See below for attributes. + * separator - Adds a separator. No attributes. + * condition - Adds a custom condition. Name attribute. + * + * The add-node may have a child node that defines a function to be invoked + * before the action is executed (or instead of an action to be executed). + * + * Attributes: + * + * as - Resource key for the label (needs entry in property file). + * action - Name of the action to execute in enclosing editor. + * icon - Optional icon (relative/absolute URL). + * iconCls - Optional CSS class for the icon. + * if - Optional name of condition that must be true (see below). + * enabled-if - Optional name of condition that specifies if the menu item + * should be enabled. + * name - Name of custom condition. Only for condition nodes. + * + * Conditions: + * + * nocell - No cell under the mouse. + * ncells - More than one cell selected. + * notRoot - Drilling position is other than home. + * cell - Cell under the mouse. + * notEmpty - Exactly one cell with children under mouse. + * expandable - Exactly one expandable cell under mouse. + * collapsable - Exactly one collapsable cell under mouse. + * validRoot - Exactly one cell which is a possible root under mouse. + * swimlane - Exactly one cell which is a swimlane under mouse. + * + * Example: + * + * To add a new item for a given action to the popupmenu: + * + * (code) + * + * + * + * (end) + * + * To add a new item for a custom function: + * + * (code) + * + * + * + * (end) + * + * The above example invokes action1 with an additional third argument via + * the editor instance. The third argument is passed to the function that + * defines action1. If the add-node has no action-attribute, then only the + * function defined in the text content is executed, otherwise first the + * function and then the action defined in the action-attribute is + * executed. The function in the text content has 3 arguments, namely the + * instance, the instance under the mouse, and the + * native mouse event. + * + * Custom Conditions: + * + * To add a new condition for popupmenu items: + * + * (code) + * + * (end) + * + * The new condition can then be used in any item as follows: + * + * (code) + * + * (end) + * + * The order in which the items and conditions appear is not significant as + * all connditions are evaluated before any items are created. + * + * Parameters: + * + * editor - Enclosing instance. + * menu - that is used for adding items and separators. + * cell - Optional which is under the mousepointer. + * evt - Optional mouse event which triggered the menu. + */ +mxDefaultPopupMenu.prototype.createMenu = function(editor, menu, cell, evt) +{ + if (this.config != null) + { + var conditions = this.createConditions(editor, cell, evt); + var item = this.config.firstChild; + + this.addItems(editor, menu, cell, evt, conditions, item, null); + } +}; + +/** + * Function: addItems + * + * Recursively adds the given items and all of its children into the given menu. + * + * Parameters: + * + * editor - Enclosing instance. + * menu - that is used for adding items and separators. + * cell - Optional which is under the mousepointer. + * evt - Optional mouse event which triggered the menu. + * conditions - Array of names boolean conditions. + * item - XML node that represents the current menu item. + * parent - DOM node that represents the parent menu item. + */ +mxDefaultPopupMenu.prototype.addItems = function(editor, menu, cell, evt, conditions, item, parent) +{ + var addSeparator = false; + + while (item != null) + { + if (item.nodeName == 'add') + { + var condition = item.getAttribute('if'); + + if (condition == null || conditions[condition]) + { + var as = item.getAttribute('as'); + as = mxResources.get(as) || as; + var funct = mxUtils.eval(mxUtils.getTextContent(item)); + var action = item.getAttribute('action'); + var icon = item.getAttribute('icon'); + var iconCls = item.getAttribute('iconCls'); + var enabledCond = item.getAttribute('enabled-if'); + var enabled = enabledCond == null || conditions[enabledCond]; + + if (addSeparator) + { + menu.addSeparator(parent); + addSeparator = false; + } + + if (icon != null && this.imageBasePath) + { + icon = this.imageBasePath + icon; + } + + var row = this.addAction(menu, editor, as, icon, funct, action, cell, parent, iconCls, enabled); + this.addItems(editor, menu, cell, evt, conditions, item.firstChild, row); + } + } + else if (item.nodeName == 'separator') + { + addSeparator = true; + } + + item = item.nextSibling; + } +}; + +/** + * Function: addAction + * + * Helper method to bind an action to a new menu item. + * + * Parameters: + * + * menu - that is used for adding items and separators. + * editor - Enclosing instance. + * lab - String that represents the label of the menu item. + * icon - Optional URL that represents the icon of the menu item. + * action - Optional name of the action to execute in the given editor. + * funct - Optional function to execute before the optional action. The + * function takes an , the under the mouse and the + * mouse event that triggered the call. + * cell - Optional to use as an argument for the action. + * parent - DOM node that represents the parent menu item. + * iconCls - Optional CSS class for the menu icon. + * enabled - Optional boolean that specifies if the menu item is enabled. + * Default is true. + */ +mxDefaultPopupMenu.prototype.addAction = function(menu, editor, lab, icon, funct, action, cell, parent, iconCls, enabled) +{ + var clickHandler = function(evt) + { + if (typeof(funct) == 'function') + { + funct.call(editor, editor, cell, evt); + } + + if (action != null) + { + editor.execute(action, cell, evt); + } + }; + + return menu.addItem(lab, icon, clickHandler, parent, iconCls, enabled); +}; + +/** + * Function: createConditions + * + * Evaluates the default conditions for the given context. + */ +mxDefaultPopupMenu.prototype.createConditions = function(editor, cell, evt) +{ + // Creates array with conditions + var model = editor.graph.getModel(); + var childCount = model.getChildCount(cell); + + // Adds some frequently used conditions + var conditions = []; + conditions['nocell'] = cell == null; + conditions['ncells'] = editor.graph.getSelectionCount() > 1; + conditions['notRoot'] = model.getRoot() != + model.getParent(editor.graph.getDefaultParent()); + conditions['cell'] = cell != null; + + var isCell = cell != null && editor.graph.getSelectionCount() == 1; + conditions['nonEmpty'] = isCell && childCount > 0; + conditions['expandable'] = isCell && editor.graph.isCellFoldable(cell, false); + conditions['collapsable'] = isCell && editor.graph.isCellFoldable(cell, true); + conditions['validRoot'] = isCell && editor.graph.isValidRoot(cell); + conditions['emptyValidRoot'] = conditions['validRoot'] && childCount == 0; + conditions['swimlane'] = isCell && editor.graph.isSwimlane(cell); + + // Evaluates dynamic conditions from config file + var condNodes = this.config.getElementsByTagName('condition'); + + for (var i=0; i to read configuration + * data into an existing instance. See for a + * description of the configuration format. + * + * Constructor: mxDefaultToolbar + * + * Constructs a new toolbar for the given container and editor. The + * container and editor may be null if a prototypical instance for a + * is created. + * + * Parameters: + * + * container - DOM node that contains the toolbar. + * editor - Reference to the enclosing . + */ +function mxDefaultToolbar(container, editor) +{ + this.editor = editor; + + if (container != null && editor != null) + { + this.init(container); + } +}; + +/** + * Variable: editor + * + * Reference to the enclosing . + */ +mxDefaultToolbar.prototype.editor = null; + +/** + * Variable: toolbar + * + * Holds the internal . + */ +mxDefaultToolbar.prototype.toolbar = null; + +/** + * Variable: resetHandler + * + * Reference to the function used to reset the . + */ +mxDefaultToolbar.prototype.resetHandler = null; + +/** + * Variable: spacing + * + * Defines the spacing between existing and new vertices in + * gridSize units when a new vertex is dropped on an existing + * cell. Default is 4 (40 pixels). + */ +mxDefaultToolbar.prototype.spacing = 4; + +/** + * Variable: connectOnDrop + * + * Specifies if elements should be connected if new cells are dropped onto + * connectable elements. Default is false. + */ +mxDefaultToolbar.prototype.connectOnDrop = false; + +/** + * Variable: init + * + * Constructs the for the given container and installs a listener + * that updates the on if an item is + * selected in the toolbar. This assumes that is not null. + * + * Parameters: + * + * container - DOM node that contains the toolbar. + */ +mxDefaultToolbar.prototype.init = function(container) +{ + if (container != null) + { + this.toolbar = new mxToolbar(container); + + // Installs the insert function in the editor if an item is + // selected in the toolbar + this.toolbar.addListener(mxEvent.SELECT, mxUtils.bind(this, function(sender, evt) + { + var funct = evt.getProperty('function'); + + if (funct != null) + { + this.editor.insertFunction = mxUtils.bind(this, function() + { + funct.apply(this, arguments); + this.toolbar.resetMode(); + }); + } + else + { + this.editor.insertFunction = null; + } + })); + + // Resets the selected tool after a doubleclick or escape keystroke + this.resetHandler = mxUtils.bind(this, function() + { + if (this.toolbar != null) + { + this.toolbar.resetMode(true); + } + }); + + this.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler); + this.editor.addListener(mxEvent.ESCAPE, this.resetHandler); + } +}; + +/** + * Function: addItem + * + * Adds a new item that executes the given action in . The title, + * icon and pressedIcon are used to display the toolbar item. + * + * Parameters: + * + * title - String that represents the title (tooltip) for the item. + * icon - URL of the icon to be used for displaying the item. + * action - Name of the action to execute when the item is clicked. + * pressed - Optional URL of the icon for the pressed state. + */ +mxDefaultToolbar.prototype.addItem = function(title, icon, action, pressed) +{ + var clickHandler = mxUtils.bind(this, function() + { + if (action != null && action.length > 0) + { + this.editor.execute(action); + } + }); + + return this.toolbar.addItem(title, icon, clickHandler, pressed); +}; + +/** + * Function: addSeparator + * + * Adds a vertical separator using the optional icon. + * + * Parameters: + * + * icon - Optional URL of the icon that represents the vertical separator. + * Default is + '/separator.gif'. + */ +mxDefaultToolbar.prototype.addSeparator = function(icon) +{ + icon = icon || mxClient.imageBasePath + '/separator.gif'; + this.toolbar.addSeparator(icon); +}; + +/** + * Function: addCombo + * + * Helper method to invoke on and return the + * resulting DOM node. + */ +mxDefaultToolbar.prototype.addCombo = function() +{ + return this.toolbar.addCombo(); +}; + +/** + * Function: addActionCombo + * + * Helper method to invoke on using + * the given title and return the resulting DOM node. + * + * Parameters: + * + * title - String that represents the title of the combo. + */ +mxDefaultToolbar.prototype.addActionCombo = function(title) +{ + return this.toolbar.addActionCombo(title); +}; + +/** + * Function: addActionOption + * + * Binds the given action to a option with the specified label in the + * given combo. Combo is an object returned from an earlier call to + * or . + * + * Parameters: + * + * combo - DOM node that represents the combo box. + * title - String that represents the title of the combo. + * action - Name of the action to execute in . + */ +mxDefaultToolbar.prototype.addActionOption = function(combo, title, action) +{ + var clickHandler = mxUtils.bind(this, function() + { + this.editor.execute(action); + }); + + this.addOption(combo, title, clickHandler); +}; + +/** + * Function: addOption + * + * Helper method to invoke on and return + * the resulting DOM node that represents the option. + * + * Parameters: + * + * combo - DOM node that represents the combo box. + * title - String that represents the title of the combo. + * value - Object that represents the value of the option. + */ +mxDefaultToolbar.prototype.addOption = function(combo, title, value) +{ + return this.toolbar.addOption(combo, title, value); +}; + +/** + * Function: addMode + * + * Creates an item for selecting the given mode in the 's graph. + * Supported modenames are select, connect and pan. + * + * Parameters: + * + * title - String that represents the title of the item. + * icon - URL of the icon that represents the item. + * mode - String that represents the mode name to be used in + * . + * pressed - Optional URL of the icon that represents the pressed state. + * funct - Optional JavaScript function that takes the as the + * first and only argument that is executed after the mode has been + * selected. + */ +mxDefaultToolbar.prototype.addMode = function(title, icon, mode, pressed, funct) +{ + var clickHandler = mxUtils.bind(this, function() + { + this.editor.setMode(mode); + + if (funct != null) + { + funct(this.editor); + } + }); + + return this.toolbar.addSwitchMode(title, icon, clickHandler, pressed); +}; + +/** + * Function: addPrototype + * + * Creates an item for inserting a clone of the specified prototype cell into + * the 's graph. The ptype may either be a cell or a function that + * returns a cell. + * + * Parameters: + * + * title - String that represents the title of the item. + * icon - URL of the icon that represents the item. + * ptype - Function or object that represents the prototype cell. If ptype + * is a function then it is invoked with no arguments to create new + * instances. + * pressed - Optional URL of the icon that represents the pressed state. + * insert - Optional JavaScript function that handles an insert of the new + * cell. This function takes the , new cell to be inserted, mouse + * event and optional under the mouse pointer as arguments. + * toggle - Optional boolean that specifies if the item can be toggled. + * Default is true. + */ +mxDefaultToolbar.prototype.addPrototype = function(title, icon, ptype, pressed, insert, toggle) +{ + // Creates a wrapper function that is in charge of constructing + // the new cell instance to be inserted into the graph + var factory = mxUtils.bind(this, function() + { + if (typeof(ptype) == 'function') + { + return ptype(); + } + else if (ptype != null) + { + return this.editor.graph.cloneCell(ptype); + } + + return null; + }); + + // Defines the function for a click event on the graph + // after this item has been selected in the toolbar + var clickHandler = mxUtils.bind(this, function(evt, cell) + { + if (typeof(insert) == 'function') + { + insert(this.editor, factory(), evt, cell); + } + else + { + this.drop(factory(), evt, cell); + } + + this.toolbar.resetMode(); + mxEvent.consume(evt); + }); + + var img = this.toolbar.addMode(title, icon, clickHandler, pressed, null, toggle); + + // Creates a wrapper function that calls the click handler without + // the graph argument + var dropHandler = function(graph, evt, cell) + { + clickHandler(evt, cell); + }; + + this.installDropHandler(img, dropHandler); + + return img; +}; + +/** + * Function: drop + * + * Handles a drop from a toolbar item to the graph. The given vertex + * represents the new cell to be inserted. This invokes or + * depending on the given target cell. + * + * Parameters: + * + * vertex - to be inserted. + * evt - Mouse event that represents the drop. + * target - Optional that represents the drop target. + */ +mxDefaultToolbar.prototype.drop = function(vertex, evt, target) +{ + var graph = this.editor.graph; + var model = graph.getModel(); + + if (target == null || + model.isEdge(target) || + !this.connectOnDrop || + !graph.isCellConnectable(target)) + { + while (target != null && + !graph.isValidDropTarget(target, [vertex], evt)) + { + target = model.getParent(target); + } + + this.insert(vertex, evt, target); + } + else + { + this.connect(vertex, evt, target); + } +}; + +/** + * Function: insert + * + * Handles a drop by inserting the given vertex into the given parent cell + * or the default parent if no parent is specified. + * + * Parameters: + * + * vertex - to be inserted. + * evt - Mouse event that represents the drop. + * parent - Optional that represents the parent. + */ +mxDefaultToolbar.prototype.insert = function(vertex, evt, target) +{ + var graph = this.editor.graph; + + if (graph.canImportCell(vertex)) + { + var x = mxEvent.getClientX(evt); + var y = mxEvent.getClientY(evt); + var pt = mxUtils.convertPoint(graph.container, x, y); + + // Splits the target edge or inserts into target group + if (graph.isSplitEnabled() && + graph.isSplitTarget(target, [vertex], evt)) + { + return graph.splitEdge(target, [vertex], null, pt.x, pt.y); + } + else + { + return this.editor.addVertex(target, vertex, pt.x, pt.y); + } + } + + return null; +}; + +/** + * Function: connect + * + * Handles a drop by connecting the given vertex to the given source cell. + * + * vertex - to be inserted. + * evt - Mouse event that represents the drop. + * source - Optional that represents the source terminal. + */ +mxDefaultToolbar.prototype.connect = function(vertex, evt, source) +{ + var graph = this.editor.graph; + var model = graph.getModel(); + + if (source != null && + graph.isCellConnectable(vertex) && + graph.isEdgeValid(null, source, vertex)) + { + var edge = null; + + model.beginUpdate(); + try + { + var geo = model.getGeometry(source); + var g = model.getGeometry(vertex).clone(); + + // Moves the vertex away from the drop target that will + // be used as the source for the new connection + g.x = geo.x + (geo.width - g.width) / 2; + g.y = geo.y + (geo.height - g.height) / 2; + + var step = this.spacing * graph.gridSize; + var dist = model.getDirectedEdgeCount(source, true) * 20; + + if (this.editor.horizontalFlow) + { + g.x += (g.width + geo.width) / 2 + step + dist; + } + else + { + g.y += (g.height + geo.height) / 2 + step + dist; + } + + vertex.setGeometry(g); + + // Fires two add-events with the code below - should be fixed + // to only fire one add event for both inserts + var parent = model.getParent(source); + graph.addCell(vertex, parent); + graph.constrainChild(vertex); + + // Creates the edge using the editor instance and calls + // the second function that fires an add event + edge = this.editor.createEdge(source, vertex); + + if (model.getGeometry(edge) == null) + { + var edgeGeometry = new mxGeometry(); + edgeGeometry.relative = true; + + model.setGeometry(edge, edgeGeometry); + } + + graph.addEdge(edge, parent, source, vertex); + } + finally + { + model.endUpdate(); + } + + graph.setSelectionCells([vertex, edge]); + graph.scrollCellToVisible(vertex); + } +}; + +/** + * Function: installDropHandler + * + * Makes the given img draggable using the given function for handling a + * drop event. + * + * Parameters: + * + * img - DOM node that represents the image. + * dropHandler - Function that handles a drop of the image. + */ +mxDefaultToolbar.prototype.installDropHandler = function (img, dropHandler) +{ + var sprite = document.createElement('img'); + sprite.setAttribute('src', img.getAttribute('src')); + + // Handles delayed loading of the images + var loader = mxUtils.bind(this, function(evt) + { + // Preview uses the image node with double size. Later this can be + // changed to use a separate preview and guides, but for this the + // dropHandler must use the additional x- and y-arguments and the + // dragsource which makeDraggable returns much be configured to + // use guides via mxDragSource.isGuidesEnabled. + sprite.style.width = (2 * img.offsetWidth) + 'px'; + sprite.style.height = (2 * img.offsetHeight) + 'px'; + + mxUtils.makeDraggable(img, this.editor.graph, dropHandler, + sprite); + mxEvent.removeListener(sprite, 'load', loader); + }); + + if (mxClient.IS_IE) + { + loader(); + } + else + { + mxEvent.addListener(sprite, 'load', loader); + } +}; + +/** + * Function: destroy + * + * Destroys the associated with this object and removes all + * installed listeners. This does normally not need to be called, the + * is destroyed automatically when the window unloads (in IE) by + * . + */ +mxDefaultToolbar.prototype.destroy = function () +{ + if (this.resetHandler != null) + { + this.editor.graph.removeListener('dblclick', this.resetHandler); + this.editor.removeListener('escape', this.resetHandler); + this.resetHandler = null; + } + + if (this.toolbar != null) + { + this.toolbar.destroy(); + this.toolbar = null; + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/editor/mxEditor.js b/oaweb/public/cherry/drawio/src/js/editor/mxEditor.js new file mode 100644 index 0000000..9f7330b --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/editor/mxEditor.js @@ -0,0 +1,3118 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxEditor + * + * Extends to implement a application wrapper for a graph that + * adds , I/O using , auto-layout using , + * command history using , and standard dialogs and widgets, eg. + * properties, help, outline, toolbar, and popupmenu. It also adds + * to be used as cells in toolbars, auto-validation using the + * flag, attribute cycling using , higher-level events + * such as , and backend integration using and . + * + * Actions: + * + * Actions are functions stored in the array under their names. The + * functions take the as the first, and an optional as the + * second argument and are invoked using . Any additional arguments + * passed to execute are passed on to the action as-is. + * + * A list of built-in actions is available in the description. + * + * Read/write Diagrams: + * + * To read a diagram from an XML string, for example from a textfield within the + * page, the following code is used: + * + * (code) + * var doc = mxUtils.parseXML(xmlString); + * var node = doc.documentElement; + * editor.readGraphModel(node); + * (end) + * + * For reading a diagram from a remote location, use the method. + * + * To save diagrams in XML on a server, you can set the variable. + * This variable will be used in to construct a URL for the post + * request that is issued in the method. The post request contains the + * XML representation of the diagram as returned by in the + * xml parameter. + * + * On the server side, the post request is processed using standard + * technologies such as Java Servlets, CGI, .NET or ASP. + * + * Here are some examples of processing a post request in various languages. + * + * - Java: URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", " ") + * + * Note that the linefeeds should only be replaced if the XML is + * processed in Java, for example when creating an image, but not + * if the XML is passed back to the client-side. + * + * - .NET: HttpUtility.UrlDecode(context.Request.Params["xml"]) + * - PHP: urldecode($_POST["xml"]) + * + * Creating images: + * + * A backend (Java, PHP or C#) is required for creating images. The + * distribution contains an example for each backend (ImageHandler.java, + * ImageHandler.cs and graph.php). More information about using a backend + * to create images can be found in the readme.html files. Note that the + * preview is implemented using VML/SVG in the browser and does not require + * a backend. The backend is only required to creates images (bitmaps). + * + * Special characters: + * + * Note There are five characters that should always appear in XML content as + * escapes, so that they do not interact with the syntax of the markup. These + * are part of the language for all documents based on XML and for HTML. + * + * - < (<) + * - > (>) + * - & (&) + * - " (") + * - ' (') + * + * Although it is part of the XML language, ' is not defined in HTML. + * For this reason the XHTML specification recommends instead the use of + * ' if text may be passed to a HTML user agent. + * + * If you are having problems with special characters on the server-side then + * you may want to try the flag. + * + * For converting decimal escape sequences inside strings, a user has provided + * us with the following function: + * + * (code) + * function html2js(text) + * { + * var entitySearch = /&#[0-9]+;/; + * var entity; + * + * while (entity = entitySearch.exec(text)) + * { + * var charCode = entity[0].substring(2, entity[0].length -1); + * text = text.substring(0, entity.index) + * + String.fromCharCode(charCode) + * + text.substring(entity.index + entity[0].length); + * } + * + * return text; + * } + * (end) + * + * Otherwise try using hex escape sequences and the built-in unescape function + * for converting such strings. + * + * Local Files: + * + * For saving and opening local files, no standardized method exists that + * works across all browsers. The recommended way of dealing with local files + * is to create a backend that streams the XML data back to the browser (echo) + * as an attachment so that a Save-dialog is displayed on the client-side and + * the file can be saved to the local disk. + * + * For example, in PHP the code that does this looks as follows. + * + * (code) + * $xml = stripslashes($_POST["xml"]); + * header("Content-Disposition: attachment; filename=\"diagram.xml\""); + * echo($xml); + * (end) + * + * To open a local file, the file should be uploaded via a form in the browser + * and then opened from the server in the editor. + * + * Cell Properties: + * + * The properties displayed in the properties dialog are the attributes and + * values of the cell's user object, which is an XML node. The XML node is + * defined in the templates section of the config file. + * + * The templates are stored in and contain cells which + * are cloned at insertion time to create new vertices by use of drag and + * drop from the toolbar. Each entry in the toolbar for adding a new vertex + * must refer to an existing template. + * + * In the following example, the task node is a business object and only the + * mxCell node and its mxGeometry child contain graph information: + * + * (code) + * + * + * + * + * + * (end) + * + * The idea is that the XML representation is inverse from the in-memory + * representation: The outer XML node is the user object and the inner node is + * the cell. This means the user object of the cell is the Task node with no + * children for the above example: + * + * (code) + * + * (end) + * + * The Task node can have any tag name, attributes and child nodes. The + * will use the XML hierarchy as the user object, while removing the + * "known annotations", such as the mxCell node. At save-time the cell data + * will be "merged" back into the user object. The user object is only modified + * via the properties dialog during the lifecycle of the cell. + * + * In the default implementation of , the user object's + * attributes are put into a form for editing. Attributes are changed using + * the action in the model. The dialog can be replaced + * by overriding the hook or by replacing the showProperties + * action in . Alternatively, the entry in the config file's popupmenu + * section can be modified to invoke a different action. + * + * If you want to displey the properties dialog on a doubleclick, you can set + * to showProperties as follows: + * + * (code) + * editor.dblClickAction = 'showProperties'; + * (end) + * + * Popupmenu and Toolbar: + * + * The toolbar and popupmenu are typically configured using the respective + * sections in the config file, that is, the popupmenu is defined as follows: + * + * (code) + * + * + * + * ... + * (end) + * + * New entries can be added to the toolbar by inserting an add-node into the + * above configuration. Existing entries may be removed and changed by + * modifying or removing the respective entries in the configuration. + * The configuration is read by the , the format of the + * configuration is explained in . + * + * The toolbar is defined in the mxDefaultToolbar section. Items can be added + * and removed in this section. + * + * (code) + * + * + * + * + * ... + * (end) + * + * The format of the configuration is described in + * . + * + * Ids: + * + * For the IDs, there is an implicit behaviour in : It moves the Id + * from the cell to the user object at encoding time and vice versa at decoding + * time. For example, if the Task node from above has an id attribute, then + * the of the corresponding cell will have this value. If there + * is no Id collision in the model, then the cell may be retrieved using this + * Id with the function. If there is a collision, a new + * Id will be created for the cell using . At encoding + * time, this new Id will replace the value previously stored under the id + * attribute in the Task node. + * + * See , and + * for information about configuring the editor and user interface. + * + * Programmatically inserting cells: + * + * For inserting a new cell, say, by clicking a button in the document, + * the following code can be used. This requires an reference to the editor. + * + * (code) + * var userObject = new Object(); + * var parent = editor.graph.getDefaultParent(); + * var model = editor.graph.model; + * model.beginUpdate(); + * try + * { + * editor.graph.insertVertex(parent, null, userObject, 20, 20, 80, 30); + * } + * finally + * { + * model.endUpdate(); + * } + * (end) + * + * If a template cell from the config file should be inserted, then a clone + * of the template can be created as follows. The clone is then inserted using + * the add function instead of addVertex. + * + * (code) + * var template = editor.templates['task']; + * var clone = editor.graph.model.cloneCell(template); + * (end) + * + * Resources: + * + * resources/editor - Language resources for mxEditor + * + * Callback: onInit + * + * Called from within the constructor. In the callback, + * "this" refers to the editor instance. + * + * Cookie: mxgraph=seen + * + * Set when the editor is started. Never expires. Use + * to reset this cookie. This cookie + * only exists if is implemented. + * + * Event: mxEvent.OPEN + * + * Fires after a file was opened in . The filename property + * contains the filename that was used. The same value is also available in + * . + * + * Event: mxEvent.SAVE + * + * Fires after the current file was saved in . The url + * property contains the URL that was used for saving. + * + * Event: mxEvent.POST + * + * Fires if a successful response was received in . The + * request property contains the , the + * url and data properties contain the URL and the + * data that were used in the post request. + * + * Event: mxEvent.ROOT + * + * Fires when the current root has changed, or when the title of the current + * root has changed. This event has no properties. + * + * Event: mxEvent.BEFORE_ADD_VERTEX + * + * Fires before a vertex is added in . The vertex + * property contains the new vertex and the parent property + * contains its parent. + * + * Event: mxEvent.ADD_VERTEX + * + * Fires between begin- and endUpdate in . The vertex + * property contains the vertex that is being inserted. + * + * Event: mxEvent.AFTER_ADD_VERTEX + * + * Fires after a vertex was inserted and selected in . The + * vertex property contains the new vertex. + * + * Example: + * + * For starting an in-place edit after a new vertex has been added to the + * graph, the following code can be used. + * + * (code) + * editor.addListener(mxEvent.AFTER_ADD_VERTEX, function(sender, evt) + * { + * var vertex = evt.getProperty('vertex'); + * + * if (editor.graph.isCellEditable(vertex)) + * { + * editor.graph.startEditingAtCell(vertex); + * } + * }); + * (end) + * + * Event: mxEvent.ESCAPE + * + * Fires when the escape key is pressed. The event property + * contains the key event. + * + * Constructor: mxEditor + * + * Constructs a new editor. This function invokes the callback + * upon completion. + * + * Example: + * + * (code) + * var config = mxUtils.load('config/diagrameditor.xml').getDocumentElement(); + * var editor = new mxEditor(config); + * (end) + * + * Parameters: + * + * config - Optional XML node that contains the configuration. + */ +function mxEditor(config) +{ + this.actions = []; + this.addActions(); + + // Executes the following only if a document has been instanciated. + // That is, don't execute when the editorcodec is setup. + if (document.body != null) + { + // Defines instance fields + this.cycleAttributeValues = []; + this.popupHandler = new mxDefaultPopupMenu(); + this.undoManager = new mxUndoManager(); + + // Creates the graph and toolbar without the containers + this.graph = this.createGraph(); + this.toolbar = this.createToolbar(); + + // Creates the global keyhandler (requires graph instance) + this.keyHandler = new mxDefaultKeyHandler(this); + + // Configures the editor using the URI + // which was passed to the ctor + this.configure(config); + + // Assigns the swimlaneIndicatorColorAttribute on the graph + this.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName; + + // Checks if the hook has been set + if (this.onInit != null) + { + // Invokes the hook + this.onInit(); + } + + // Automatic deallocation of memory + if (mxClient.IS_IE) + { + mxEvent.addListener(window, 'unload', mxUtils.bind(this, function() + { + this.destroy(); + })); + } + } +}; + +/** + * Installs the required language resources at class + * loading time. + */ +if (mxLoadResources) +{ + mxResources.add(mxClient.basePath + '/resources/editor'); +} +else +{ + mxClient.defaultBundles.push(mxClient.basePath + '/resources/editor'); +} + +/** + * Extends mxEventSource. + */ +mxEditor.prototype = new mxEventSource(); +mxEditor.prototype.constructor = mxEditor; + +/** + * Group: Controls and Handlers + */ + +/** + * Variable: askZoomResource + * + * Specifies the resource key for the zoom dialog. If the resource for this + * key does not exist then the value is used as the error message. Default + * is 'askZoom'. + */ +mxEditor.prototype.askZoomResource = (mxClient.language != 'none') ? 'askZoom' : ''; + +/** + * Variable: lastSavedResource + * + * Specifies the resource key for the last saved info. If the resource for + * this key does not exist then the value is used as the error message. + * Default is 'lastSaved'. + */ +mxEditor.prototype.lastSavedResource = (mxClient.language != 'none') ? 'lastSaved' : ''; + +/** + * Variable: currentFileResource + * + * Specifies the resource key for the current file info. If the resource for + * this key does not exist then the value is used as the error message. + * Default is 'lastSaved'. + */ +mxEditor.prototype.currentFileResource = (mxClient.language != 'none') ? 'currentFile' : ''; + +/** + * Variable: propertiesResource + * + * Specifies the resource key for the properties window title. If the + * resource for this key does not exist then the value is used as the + * error message. Default is 'properties'. + */ +mxEditor.prototype.propertiesResource = (mxClient.language != 'none') ? 'properties' : ''; + +/** + * Variable: tasksResource + * + * Specifies the resource key for the tasks window title. If the + * resource for this key does not exist then the value is used as the + * error message. Default is 'tasks'. + */ +mxEditor.prototype.tasksResource = (mxClient.language != 'none') ? 'tasks' : ''; + +/** + * Variable: helpResource + * + * Specifies the resource key for the help window title. If the + * resource for this key does not exist then the value is used as the + * error message. Default is 'help'. + */ +mxEditor.prototype.helpResource = (mxClient.language != 'none') ? 'help' : ''; + +/** + * Variable: outlineResource + * + * Specifies the resource key for the outline window title. If the + * resource for this key does not exist then the value is used as the + * error message. Default is 'outline'. + */ +mxEditor.prototype.outlineResource = (mxClient.language != 'none') ? 'outline' : ''; + +/** + * Variable: outline + * + * Reference to the that contains the outline. The + * is stored in outline.outline. + */ +mxEditor.prototype.outline = null; + +/** + * Variable: graph + * + * Holds a for displaying the diagram. The graph + * is created in . + */ +mxEditor.prototype.graph = null; + +/** + * Variable: graphRenderHint + * + * Holds the render hint used for creating the + * graph in . See . + * Default is null. + */ +mxEditor.prototype.graphRenderHint = null; + +/** + * Variable: toolbar + * + * Holds a for displaying the toolbar. The + * toolbar is created in . + */ +mxEditor.prototype.toolbar = null; + +/** + * Variable: status + * + * DOM container that holds the statusbar. Default is null. + * Use to set this value. + */ +mxEditor.prototype.status = null; + +/** + * Variable: popupHandler + * + * Holds a for displaying + * popupmenus. + */ +mxEditor.prototype.popupHandler = null; + +/** + * Variable: undoManager + * + * Holds an for the command history. + */ +mxEditor.prototype.undoManager = null; + +/** + * Variable: keyHandler + * + * Holds a for handling keyboard events. + * The handler is created in . + */ +mxEditor.prototype.keyHandler = null; + +/** + * Group: Actions and Options + */ + +/** + * Variable: actions + * + * Maps from actionnames to actions, which are functions taking + * the editor and the cell as arguments. Use + * to add or replace an action and to execute an action + * by name, passing the cell to be operated upon as the second + * argument. + */ +mxEditor.prototype.actions = null; + +/** + * Variable: dblClickAction + * + * Specifies the name of the action to be executed + * when a cell is double clicked. Default is edit. + * + * To handle a singleclick, use the following code. + * + * (code) + * editor.graph.addListener(mxEvent.CLICK, function(sender, evt) + * { + * var e = evt.getProperty('event'); + * var cell = evt.getProperty('cell'); + * + * if (cell != null && !e.isConsumed()) + * { + * // Do something useful with cell... + * e.consume(); + * } + * }); + * (end) + */ +mxEditor.prototype.dblClickAction = 'edit'; + +/** + * Variable: swimlaneRequired + * + * Specifies if new cells must be inserted + * into an existing swimlane. Otherwise, cells + * that are not swimlanes can be inserted as + * top-level cells. Default is false. + */ +mxEditor.prototype.swimlaneRequired = false; + +/** + * Variable: disableContextMenu + * + * Specifies if the context menu should be disabled in the graph container. + * Default is true. + */ +mxEditor.prototype.disableContextMenu = true; + +/** + * Group: Templates + */ + +/** + * Variable: insertFunction + * + * Specifies the function to be used for inserting new + * cells into the graph. This is assigned from the + * if a vertex-tool is clicked. + */ +mxEditor.prototype.insertFunction = null; + +/** + * Variable: forcedInserting + * + * Specifies if a new cell should be inserted on a single + * click even using if there is a cell + * under the mousepointer, otherwise the cell under the + * mousepointer is selected. Default is false. + */ +mxEditor.prototype.forcedInserting = false; + +/** + * Variable: templates + * + * Maps from names to protoype cells to be used + * in the toolbar for inserting new cells into + * the diagram. + */ +mxEditor.prototype.templates = null; + +/** + * Variable: defaultEdge + * + * Prototype edge cell that is used for creating + * new edges. + */ +mxEditor.prototype.defaultEdge = null; + +/** + * Variable: defaultEdgeStyle + * + * Specifies the edge style to be returned in . + * Default is null. + */ +mxEditor.prototype.defaultEdgeStyle = null; + +/** + * Variable: defaultGroup + * + * Prototype group cell that is used for creating + * new groups. + */ +mxEditor.prototype.defaultGroup = null; + +/** + * Variable: groupBorderSize + * + * Default size for the border of new groups. If null, + * then then is used. Default is + * null. + */ +mxEditor.prototype.groupBorderSize = null; + +/** + * Group: Backend Integration + */ + +/** + * Variable: filename + * + * Contains the URL of the last opened file as a string. + * Default is null. + */ +mxEditor.prototype.filename = null; + +/** + * Variable: lineFeed + * + * Character to be used for encoding linefeeds in . Default is ' '. + */ +mxEditor.prototype.linefeed = ' '; + +/** + * Variable: postParameterName + * + * Specifies if the name of the post parameter that contains the diagram + * data in a post request to the server. Default is xml. + */ +mxEditor.prototype.postParameterName = 'xml'; + +/** + * Variable: escapePostData + * + * Specifies if the data in the post request for saving a diagram + * should be converted using encodeURIComponent. Default is true. + */ +mxEditor.prototype.escapePostData = true; + +/** + * Variable: urlPost + * + * Specifies the URL to be used for posting the diagram + * to a backend in . + */ +mxEditor.prototype.urlPost = null; + +/** + * Variable: urlImage + * + * Specifies the URL to be used for creating a bitmap of + * the graph in the image action. + */ +mxEditor.prototype.urlImage = null; + +/** + * Group: Autolayout + */ + +/** + * Variable: horizontalFlow + * + * Specifies the direction of the flow + * in the diagram. This is used in the + * layout algorithms. Default is false, + * ie. vertical flow. + */ +mxEditor.prototype.horizontalFlow = false; + +/** + * Variable: layoutDiagram + * + * Specifies if the top-level elements in the + * diagram should be layed out using a vertical + * or horizontal stack depending on the setting + * of . The spacing between the + * swimlanes is specified by . + * Default is false. + * + * If the top-level elements are swimlanes, then + * the intra-swimlane layout is activated by + * the switch. + */ +mxEditor.prototype.layoutDiagram = false; + +/** + * Variable: swimlaneSpacing + * + * Specifies the spacing between swimlanes if + * automatic layout is turned on in + * . Default is 0. + */ +mxEditor.prototype.swimlaneSpacing = 0; + +/** + * Variable: maintainSwimlanes + * + * Specifies if the swimlanes should be kept at the same + * width or height depending on the setting of + * . Default is false. + * + * For horizontal flows, all swimlanes + * have the same height and for vertical flows, all swimlanes + * have the same width. Furthermore, the swimlanes are + * automatically "stacked" if is true. + */ +mxEditor.prototype.maintainSwimlanes = false; + +/** + * Variable: layoutSwimlanes + * + * Specifies if the children of swimlanes should + * be layed out, either vertically or horizontally + * depending on . + * Default is false. + */ +mxEditor.prototype.layoutSwimlanes = false; + +/** + * Group: Attribute Cycling + */ + +/** + * Variable: cycleAttributeValues + * + * Specifies the attribute values to be cycled when + * inserting new swimlanes. Default is an empty + * array. + */ +mxEditor.prototype.cycleAttributeValues = null; + +/** + * Variable: cycleAttributeIndex + * + * Index of the last consumed attribute index. If a new + * swimlane is inserted, then the + * at this index will be used as the value for + * . Default is 0. + */ +mxEditor.prototype.cycleAttributeIndex = 0; + +/** + * Variable: cycleAttributeName + * + * Name of the attribute to be assigned a + * when inserting new swimlanes. Default is fillColor. + */ +mxEditor.prototype.cycleAttributeName = 'fillColor'; + +/** + * Group: Windows + */ + +/** + * Variable: tasks + * + * Holds the created in . + */ +mxEditor.prototype.tasks = null; + +/** + * Variable: tasksWindowImage + * + * Icon for the tasks window. + */ +mxEditor.prototype.tasksWindowImage = null; + +/** + * Variable: tasksTop + * + * Specifies the top coordinate of the tasks window in pixels. + * Default is 20. + */ +mxEditor.prototype.tasksTop = 20; + +/** + * Variable: help + * + * Holds the created in . + */ +mxEditor.prototype.help = null; + +/** + * Variable: helpWindowImage + * + * Icon for the help window. + */ +mxEditor.prototype.helpWindowImage = null; + +/** + * Variable: urlHelp + * + * Specifies the URL to be used for the contents of the + * Online Help window. This is usually specified in the + * resources file under urlHelp for language-specific + * online help support. + */ +mxEditor.prototype.urlHelp = null; + +/** + * Variable: helpWidth + * + * Specifies the width of the help window in pixels. + * Default is 300. + */ +mxEditor.prototype.helpWidth = 300; + +/** + * Variable: helpHeight + * + * Specifies the height of the help window in pixels. + * Default is 260. + */ +mxEditor.prototype.helpHeight = 260; + +/** + * Variable: propertiesWidth + * + * Specifies the width of the properties window in pixels. + * Default is 240. + */ +mxEditor.prototype.propertiesWidth = 240; + +/** + * Variable: propertiesHeight + * + * Specifies the height of the properties window in pixels. + * If no height is specified then the window will be automatically + * sized to fit its contents. Default is null. + */ +mxEditor.prototype.propertiesHeight = null; + +/** + * Variable: movePropertiesDialog + * + * Specifies if the properties dialog should be automatically + * moved near the cell it is displayed for, otherwise the + * dialog is not moved. This value is only taken into + * account if the dialog is already visible. Default is false. + */ +mxEditor.prototype.movePropertiesDialog = false; + +/** + * Variable: validating + * + * Specifies if should automatically be invoked after + * each change. Default is false. + */ +mxEditor.prototype.validating = false; + +/** + * Variable: modified + * + * True if the graph has been modified since it was last saved. + */ +mxEditor.prototype.modified = false; + +/** + * Function: isModified + * + * Returns . + */ +mxEditor.prototype.isModified = function () +{ + return this.modified; +}; + +/** + * Function: setModified + * + * Sets to the specified boolean value. + */ +mxEditor.prototype.setModified = function (value) +{ + this.modified = value; +}; + +/** + * Function: addActions + * + * Adds the built-in actions to the editor instance. + * + * save - Saves the graph using . + * print - Shows the graph in a new print preview window. + * show - Shows the graph in a new window. + * exportImage - Shows the graph as a bitmap image using . + * refresh - Refreshes the graph's display. + * cut - Copies the current selection into the clipboard + * and removes it from the graph. + * copy - Copies the current selection into the clipboard. + * paste - Pastes the clipboard into the graph. + * delete - Removes the current selection from the graph. + * group - Puts the current selection into a new group. + * ungroup - Removes the selected groups and selects the children. + * undo - Undoes the last change on the graph model. + * redo - Redoes the last change on the graph model. + * zoom - Sets the zoom via a dialog. + * zoomIn - Zooms into the graph. + * zoomOut - Zooms out of the graph + * actualSize - Resets the scale and translation on the graph. + * fit - Changes the scale so that the graph fits into the window. + * showProperties - Shows the properties dialog. + * selectAll - Selects all cells. + * selectNone - Clears the selection. + * selectVertices - Selects all vertices. + * selectEdges = Selects all edges. + * edit - Starts editing the current selection cell. + * enterGroup - Drills down into the current selection cell. + * exitGroup - Moves up in the drilling hierachy + * home - Moves to the topmost parent in the drilling hierarchy + * selectPrevious - Selects the previous cell. + * selectNext - Selects the next cell. + * selectParent - Selects the parent of the selection cell. + * selectChild - Selects the first child of the selection cell. + * collapse - Collapses the currently selected cells. + * expand - Expands the currently selected cells. + * bold - Toggle bold text style. + * italic - Toggle italic text style. + * underline - Toggle underline text style. + * alignCellsLeft - Aligns the selection cells at the left. + * alignCellsCenter - Aligns the selection cells in the center. + * alignCellsRight - Aligns the selection cells at the right. + * alignCellsTop - Aligns the selection cells at the top. + * alignCellsMiddle - Aligns the selection cells in the middle. + * alignCellsBottom - Aligns the selection cells at the bottom. + * alignFontLeft - Sets the horizontal text alignment to left. + * alignFontCenter - Sets the horizontal text alignment to center. + * alignFontRight - Sets the horizontal text alignment to right. + * alignFontTop - Sets the vertical text alignment to top. + * alignFontMiddle - Sets the vertical text alignment to middle. + * alignFontBottom - Sets the vertical text alignment to bottom. + * toggleTasks - Shows or hides the tasks window. + * toggleHelp - Shows or hides the help window. + * toggleOutline - Shows or hides the outline window. + * toggleConsole - Shows or hides the console window. + */ +mxEditor.prototype.addActions = function () +{ + this.addAction('save', function(editor) + { + editor.save(); + }); + + this.addAction('print', function(editor) + { + var preview = new mxPrintPreview(editor.graph, 1); + preview.open(); + }); + + this.addAction('show', function(editor) + { + mxUtils.show(editor.graph, null, 10, 10); + }); + + this.addAction('exportImage', function(editor) + { + var url = editor.getUrlImage(); + + if (url == null || mxClient.IS_LOCAL) + { + editor.execute('show'); + } + else + { + var node = mxUtils.getViewXml(editor.graph, 1); + var xml = mxUtils.getXml(node, '\n'); + + mxUtils.submit(url, editor.postParameterName + '=' + + encodeURIComponent(xml), document, '_blank'); + } + }); + + this.addAction('refresh', function(editor) + { + editor.graph.refresh(); + }); + + this.addAction('cut', function(editor) + { + if (editor.graph.isEnabled()) + { + mxClipboard.cut(editor.graph); + } + }); + + this.addAction('copy', function(editor) + { + if (editor.graph.isEnabled()) + { + mxClipboard.copy(editor.graph); + } + }); + + this.addAction('paste', function(editor) + { + if (editor.graph.isEnabled()) + { + mxClipboard.paste(editor.graph); + } + }); + + this.addAction('delete', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.removeCells(); + } + }); + + this.addAction('group', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.setSelectionCell(editor.groupCells()); + } + }); + + this.addAction('ungroup', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.setSelectionCells(editor.graph.ungroupCells()); + } + }); + + this.addAction('removeFromParent', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.removeCellsFromParent(); + } + }); + + this.addAction('undo', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.undo(); + } + }); + + this.addAction('redo', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.redo(); + } + }); + + this.addAction('zoomIn', function(editor) + { + editor.graph.zoomIn(); + }); + + this.addAction('zoomOut', function(editor) + { + editor.graph.zoomOut(); + }); + + this.addAction('actualSize', function(editor) + { + editor.graph.zoomActual(); + }); + + this.addAction('fit', function(editor) + { + editor.graph.fit(); + }); + + this.addAction('showProperties', function(editor, cell) + { + editor.showProperties(cell); + }); + + this.addAction('selectAll', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.selectAll(); + } + }); + + this.addAction('selectNone', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.clearSelection(); + } + }); + + this.addAction('selectVertices', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.selectVertices(); + } + }); + + this.addAction('selectEdges', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.selectEdges(); + } + }); + + this.addAction('edit', function(editor, cell) + { + if (editor.graph.isEnabled() && + editor.graph.isCellEditable(cell)) + { + editor.graph.startEditingAtCell(cell); + } + }); + + this.addAction('toBack', function(editor, cell) + { + if (editor.graph.isEnabled()) + { + editor.graph.orderCells(true); + } + }); + + this.addAction('toFront', function(editor, cell) + { + if (editor.graph.isEnabled()) + { + editor.graph.orderCells(false); + } + }); + + this.addAction('enterGroup', function(editor, cell) + { + editor.graph.enterGroup(cell); + }); + + this.addAction('exitGroup', function(editor) + { + editor.graph.exitGroup(); + }); + + this.addAction('home', function(editor) + { + editor.graph.home(); + }); + + this.addAction('selectPrevious', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.selectPreviousCell(); + } + }); + + this.addAction('selectNext', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.selectNextCell(); + } + }); + + this.addAction('selectParent', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.selectParentCell(); + } + }); + + this.addAction('selectChild', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.selectChildCell(); + } + }); + + this.addAction('collapse', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.foldCells(true); + } + }); + + this.addAction('collapseAll', function(editor) + { + if (editor.graph.isEnabled()) + { + var cells = editor.graph.getChildVertices(); + editor.graph.foldCells(true, false, cells); + } + }); + + this.addAction('expand', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.foldCells(false); + } + }); + + this.addAction('expandAll', function(editor) + { + if (editor.graph.isEnabled()) + { + var cells = editor.graph.getChildVertices(); + editor.graph.foldCells(false, false, cells); + } + }); + + this.addAction('bold', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.toggleCellStyleFlags( + mxConstants.STYLE_FONTSTYLE, + mxConstants.FONT_BOLD); + } + }); + + this.addAction('italic', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.toggleCellStyleFlags( + mxConstants.STYLE_FONTSTYLE, + mxConstants.FONT_ITALIC); + } + }); + + this.addAction('underline', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.toggleCellStyleFlags( + mxConstants.STYLE_FONTSTYLE, + mxConstants.FONT_UNDERLINE); + } + }); + + this.addAction('alignCellsLeft', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.alignCells(mxConstants.ALIGN_LEFT); + } + }); + + this.addAction('alignCellsCenter', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.alignCells(mxConstants.ALIGN_CENTER); + } + }); + + this.addAction('alignCellsRight', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.alignCells(mxConstants.ALIGN_RIGHT); + } + }); + + this.addAction('alignCellsTop', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.alignCells(mxConstants.ALIGN_TOP); + } + }); + + this.addAction('alignCellsMiddle', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.alignCells(mxConstants.ALIGN_MIDDLE); + } + }); + + this.addAction('alignCellsBottom', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.alignCells(mxConstants.ALIGN_BOTTOM); + } + }); + + this.addAction('alignFontLeft', function(editor) + { + + editor.graph.setCellStyles( + mxConstants.STYLE_ALIGN, + mxConstants.ALIGN_LEFT); + }); + + this.addAction('alignFontCenter', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.setCellStyles( + mxConstants.STYLE_ALIGN, + mxConstants.ALIGN_CENTER); + } + }); + + this.addAction('alignFontRight', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.setCellStyles( + mxConstants.STYLE_ALIGN, + mxConstants.ALIGN_RIGHT); + } + }); + + this.addAction('alignFontTop', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.setCellStyles( + mxConstants.STYLE_VERTICAL_ALIGN, + mxConstants.ALIGN_TOP); + } + }); + + this.addAction('alignFontMiddle', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.setCellStyles( + mxConstants.STYLE_VERTICAL_ALIGN, + mxConstants.ALIGN_MIDDLE); + } + }); + + this.addAction('alignFontBottom', function(editor) + { + if (editor.graph.isEnabled()) + { + editor.graph.setCellStyles( + mxConstants.STYLE_VERTICAL_ALIGN, + mxConstants.ALIGN_BOTTOM); + } + }); + + this.addAction('zoom', function(editor) + { + var current = editor.graph.getView().scale*100; + var scale = parseFloat(mxUtils.prompt( + mxResources.get(editor.askZoomResource) || + editor.askZoomResource, + current))/100; + + if (!isNaN(scale)) + { + editor.graph.getView().setScale(scale); + } + }); + + this.addAction('toggleTasks', function(editor) + { + if (editor.tasks != null) + { + editor.tasks.setVisible(!editor.tasks.isVisible()); + } + else + { + editor.showTasks(); + } + }); + + this.addAction('toggleHelp', function(editor) + { + if (editor.help != null) + { + editor.help.setVisible(!editor.help.isVisible()); + } + else + { + editor.showHelp(); + } + }); + + this.addAction('toggleOutline', function(editor) + { + if (editor.outline == null) + { + editor.showOutline(); + } + else + { + editor.outline.setVisible(!editor.outline.isVisible()); + } + }); + + this.addAction('toggleConsole', function(editor) + { + mxLog.setVisible(!mxLog.isVisible()); + }); +}; + +/** + * Function: configure + * + * Configures the editor using the specified node. To load the + * configuration from a given URL the following code can be used to obtain + * the XML node. + * + * (code) + * var node = mxUtils.load(url).getDocumentElement(); + * (end) + * + * Parameters: + * + * node - XML node that contains the configuration. + */ +mxEditor.prototype.configure = function (node) +{ + if (node != null) + { + // Creates a decoder for the XML data + // and uses it to configure the editor + var dec = new mxCodec(node.ownerDocument); + dec.decode(node, this); + + // Resets the counters, modified state and + // command history + this.resetHistory(); + } +}; + +/** + * Function: resetFirstTime + * + * Resets the cookie that is used to remember if the editor has already + * been used. + */ +mxEditor.prototype.resetFirstTime = function () +{ + document.cookie = + 'mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/'; +}; + +/** + * Function: resetHistory + * + * Resets the command history, modified state and counters. + */ +mxEditor.prototype.resetHistory = function () +{ + this.lastSnapshot = new Date().getTime(); + this.undoManager.clear(); + this.ignoredChanges = 0; + this.setModified(false); +}; + +/** + * Function: addAction + * + * Binds the specified actionname to the specified function. + * + * Parameters: + * + * actionname - String that specifies the name of the action + * to be added. + * funct - Function that implements the new action. The first + * argument of the function is the editor it is used + * with, the second argument is the cell it operates + * upon. + * + * Example: + * (code) + * editor.addAction('test', function(editor, cell) + * { + * mxUtils.alert("test "+cell); + * }); + * (end) + */ +mxEditor.prototype.addAction = function (actionname, funct) +{ + this.actions[actionname] = funct; +}; + +/** + * Function: execute + * + * Executes the function with the given name in passing the + * editor instance and given cell as the first and second argument. All + * additional arguments are passed to the action as well. This method + * contains a try-catch block and displays an error message if an action + * causes an exception. The exception is re-thrown after the error + * message was displayed. + * + * Example: + * + * (code) + * editor.execute("showProperties", cell); + * (end) + */ +mxEditor.prototype.execute = function (actionname, cell, evt) +{ + var action = this.actions[actionname]; + + if (action != null) + { + try + { + // Creates the array of arguments by replacing the actionname + // with the editor instance in the args of this function + var args = arguments; + args[0] = this; + + // Invokes the function on the editor using the args + action.apply(this, args); + } + catch (e) + { + mxUtils.error('Cannot execute ' + actionname + + ': ' + e.message, 280, true); + + throw e; + } + } + else + { + mxUtils.error('Cannot find action '+actionname, 280, true); + } +}; + +/** + * Function: addTemplate + * + * Adds the specified template under the given name in . + */ +mxEditor.prototype.addTemplate = function (name, template) +{ + this.templates[name] = template; +}; + +/** + * Function: getTemplate + * + * Returns the template for the given name. + */ +mxEditor.prototype.getTemplate = function (name) +{ + return this.templates[name]; +}; + +/** + * Function: createGraph + * + * Creates the for the editor. The graph is created with no + * container and is initialized from . + */ +mxEditor.prototype.createGraph = function () +{ + var graph = new mxGraph(null, null, this.graphRenderHint); + + // Enables rubberband, tooltips, panning + graph.setTooltips(true); + graph.setPanning(true); + + // Overrides the dblclick method on the graph to + // invoke the dblClickAction for a cell and reset + // the selection tool in the toolbar + this.installDblClickHandler(graph); + + // Installs the command history + this.installUndoHandler(graph); + + // Installs the handlers for the root event + this.installDrillHandler(graph); + + // Installs the handler for validation + this.installChangeHandler(graph); + + // Installs the handler for calling the + // insert function and consume the + // event if an insert function is defined + this.installInsertHandler(graph); + + // Redirects the function for creating the + // popupmenu items + graph.popupMenuHandler.factoryMethod = + mxUtils.bind(this, function(menu, cell, evt) + { + return this.createPopupMenu(menu, cell, evt); + }); + + // Redirects the function for creating + // new connections in the diagram + graph.connectionHandler.factoryMethod = + mxUtils.bind(this, function(source, target) + { + return this.createEdge(source, target); + }); + + // Maintains swimlanes and installs autolayout + this.createSwimlaneManager(graph); + this.createLayoutManager(graph); + + return graph; +}; + +/** + * Function: createSwimlaneManager + * + * Sets the graph's container using . + */ +mxEditor.prototype.createSwimlaneManager = function (graph) +{ + var swimlaneMgr = new mxSwimlaneManager(graph, false); + + swimlaneMgr.isHorizontal = mxUtils.bind(this, function() + { + return this.horizontalFlow; + }); + + swimlaneMgr.isEnabled = mxUtils.bind(this, function() + { + return this.maintainSwimlanes; + }); + + return swimlaneMgr; +}; + +/** + * Function: createLayoutManager + * + * Creates a layout manager for the swimlane and diagram layouts, that + * is, the locally defined inter- and intraswimlane layouts. + */ +mxEditor.prototype.createLayoutManager = function (graph) +{ + var layoutMgr = new mxLayoutManager(graph); + + var self = this; // closure + layoutMgr.getLayout = function(cell) + { + var layout = null; + var model = self.graph.getModel(); + + if (model.getParent(cell) != null) + { + // Executes the swimlane layout if a child of + // a swimlane has been changed. The layout is + // lazy created in createSwimlaneLayout. + if (self.layoutSwimlanes && + graph.isSwimlane(cell)) + { + if (self.swimlaneLayout == null) + { + self.swimlaneLayout = self.createSwimlaneLayout(); + } + + layout = self.swimlaneLayout; + } + + // Executes the diagram layout if the modified + // cell is a top-level cell. The layout is + // lazy created in createDiagramLayout. + else if (self.layoutDiagram && + (graph.isValidRoot(cell) || + model.getParent(model.getParent(cell)) == null)) + { + if (self.diagramLayout == null) + { + self.diagramLayout = self.createDiagramLayout(); + } + + layout = self.diagramLayout; + } + } + + return layout; + }; + + return layoutMgr; +}; + +/** + * Function: setGraphContainer + * + * Sets the graph's container using . + */ +mxEditor.prototype.setGraphContainer = function (container) +{ + if (this.graph.container == null) + { + // Creates the graph instance inside the given container and render hint + //this.graph = new mxGraph(container, null, this.graphRenderHint); + this.graph.init(container); + + // Install rubberband selection as the last + // action handler in the chain + this.rubberband = new mxRubberband(this.graph); + + // Disables the context menu + if (this.disableContextMenu) + { + mxEvent.disableContextMenu(container); + } + + // Workaround for stylesheet directives in IE + if (mxClient.IS_QUIRKS) + { + new mxDivResizer(container); + } + } +}; + +/** + * Function: installDblClickHandler + * + * Overrides to invoke + * on a cell and reset the selection tool in the toolbar. + */ +mxEditor.prototype.installDblClickHandler = function (graph) +{ + // Installs a listener for double click events + graph.addListener(mxEvent.DOUBLE_CLICK, + mxUtils.bind(this, function(sender, evt) + { + var cell = evt.getProperty('cell'); + + if (cell != null && + graph.isEnabled() && + this.dblClickAction != null) + { + this.execute(this.dblClickAction, cell); + evt.consume(); + } + }) + ); +}; + +/** + * Function: installUndoHandler + * + * Adds the to the graph model and the view. + */ +mxEditor.prototype.installUndoHandler = function (graph) +{ + var listener = mxUtils.bind(this, function(sender, evt) + { + var edit = evt.getProperty('edit'); + this.undoManager.undoableEditHappened(edit); + }); + + graph.getModel().addListener(mxEvent.UNDO, listener); + graph.getView().addListener(mxEvent.UNDO, listener); + + // Keeps the selection state in sync + var undoHandler = function(sender, evt) + { + var changes = evt.getProperty('edit').changes; + graph.setSelectionCells(graph.getSelectionCellsForChanges(changes)); + }; + + this.undoManager.addListener(mxEvent.UNDO, undoHandler); + this.undoManager.addListener(mxEvent.REDO, undoHandler); +}; + +/** + * Function: installDrillHandler + * + * Installs listeners for dispatching the event. + */ +mxEditor.prototype.installDrillHandler = function (graph) +{ + var listener = mxUtils.bind(this, function(sender) + { + this.fireEvent(new mxEventObject(mxEvent.ROOT)); + }); + + graph.getView().addListener(mxEvent.DOWN, listener); + graph.getView().addListener(mxEvent.UP, listener); +}; + +/** + * Function: installChangeHandler + * + * Installs the listeners required to automatically validate + * the graph. On each change of the root, this implementation + * fires a event. + */ +mxEditor.prototype.installChangeHandler = function (graph) +{ + var listener = mxUtils.bind(this, function(sender, evt) + { + // Updates the modified state + this.setModified(true); + + // Automatically validates the graph + // after each change + if (this.validating == true) + { + graph.validateGraph(); + } + + // Checks if the root has been changed + var changes = evt.getProperty('edit').changes; + + for (var i = 0; i < changes.length; i++) + { + var change = changes[i]; + + if (change instanceof mxRootChange || + (change instanceof mxValueChange && + change.cell == this.graph.model.root) || + (change instanceof mxCellAttributeChange && + change.cell == this.graph.model.root)) + { + this.fireEvent(new mxEventObject(mxEvent.ROOT)); + break; + } + } + }); + + graph.getModel().addListener(mxEvent.CHANGE, listener); +}; + +/** + * Function: installInsertHandler + * + * Installs the handler for invoking if + * one is defined. + */ +mxEditor.prototype.installInsertHandler = function (graph) +{ + var self = this; // closure + var insertHandler = + { + mouseDown: function(sender, me) + { + if (self.insertFunction != null && + !me.isPopupTrigger() && + (self.forcedInserting || + me.getState() == null)) + { + self.graph.clearSelection(); + self.insertFunction(me.getEvent(), me.getCell()); + + // Consumes the rest of the events + // for this gesture (down, move, up) + this.isActive = true; + me.consume(); + } + }, + + mouseMove: function(sender, me) + { + if (this.isActive) + { + me.consume(); + } + }, + + mouseUp: function(sender, me) + { + if (this.isActive) + { + this.isActive = false; + me.consume(); + } + } + }; + + graph.addMouseListener(insertHandler); +}; + +/** + * Function: createDiagramLayout + * + * Creates the layout instance used to layout the + * swimlanes in the diagram. + */ +mxEditor.prototype.createDiagramLayout = function () +{ + var gs = this.graph.gridSize; + var layout = new mxStackLayout(this.graph, !this.horizontalFlow, + this.swimlaneSpacing, 2*gs, 2*gs); + + // Overrides isIgnored to only take into account swimlanes + layout.isVertexIgnored = function(cell) + { + return !layout.graph.isSwimlane(cell); + }; + + return layout; +}; + +/** + * Function: createSwimlaneLayout + * + * Creates the layout instance used to layout the + * children of each swimlane. + */ +mxEditor.prototype.createSwimlaneLayout = function () +{ + return new mxCompactTreeLayout(this.graph, this.horizontalFlow); +}; + +/** + * Function: createToolbar + * + * Creates the with no container. + */ +mxEditor.prototype.createToolbar = function () +{ + return new mxDefaultToolbar(null, this); +}; + +/** + * Function: setToolbarContainer + * + * Initializes the toolbar for the given container. + */ +mxEditor.prototype.setToolbarContainer = function (container) +{ + this.toolbar.init(container); + + // Workaround for stylesheet directives in IE + if (mxClient.IS_QUIRKS) + { + new mxDivResizer(container); + } +}; + +/** + * Function: setStatusContainer + * + * Creates the using the specified container. + * + * This implementation adds listeners in the editor to + * display the last saved time and the current filename + * in the status bar. + * + * Parameters: + * + * container - DOM node that will contain the statusbar. + */ +mxEditor.prototype.setStatusContainer = function (container) +{ + if (this.status == null) + { + this.status = container; + + // Prints the last saved time in the status bar + // when files are saved + this.addListener(mxEvent.SAVE, mxUtils.bind(this, function() + { + var tstamp = new Date().toLocaleString(); + this.setStatus((mxResources.get(this.lastSavedResource) || + this.lastSavedResource)+': '+tstamp); + })); + + // Updates the statusbar to display the filename + // when new files are opened + this.addListener(mxEvent.OPEN, mxUtils.bind(this, function() + { + this.setStatus((mxResources.get(this.currentFileResource) || + this.currentFileResource)+': '+this.filename); + })); + + // Workaround for stylesheet directives in IE + if (mxClient.IS_QUIRKS) + { + new mxDivResizer(container); + } + } +}; + +/** + * Function: setStatus + * + * Display the specified message in the status bar. + * + * Parameters: + * + * message - String the specified the message to + * be displayed. + */ +mxEditor.prototype.setStatus = function (message) +{ + if (this.status != null && message != null) + { + this.status.innerHTML = message; + } +}; + +/** + * Function: setTitleContainer + * + * Creates a listener to update the inner HTML of the + * specified DOM node with the value of . + * + * Parameters: + * + * container - DOM node that will contain the title. + */ +mxEditor.prototype.setTitleContainer = function (container) +{ + this.addListener(mxEvent.ROOT, mxUtils.bind(this, function(sender) + { + container.innerHTML = this.getTitle(); + })); + + // Workaround for stylesheet directives in IE + if (mxClient.IS_QUIRKS) + { + new mxDivResizer(container); + } +}; + +/** + * Function: treeLayout + * + * Executes a vertical or horizontal compact tree layout + * using the specified cell as an argument. The cell may + * either be a group or the root of a tree. + * + * Parameters: + * + * cell - to use in the compact tree layout. + * horizontal - Optional boolean to specify the tree's + * orientation. Default is true. + */ +mxEditor.prototype.treeLayout = function (cell, horizontal) +{ + if (cell != null) + { + var layout = new mxCompactTreeLayout(this.graph, horizontal); + layout.execute(cell); + } +}; + +/** + * Function: getTitle + * + * Returns the string value for the current root of the + * diagram. + */ +mxEditor.prototype.getTitle = function () +{ + var title = ''; + var graph = this.graph; + var cell = graph.getCurrentRoot(); + + while (cell != null && + graph.getModel().getParent( + graph.getModel().getParent(cell)) != null) + { + // Append each label of a valid root + if (graph.isValidRoot(cell)) + { + title = ' > ' + + graph.convertValueToString(cell) + title; + } + + cell = graph.getModel().getParent(cell); + } + + var prefix = this.getRootTitle(); + + return prefix + title; +}; + +/** + * Function: getRootTitle + * + * Returns the string value of the root cell in + * . + */ +mxEditor.prototype.getRootTitle = function () +{ + var root = this.graph.getModel().getRoot(); + return this.graph.convertValueToString(root); +}; + +/** + * Function: undo + * + * Undo the last change in . + */ +mxEditor.prototype.undo = function () +{ + this.undoManager.undo(); +}; + +/** + * Function: redo + * + * Redo the last change in . + */ +mxEditor.prototype.redo = function () +{ + this.undoManager.redo(); +}; + +/** + * Function: groupCells + * + * Invokes to create a new group cell and the invokes + * , using the grid size of the graph as the spacing + * in the group's content area. + */ +mxEditor.prototype.groupCells = function () +{ + var border = (this.groupBorderSize != null) ? + this.groupBorderSize : + this.graph.gridSize; + return this.graph.groupCells(this.createGroup(), border); +}; + +/** + * Function: createGroup + * + * Creates and returns a clone of to be used + * as a new group cell in . + */ +mxEditor.prototype.createGroup = function () +{ + var model = this.graph.getModel(); + + return model.cloneCell(this.defaultGroup); +}; + +/** + * Function: open + * + * Opens the specified file synchronously and parses it using + * . It updates and fires an -event after + * the file has been opened. Exceptions should be handled as follows: + * + * (code) + * try + * { + * editor.open(filename); + * } + * catch (e) + * { + * mxUtils.error('Cannot open ' + filename + + * ': ' + e.message, 280, true); + * } + * (end) + * + * Parameters: + * + * filename - URL of the file to be opened. + */ +mxEditor.prototype.open = function (filename) +{ + if (filename != null) + { + var xml = mxUtils.load(filename).getXml(); + this.readGraphModel(xml.documentElement); + this.filename = filename; + + this.fireEvent(new mxEventObject(mxEvent.OPEN, 'filename', filename)); + } +}; + +/** + * Function: readGraphModel + * + * Reads the specified XML node into the existing graph model and resets + * the command history and modified state. + */ +mxEditor.prototype.readGraphModel = function (node) +{ + var dec = new mxCodec(node.ownerDocument); + dec.decode(node, this.graph.getModel()); + this.resetHistory(); +}; + +/** + * Function: save + * + * Posts the string returned by to the given URL or the + * URL returned by . The actual posting is carried out by + * . If the URL is null then the resulting XML will be + * displayed using . Exceptions should be handled as + * follows: + * + * (code) + * try + * { + * editor.save(); + * } + * catch (e) + * { + * mxUtils.error('Cannot save : ' + e.message, 280, true); + * } + * (end) + */ +mxEditor.prototype.save = function (url, linefeed) +{ + // Gets the URL to post the data to + url = url || this.getUrlPost(); + + // Posts the data if the URL is not empty + if (url != null && url.length > 0) + { + var data = this.writeGraphModel(linefeed); + this.postDiagram(url, data); + + // Resets the modified flag + this.setModified(false); + } + + // Dispatches a save event + this.fireEvent(new mxEventObject(mxEvent.SAVE, 'url', url)); +}; + +/** + * Function: postDiagram + * + * Hook for subclassers to override the posting of a diagram + * represented by the given node to the given URL. This fires + * an asynchronous event if the diagram has been posted. + * + * Example: + * + * To replace the diagram with the diagram in the response, use the + * following code. + * + * (code) + * editor.addListener(mxEvent.POST, function(sender, evt) + * { + * // Process response (replace diagram) + * var req = evt.getProperty('request'); + * var root = req.getDocumentElement(); + * editor.graph.readGraphModel(root) + * }); + * (end) + */ +mxEditor.prototype.postDiagram = function (url, data) +{ + if (this.escapePostData) + { + data = encodeURIComponent(data); + } + + mxUtils.post(url, this.postParameterName+'='+data, + mxUtils.bind(this, function(req) + { + this.fireEvent(new mxEventObject(mxEvent.POST, + 'request', req, 'url', url, 'data', data)); + }) + ); +}; + +/** + * Function: writeGraphModel + * + * Hook to create the string representation of the diagram. The default + * implementation uses an to encode the graph model as + * follows: + * + * (code) + * var enc = new mxCodec(); + * var node = enc.encode(this.graph.getModel()); + * return mxUtils.getXml(node, this.linefeed); + * (end) + * + * Parameters: + * + * linefeed - Optional character to be used as the linefeed. Default is + * . + */ +mxEditor.prototype.writeGraphModel = function (linefeed) +{ + linefeed = (linefeed != null) ? linefeed : this.linefeed; + var enc = new mxCodec(); + var node = enc.encode(this.graph.getModel()); + + return mxUtils.getXml(node, linefeed); +}; + +/** + * Function: getUrlPost + * + * Returns the URL to post the diagram to. This is used + * in . The default implementation returns , + * adding ?draft=true. + */ +mxEditor.prototype.getUrlPost = function () +{ + return this.urlPost; +}; + +/** + * Function: getUrlImage + * + * Returns the URL to create the image with. This is typically + * the URL of a backend which accepts an XML representation + * of a graph view to create an image. The function is used + * in the image action to create an image. This implementation + * returns . + */ +mxEditor.prototype.getUrlImage = function () +{ + return this.urlImage; +}; + +/** + * Function: swapStyles + * + * Swaps the styles for the given names in the graph's + * stylesheet and refreshes the graph. + */ +mxEditor.prototype.swapStyles = function (first, second) +{ + var style = this.graph.getStylesheet().styles[second]; + this.graph.getView().getStylesheet().putCellStyle( + second, this.graph.getStylesheet().styles[first]); + this.graph.getStylesheet().putCellStyle(first, style); + this.graph.refresh(); +}; + +/** + * Function: showProperties + * + * Creates and shows the properties dialog for the given + * cell. The content area of the dialog is created using + * . + */ +mxEditor.prototype.showProperties = function (cell) +{ + cell = cell || this.graph.getSelectionCell(); + + // Uses the root node for the properties dialog + // if not cell was passed in and no cell is + // selected + if (cell == null) + { + cell = this.graph.getCurrentRoot(); + + if (cell == null) + { + cell = this.graph.getModel().getRoot(); + } + } + + if (cell != null) + { + // Makes sure there is no in-place editor in the + // graph and computes the location of the dialog + this.graph.stopEditing(true); + + var offset = mxUtils.getOffset(this.graph.container); + var x = offset.x+10; + var y = offset.y; + + // Avoids moving the dialog if it is alredy open + if (this.properties != null && !this.movePropertiesDialog) + { + x = this.properties.getX(); + y = this.properties.getY(); + } + + // Places the dialog near the cell for which it + // displays the properties + else + { + var bounds = this.graph.getCellBounds(cell); + + if (bounds != null) + { + x += bounds.x+Math.min(200, bounds.width); + y += bounds.y; + } + } + + // Hides the existing properties dialog and creates a new one with the + // contents created in the hook method + this.hideProperties(); + var node = this.createProperties(cell); + + if (node != null) + { + // Displays the contents in a window and stores a reference to the + // window for later hiding of the window + this.properties = new mxWindow(mxResources.get(this.propertiesResource) || + this.propertiesResource, node, x, y, this.propertiesWidth, this.propertiesHeight, false); + this.properties.setVisible(true); + } + } +}; + +/** + * Function: isPropertiesVisible + * + * Returns true if the properties dialog is currently visible. + */ +mxEditor.prototype.isPropertiesVisible = function () +{ + return this.properties != null; +}; + +/** + * Function: createProperties + * + * Creates and returns the DOM node that represents the contents + * of the properties dialog for the given cell. This implementation + * works for user objects that are XML nodes and display all the + * node attributes in a form. + */ +mxEditor.prototype.createProperties = function (cell) +{ + var model = this.graph.getModel(); + var value = model.getValue(cell); + + if (mxUtils.isNode(value)) + { + // Creates a form for the user object inside + // the cell + var form = new mxForm('properties'); + + // Adds a readonly field for the cell id + var id = form.addText('ID', cell.getId()); + id.setAttribute('readonly', 'true'); + + var geo = null; + var yField = null; + var xField = null; + var widthField = null; + var heightField = null; + + // Adds fields for the location and size + if (model.isVertex(cell)) + { + geo = model.getGeometry(cell); + + if (geo != null) + { + yField = form.addText('top', geo.y); + xField = form.addText('left', geo.x); + widthField = form.addText('width', geo.width); + heightField = form.addText('height', geo.height); + } + } + + // Adds a field for the cell style + var tmp = model.getStyle(cell); + var style = form.addText('Style', tmp || ''); + + // Creates textareas for each attribute of the + // user object within the cell + var attrs = value.attributes; + var texts = []; + + for (var i = 0; i < attrs.length; i++) + { + // Creates a textarea with more lines for + // the cell label + var val = attrs[i].value; + texts[i] = form.addTextarea(attrs[i].nodeName, val, + (attrs[i].nodeName == 'label') ? 4 : 2); + } + + // Adds an OK and Cancel button to the dialog + // contents and implements the respective + // actions below + + // Defines the function to be executed when the + // OK button is pressed in the dialog + var okFunction = mxUtils.bind(this, function() + { + // Hides the dialog + this.hideProperties(); + + // Supports undo for the changes on the underlying + // XML structure / XML node attribute changes. + model.beginUpdate(); + try + { + if (geo != null) + { + geo = geo.clone(); + + geo.x = parseFloat(xField.value); + geo.y = parseFloat(yField.value); + geo.width = parseFloat(widthField.value); + geo.height = parseFloat(heightField.value); + + model.setGeometry(cell, geo); + } + + // Applies the style + if (style.value.length > 0) + { + model.setStyle(cell, style.value); + } + else + { + model.setStyle(cell, null); + } + + // Creates an undoable change for each + // attribute and executes it using the + // model, which will also make the change + // part of the current transaction + for (var i=0; i. The + * default width of the window is 200 pixels, the y-coordinate of the location + * can be specifies in and the x-coordinate is right aligned with a + * 20 pixel offset from the right border. To change the location of the tasks + * window, the following code can be used: + * + * (code) + * var oldShowTasks = mxEditor.prototype.showTasks; + * mxEditor.prototype.showTasks = function() + * { + * oldShowTasks.apply(this, arguments); // "supercall" + * + * if (this.tasks != null) + * { + * this.tasks.setLocation(10, 10); + * } + * }; + * (end) + */ +mxEditor.prototype.showTasks = function () +{ + if (this.tasks == null) + { + var div = document.createElement('div'); + div.style.padding = '4px'; + div.style.paddingLeft = '20px'; + var w = document.body.clientWidth; + var wnd = new mxWindow( + mxResources.get(this.tasksResource) || + this.tasksResource, + div, w - 220, this.tasksTop, 200); + wnd.setClosable(true); + wnd.destroyOnClose = false; + + // Installs a function to update the contents + // of the tasks window on every change of the + // model, selection or root. + var funct = mxUtils.bind(this, function(sender) + { + mxEvent.release(div); + div.innerHTML = ''; + this.createTasks(div); + }); + + this.graph.getModel().addListener(mxEvent.CHANGE, funct); + this.graph.getSelectionModel().addListener(mxEvent.CHANGE, funct); + this.graph.addListener(mxEvent.ROOT, funct); + + // Assigns the icon to the tasks window + if (this.tasksWindowImage != null) + { + wnd.setImage(this.tasksWindowImage); + } + + this.tasks = wnd; + this.createTasks(div); + } + + this.tasks.setVisible(true); +}; + +/** + * Function: refreshTasks + * + * Updates the contents of the tasks window using . + */ +mxEditor.prototype.refreshTasks = function (div) +{ + if (this.tasks != null) + { + var div = this.tasks.content; + mxEvent.release(div); + div.innerHTML = ''; + this.createTasks(div); + } +}; + +/** + * Function: createTasks + * + * Updates the contents of the given DOM node to + * display the tasks associated with the current + * editor state. This is invoked whenever there + * is a possible change of state in the editor. + * Default implementation is empty. + */ +mxEditor.prototype.createTasks = function (div) +{ + // override +}; + +/** + * Function: showHelp + * + * Shows the help window. If the help window does not exist + * then it is created using an iframe pointing to the resource + * for the urlHelp key or if the resource + * is undefined. + */ +mxEditor.prototype.showHelp = function (tasks) +{ + if (this.help == null) + { + var frame = document.createElement('iframe'); + frame.setAttribute('src', mxResources.get('urlHelp') || this.urlHelp); + frame.setAttribute('height', '100%'); + frame.setAttribute('width', '100%'); + frame.setAttribute('frameBorder', '0'); + frame.style.backgroundColor = 'white'; + + var w = document.body.clientWidth; + var h = (document.body.clientHeight || document.documentElement.clientHeight); + + var wnd = new mxWindow(mxResources.get(this.helpResource) || this.helpResource, + frame, (w-this.helpWidth)/2, (h-this.helpHeight)/3, this.helpWidth, this.helpHeight); + wnd.setMaximizable(true); + wnd.setClosable(true); + wnd.destroyOnClose = false; + wnd.setResizable(true); + + // Assigns the icon to the help window + if (this.helpWindowImage != null) + { + wnd.setImage(this.helpWindowImage); + } + + // Workaround for ignored iframe height 100% in FF + if (mxClient.IS_NS) + { + var handler = function(sender) + { + var h = wnd.div.offsetHeight; + frame.setAttribute('height', (h-26)+'px'); + }; + + wnd.addListener(mxEvent.RESIZE_END, handler); + wnd.addListener(mxEvent.MAXIMIZE, handler); + wnd.addListener(mxEvent.NORMALIZE, handler); + wnd.addListener(mxEvent.SHOW, handler); + } + + this.help = wnd; + } + + this.help.setVisible(true); +}; + +/** + * Function: showOutline + * + * Shows the outline window. If the window does not exist, then it is + * created using an . + */ +mxEditor.prototype.showOutline = function () +{ + var create = this.outline == null; + + if (create) + { + var div = document.createElement('div'); + + div.style.overflow = 'hidden'; + div.style.position = 'relative'; + div.style.width = '100%'; + div.style.height = '100%'; + div.style.background = 'white'; + div.style.cursor = 'move'; + + if (document.documentMode == 8) + { + div.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)'; + } + + var wnd = new mxWindow( + mxResources.get(this.outlineResource) || + this.outlineResource, + div, 600, 480, 200, 200, false); + + // Creates the outline in the specified div + // and links it to the existing graph + var outline = new mxOutline(this.graph, div); + wnd.setClosable(true); + wnd.setResizable(true); + wnd.destroyOnClose = false; + + wnd.addListener(mxEvent.RESIZE_END, function() + { + outline.update(); + }); + + this.outline = wnd; + this.outline.outline = outline; + } + + // Finally shows the outline + this.outline.setVisible(true); + this.outline.outline.update(true); +}; + +/** + * Function: setMode + * + * Puts the graph into the specified mode. The following modenames are + * supported: + * + * select - Selects using the left mouse button, new connections + * are disabled. + * connect - Selects using the left mouse button or creates new + * connections if mouse over cell hotspot. See . + * pan - Pans using the left mouse button, new connections are disabled. + */ +mxEditor.prototype.setMode = function(modename) +{ + if (modename == 'select') + { + this.graph.panningHandler.useLeftButtonForPanning = false; + this.graph.setConnectable(false); + } + else if (modename == 'connect') + { + this.graph.panningHandler.useLeftButtonForPanning = false; + this.graph.setConnectable(true); + } + else if (modename == 'pan') + { + this.graph.panningHandler.useLeftButtonForPanning = true; + this.graph.setConnectable(false); + } +}; + +/** + * Function: createPopupMenu + * + * Uses to create the menu in the graph's + * panning handler. The redirection is setup in + * . + */ +mxEditor.prototype.createPopupMenu = function (menu, cell, evt) +{ + this.popupHandler.createMenu(this, menu, cell, evt); +}; + +/** + * Function: createEdge + * + * Uses as the prototype for creating new edges + * in the connection handler of the graph. The style of the + * edge will be overridden with the value returned by + * . + */ +mxEditor.prototype.createEdge = function (source, target) +{ + // Clones the defaultedge prototype + var e = null; + + if (this.defaultEdge != null) + { + var model = this.graph.getModel(); + e = model.cloneCell(this.defaultEdge); + } + else + { + e = new mxCell(''); + e.setEdge(true); + + var geo = new mxGeometry(); + geo.relative = true; + e.setGeometry(geo); + } + + // Overrides the edge style + var style = this.getEdgeStyle(); + + if (style != null) + { + e.setStyle(style); + } + + return e; +}; + +/** + * Function: getEdgeStyle + * + * Returns a string identifying the style of new edges. + * The function is used in when new edges + * are created in the graph. + */ +mxEditor.prototype.getEdgeStyle = function () +{ + return this.defaultEdgeStyle; +}; + +/** + * Function: consumeCycleAttribute + * + * Returns the next attribute in + * or null, if not attribute should be used in the + * specified cell. + */ +mxEditor.prototype.consumeCycleAttribute = function (cell) +{ + return (this.cycleAttributeValues != null && + this.cycleAttributeValues.length > 0 && + this.graph.isSwimlane(cell)) ? + this.cycleAttributeValues[this.cycleAttributeIndex++ % + this.cycleAttributeValues.length] : null; +}; + +/** + * Function: cycleAttribute + * + * Uses the returned value from + * as the value for the key in + * the given cell's style. + */ +mxEditor.prototype.cycleAttribute = function (cell) +{ + if (this.cycleAttributeName != null) + { + var value = this.consumeCycleAttribute(cell); + + if (value != null) + { + cell.setStyle(cell.getStyle()+';'+ + this.cycleAttributeName+'='+value); + } + } +}; + +/** + * Function: addVertex + * + * Adds the given vertex as a child of parent at the specified + * x and y coordinate and fires an event. + */ +mxEditor.prototype.addVertex = function (parent, vertex, x, y) +{ + var model = this.graph.getModel(); + + while (parent != null && !this.graph.isValidDropTarget(parent)) + { + parent = model.getParent(parent); + } + + parent = (parent != null) ? parent : this.graph.getSwimlaneAt(x, y); + var scale = this.graph.getView().scale; + + var geo = model.getGeometry(vertex); + var pgeo = model.getGeometry(parent); + + if (this.graph.isSwimlane(vertex) && + !this.graph.swimlaneNesting) + { + parent = null; + } + else if (parent == null && this.swimlaneRequired) + { + return null; + } + else if (parent != null && pgeo != null) + { + // Keeps vertex inside parent + var state = this.graph.getView().getState(parent); + + if (state != null) + { + x -= state.origin.x * scale; + y -= state.origin.y * scale; + + if (this.graph.isConstrainedMoving) + { + var width = geo.width; + var height = geo.height; + var tmp = state.x+state.width; + + if (x+width > tmp) + { + x -= x+width - tmp; + } + + tmp = state.y+state.height; + + if (y+height > tmp) + { + y -= y+height - tmp; + } + } + } + else if (pgeo != null) + { + x -= pgeo.x*scale; + y -= pgeo.y*scale; + } + } + + geo = geo.clone(); + geo.x = this.graph.snap(x / scale - + this.graph.getView().translate.x - + this.graph.gridSize/2); + geo.y = this.graph.snap(y / scale - + this.graph.getView().translate.y - + this.graph.gridSize/2); + vertex.setGeometry(geo); + + if (parent == null) + { + parent = this.graph.getDefaultParent(); + } + + this.cycleAttribute(vertex); + this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX, + 'vertex', vertex, 'parent', parent)); + + model.beginUpdate(); + try + { + vertex = this.graph.addCell(vertex, parent); + + if (vertex != null) + { + this.graph.constrainChild(vertex); + + this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX, 'vertex', vertex)); + } + } + finally + { + model.endUpdate(); + } + + if (vertex != null) + { + this.graph.setSelectionCell(vertex); + this.graph.scrollCellToVisible(vertex); + this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX, 'vertex', vertex)); + } + + return vertex; +}; + +/** + * Function: destroy + * + * Removes the editor and all its associated resources. This does not + * normally need to be called, it is called automatically when the window + * unloads. + */ +mxEditor.prototype.destroy = function () +{ + if (!this.destroyed) + { + this.destroyed = true; + + if (this.tasks != null) + { + this.tasks.destroy(); + } + + if (this.outline != null) + { + this.outline.destroy(); + } + + if (this.properties != null) + { + this.properties.destroy(); + } + + if (this.keyHandler != null) + { + this.keyHandler.destroy(); + } + + if (this.rubberband != null) + { + this.rubberband.destroy(); + } + + if (this.toolbar != null) + { + this.toolbar.destroy(); + } + + if (this.graph != null) + { + this.graph.destroy(); + } + + this.status = null; + this.templates = null; + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxCellHighlight.js b/oaweb/public/cherry/drawio/src/js/handler/mxCellHighlight.js new file mode 100644 index 0000000..937386a --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxCellHighlight.js @@ -0,0 +1,314 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxCellHighlight + * + * A helper class to highlight cells. Here is an example for a given cell. + * + * (code) + * var highlight = new mxCellHighlight(graph, '#ff0000', 2); + * highlight.highlight(graph.view.getState(cell))); + * (end) + * + * Constructor: mxCellHighlight + * + * Constructs a cell highlight. + */ +function mxCellHighlight(graph, highlightColor, strokeWidth, dashed) +{ + if (graph != null) + { + this.graph = graph; + this.highlightColor = (highlightColor != null) ? highlightColor : mxConstants.DEFAULT_VALID_COLOR; + this.strokeWidth = (strokeWidth != null) ? strokeWidth : mxConstants.HIGHLIGHT_STROKEWIDTH; + this.dashed = (dashed != null) ? dashed : false; + this.opacity = mxConstants.HIGHLIGHT_OPACITY; + + // Updates the marker if the graph changes + this.repaintHandler = mxUtils.bind(this, function() + { + // Updates reference to state + if (this.state != null) + { + var tmp = this.graph.view.getState(this.state.cell); + + if (tmp == null) + { + this.hide(); + } + else + { + this.state = tmp; + this.repaint(); + } + } + }); + + this.graph.getView().addListener(mxEvent.SCALE, this.repaintHandler); + this.graph.getView().addListener(mxEvent.TRANSLATE, this.repaintHandler); + this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler); + this.graph.getModel().addListener(mxEvent.CHANGE, this.repaintHandler); + + // Hides the marker if the current root changes + this.resetHandler = mxUtils.bind(this, function() + { + this.hide(); + }); + + this.graph.getView().addListener(mxEvent.DOWN, this.resetHandler); + this.graph.getView().addListener(mxEvent.UP, this.resetHandler); + } +}; + +/** + * Variable: keepOnTop + * + * Specifies if the highlights should appear on top of everything + * else in the overlay pane. Default is false. + */ +mxCellHighlight.prototype.keepOnTop = false; + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxCellHighlight.prototype.graph = true; + +/** + * Variable: state + * + * Reference to the . + */ +mxCellHighlight.prototype.state = null; + +/** + * Variable: spacing + * + * Specifies the spacing between the highlight for vertices and the vertex. + * Default is 2. + */ +mxCellHighlight.prototype.spacing = 2; + +/** + * Variable: resetHandler + * + * Holds the handler that automatically invokes reset if the highlight + * should be hidden. + */ +mxCellHighlight.prototype.resetHandler = null; + +/** + * Function: setHighlightColor + * + * Sets the color of the rectangle used to highlight drop targets. + * + * Parameters: + * + * color - String that represents the new highlight color. + */ +mxCellHighlight.prototype.setHighlightColor = function(color) +{ + this.highlightColor = color; + + if (this.shape != null) + { + this.shape.stroke = color; + } +}; + +/** + * Function: drawHighlight + * + * Creates and returns the highlight shape for the given state. + */ +mxCellHighlight.prototype.drawHighlight = function() +{ + this.shape = this.createShape(); + this.repaint(); + + if (!this.keepOnTop && this.shape.node.parentNode.firstChild != this.shape.node) + { + this.shape.node.parentNode.insertBefore(this.shape.node, this.shape.node.parentNode.firstChild); + } +}; + +/** + * Function: createShape + * + * Creates and returns the highlight shape for the given state. + */ +mxCellHighlight.prototype.createShape = function() +{ + var shape = this.graph.cellRenderer.createShape(this.state); + + shape.svgStrokeTolerance = this.graph.tolerance; + shape.points = this.state.absolutePoints; + shape.apply(this.state); + shape.stroke = this.highlightColor; + shape.opacity = this.opacity; + shape.isDashed = this.dashed; + shape.isShadow = false; + + shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + shape.init(this.graph.getView().getOverlayPane()); + mxEvent.redirectMouseEvents(shape.node, this.graph, this.state); + + if (this.graph.dialect != mxConstants.DIALECT_SVG) + { + shape.pointerEvents = false; + } + else + { + shape.svgPointerEvents = 'stroke'; + } + + return shape; +}; + +/** + * Function: repaint + * + * Updates the highlight after a change of the model or view. + */ +mxCellHighlight.prototype.getStrokeWidth = function(state) +{ + return this.strokeWidth; +}; + +/** + * Function: repaint + * + * Updates the highlight after a change of the model or view. + */ +mxCellHighlight.prototype.repaint = function() +{ + if (this.state != null && this.shape != null) + { + this.shape.scale = this.state.view.scale; + + if (this.graph.model.isEdge(this.state.cell)) + { + this.shape.strokewidth = this.getStrokeWidth(); + this.shape.points = this.state.absolutePoints; + this.shape.outline = false; + } + else + { + this.shape.bounds = new mxRectangle(this.state.x - this.spacing, this.state.y - this.spacing, + this.state.width + 2 * this.spacing, this.state.height + 2 * this.spacing); + this.shape.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0'); + this.shape.strokewidth = this.getStrokeWidth() / this.state.view.scale; + this.shape.outline = true; + } + + // Uses cursor from shape in highlight + if (this.state.shape != null) + { + this.shape.setCursor(this.state.shape.getCursor()); + } + + // Workaround for event transparency in VML with transparent color + // is to use a non-transparent color with near zero opacity + if (mxClient.IS_QUIRKS || document.documentMode == 8) + { + if (this.shape.stroke == 'transparent') + { + // KNOWN: Quirks mode does not seem to catch events if + // we do not force an update of the DOM via a change such + // as mxLog.debug. Since IE6 is EOL we do not add a fix. + this.shape.stroke = 'white'; + this.shape.opacity = 1; + } + else + { + this.shape.opacity = this.opacity; + } + } + + this.shape.redraw(); + } +}; + +/** + * Function: hide + * + * Resets the state of the cell marker. + */ +mxCellHighlight.prototype.hide = function() +{ + this.highlight(null); +}; + +/** + * Function: mark + * + * Marks the and fires a event. + */ +mxCellHighlight.prototype.highlight = function(state) +{ + if (this.state != state) + { + if (this.shape != null) + { + this.shape.destroy(); + this.shape = null; + } + + this.state = state; + + if (this.state != null) + { + this.drawHighlight(); + } + } +}; + +/** + * Function: isHighlightAt + * + * Returns true if this highlight is at the given position. + */ +mxCellHighlight.prototype.isHighlightAt = function(x, y) +{ + var hit = false; + + // Quirks mode is currently not supported as it used a different coordinate system + if (this.shape != null && document.elementFromPoint != null && !mxClient.IS_QUIRKS) + { + var elt = document.elementFromPoint(x, y); + + while (elt != null) + { + if (elt == this.shape.node) + { + hit = true; + break; + } + + elt = elt.parentNode; + } + } + + return hit; +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ +mxCellHighlight.prototype.destroy = function() +{ + this.graph.getView().removeListener(this.resetHandler); + this.graph.getView().removeListener(this.repaintHandler); + this.graph.getModel().removeListener(this.repaintHandler); + + if (this.shape != null) + { + this.shape.destroy(); + this.shape = null; + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxCellMarker.js b/oaweb/public/cherry/drawio/src/js/handler/mxCellMarker.js new file mode 100644 index 0000000..569620f --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxCellMarker.js @@ -0,0 +1,430 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxCellMarker + * + * A helper class to process mouse locations and highlight cells. + * + * Helper class to highlight cells. To add a cell marker to an existing graph + * for highlighting all cells, the following code is used: + * + * (code) + * var marker = new mxCellMarker(graph); + * graph.addMouseListener({ + * mouseDown: function() {}, + * mouseMove: function(sender, me) + * { + * marker.process(me); + * }, + * mouseUp: function() {} + * }); + * (end) + * + * Event: mxEvent.MARK + * + * Fires after a cell has been marked or unmarked. The state + * property contains the marked or null if no state is marked. + * + * Constructor: mxCellMarker + * + * Constructs a new cell marker. + * + * Parameters: + * + * graph - Reference to the enclosing . + * validColor - Optional marker color for valid states. Default is + * . + * invalidColor - Optional marker color for invalid states. Default is + * . + * hotspot - Portion of the width and hight where a state intersects a + * given coordinate pair. A value of 0 means always highlight. Default is + * . + */ +function mxCellMarker(graph, validColor, invalidColor, hotspot) +{ + mxEventSource.call(this); + + if (graph != null) + { + this.graph = graph; + this.validColor = (validColor != null) ? validColor : mxConstants.DEFAULT_VALID_COLOR; + this.invalidColor = (invalidColor != null) ? invalidColor : mxConstants.DEFAULT_INVALID_COLOR; + this.hotspot = (hotspot != null) ? hotspot : mxConstants.DEFAULT_HOTSPOT; + + this.highlight = new mxCellHighlight(graph); + } +}; + +/** + * Extends mxEventSource. + */ +mxUtils.extend(mxCellMarker, mxEventSource); + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxCellMarker.prototype.graph = null; + +/** + * Variable: enabled + * + * Specifies if the marker is enabled. Default is true. + */ +mxCellMarker.prototype.enabled = true; + +/** + * Variable: hotspot + * + * Specifies the portion of the width and height that should trigger + * a highlight. The area around the center of the cell to be marked is used + * as the hotspot. Possible values are between 0 and 1. Default is + * mxConstants.DEFAULT_HOTSPOT. + */ +mxCellMarker.prototype.hotspot = mxConstants.DEFAULT_HOTSPOT; + +/** + * Variable: hotspotEnabled + * + * Specifies if the hotspot is enabled. Default is false. + */ +mxCellMarker.prototype.hotspotEnabled = false; + +/** + * Variable: validColor + * + * Holds the valid marker color. + */ +mxCellMarker.prototype.validColor = null; + +/** + * Variable: invalidColor + * + * Holds the invalid marker color. + */ +mxCellMarker.prototype.invalidColor = null; + +/** + * Variable: currentColor + * + * Holds the current marker color. + */ +mxCellMarker.prototype.currentColor = null; + +/** + * Variable: validState + * + * Holds the marked if it is valid. + */ +mxCellMarker.prototype.validState = null; + +/** + * Variable: markedState + * + * Holds the marked . + */ +mxCellMarker.prototype.markedState = null; + +/** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates . + * + * Parameters: + * + * enabled - Boolean that specifies the new enabled state. + */ +mxCellMarker.prototype.setEnabled = function(enabled) +{ + this.enabled = enabled; +}; + +/** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns . + */ +mxCellMarker.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setHotspot + * + * Sets the . + */ +mxCellMarker.prototype.setHotspot = function(hotspot) +{ + this.hotspot = hotspot; +}; + +/** + * Function: getHotspot + * + * Returns the . + */ +mxCellMarker.prototype.getHotspot = function() +{ + return this.hotspot; +}; + +/** + * Function: setHotspotEnabled + * + * Specifies whether the hotspot should be used in . + */ +mxCellMarker.prototype.setHotspotEnabled = function(enabled) +{ + this.hotspotEnabled = enabled; +}; + +/** + * Function: isHotspotEnabled + * + * Returns true if hotspot is used in . + */ +mxCellMarker.prototype.isHotspotEnabled = function() +{ + return this.hotspotEnabled; +}; + +/** + * Function: hasValidState + * + * Returns true if is not null. + */ +mxCellMarker.prototype.hasValidState = function() +{ + return this.validState != null; +}; + +/** + * Function: getValidState + * + * Returns the . + */ +mxCellMarker.prototype.getValidState = function() +{ + return this.validState; +}; + +/** + * Function: getMarkedState + * + * Returns the . + */ +mxCellMarker.prototype.getMarkedState = function() +{ + return this.markedState; +}; + +/** + * Function: reset + * + * Resets the state of the cell marker. + */ +mxCellMarker.prototype.reset = function() +{ + this.validState = null; + + if (this.markedState != null) + { + this.markedState = null; + this.unmark(); + } +}; + +/** + * Function: process + * + * Processes the given event and cell and marks the state returned by + * with the color returned by . If the + * markerColor is not null, then the state is stored in . If + * returns true, then the state is stored in + * regardless of the marker color. The state is returned regardless of the + * marker color and valid state. + */ +mxCellMarker.prototype.process = function(me) +{ + var state = null; + + if (this.isEnabled()) + { + state = this.getState(me); + this.setCurrentState(state, me); + } + + return state; +}; + +/** + * Function: setCurrentState + * + * Sets and marks the current valid state. + */ +mxCellMarker.prototype.setCurrentState = function(state, me, color) +{ + var isValid = (state != null) ? this.isValidState(state) : false; + color = (color != null) ? color : this.getMarkerColor(me.getEvent(), state, isValid); + + if (isValid) + { + this.validState = state; + } + else + { + this.validState = null; + } + + if (state != this.markedState || color != this.currentColor) + { + this.currentColor = color; + + if (state != null && this.currentColor != null) + { + this.markedState = state; + this.mark(); + } + else if (this.markedState != null) + { + this.markedState = null; + this.unmark(); + } + } +}; + +/** + * Function: markCell + * + * Marks the given cell using the given color, or if no color is specified. + */ +mxCellMarker.prototype.markCell = function(cell, color) +{ + var state = this.graph.getView().getState(cell); + + if (state != null) + { + this.currentColor = (color != null) ? color : this.validColor; + this.markedState = state; + this.mark(); + } +}; + +/** + * Function: mark + * + * Marks the and fires a event. + */ +mxCellMarker.prototype.mark = function() +{ + this.highlight.setHighlightColor(this.currentColor); + this.highlight.highlight(this.markedState); + this.fireEvent(new mxEventObject(mxEvent.MARK, 'state', this.markedState)); +}; + +/** + * Function: unmark + * + * Hides the marker and fires a event. + */ +mxCellMarker.prototype.unmark = function() +{ + this.mark(); +}; + +/** + * Function: isValidState + * + * Returns true if the given is a valid state. If this + * returns true, then the state is stored in . The return value + * of this method is used as the argument for . + */ +mxCellMarker.prototype.isValidState = function(state) +{ + return true; +}; + +/** + * Function: getMarkerColor + * + * Returns the valid- or invalidColor depending on the value of isValid. + * The given is ignored by this implementation. + */ +mxCellMarker.prototype.getMarkerColor = function(evt, state, isValid) +{ + return (isValid) ? this.validColor : this.invalidColor; +}; + +/** + * Function: getState + * + * Uses , and to return the + * for the given . + */ +mxCellMarker.prototype.getState = function(me) +{ + var view = this.graph.getView(); + var cell = this.getCell(me); + var state = this.getStateToMark(view.getState(cell)); + + return (state != null && this.intersects(state, me)) ? state : null; +}; + +/** + * Function: getCell + * + * Returns the for the given event and cell. This returns the + * given cell. + */ +mxCellMarker.prototype.getCell = function(me) +{ + return me.getCell(); +}; + +/** + * Function: getStateToMark + * + * Returns the to be marked for the given under + * the mouse. This returns the given state. + */ +mxCellMarker.prototype.getStateToMark = function(state) +{ + return state; +}; + +/** + * Function: intersects + * + * Returns true if the given coordinate pair intersects the given state. + * This returns true if the is 0 or the coordinates are inside + * the hotspot for the given cell state. + */ +mxCellMarker.prototype.intersects = function(state, me) +{ + if (this.hotspotEnabled) + { + return mxUtils.intersectsHotspot(state, me.getGraphX(), me.getGraphY(), + this.hotspot, mxConstants.MIN_HOTSPOT_SIZE, + mxConstants.MAX_HOTSPOT_SIZE); + } + + return true; +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ +mxCellMarker.prototype.destroy = function() +{ + this.graph.getView().removeListener(this.resetHandler); + this.graph.getModel().removeListener(this.resetHandler); + this.highlight.destroy(); +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxCellTracker.js b/oaweb/public/cherry/drawio/src/js/handler/mxCellTracker.js new file mode 100644 index 0000000..9f0c8bb --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxCellTracker.js @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxCellTracker + * + * Event handler that highlights cells. Inherits from . + * + * Example: + * + * (code) + * new mxCellTracker(graph, '#00FF00'); + * (end) + * + * For detecting dragEnter, dragOver and dragLeave on cells, the following + * code can be used: + * + * (code) + * graph.addMouseListener( + * { + * cell: null, + * mouseDown: function(sender, me) { }, + * mouseMove: function(sender, me) + * { + * var tmp = me.getCell(); + * + * if (tmp != this.cell) + * { + * if (this.cell != null) + * { + * this.dragLeave(me.getEvent(), this.cell); + * } + * + * this.cell = tmp; + * + * if (this.cell != null) + * { + * this.dragEnter(me.getEvent(), this.cell); + * } + * } + * + * if (this.cell != null) + * { + * this.dragOver(me.getEvent(), this.cell); + * } + * }, + * mouseUp: function(sender, me) { }, + * dragEnter: function(evt, cell) + * { + * mxLog.debug('dragEnter', cell.value); + * }, + * dragOver: function(evt, cell) + * { + * mxLog.debug('dragOver', cell.value); + * }, + * dragLeave: function(evt, cell) + * { + * mxLog.debug('dragLeave', cell.value); + * } + * }); + * (end) + * + * Constructor: mxCellTracker + * + * Constructs an event handler that highlights cells. + * + * Parameters: + * + * graph - Reference to the enclosing . + * color - Color of the highlight. Default is blue. + * funct - Optional JavaScript function that is used to override + * . + */ +function mxCellTracker(graph, color, funct) +{ + mxCellMarker.call(this, graph, color); + + this.graph.addMouseListener(this); + + if (funct != null) + { + this.getCell = funct; + } + + // Automatic deallocation of memory + if (mxClient.IS_IE) + { + mxEvent.addListener(window, 'unload', mxUtils.bind(this, function() + { + this.destroy(); + })); + } +}; + +/** + * Extends mxCellMarker. + */ +mxUtils.extend(mxCellTracker, mxCellMarker); + +/** + * Function: mouseDown + * + * Ignores the event. The event is not consumed. + */ +mxCellTracker.prototype.mouseDown = function(sender, me) { }; + +/** + * Function: mouseMove + * + * Handles the event by highlighting the cell under the mousepointer if it + * is over the hotspot region of the cell. + */ +mxCellTracker.prototype.mouseMove = function(sender, me) +{ + if (this.isEnabled()) + { + this.process(me); + } +}; + +/** + * Function: mouseUp + * + * Handles the event by reseting the highlight. + */ +mxCellTracker.prototype.mouseUp = function(sender, me) { }; + +/** + * Function: destroy + * + * Destroys the object and all its resources and DOM nodes. This doesn't + * normally need to be called. It is called automatically when the window + * unloads. + */ +mxCellTracker.prototype.destroy = function() +{ + if (!this.destroyed) + { + this.destroyed = true; + + this.graph.removeMouseListener(this); + mxCellMarker.prototype.destroy.apply(this); + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxConnectionHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxConnectionHandler.js new file mode 100644 index 0000000..b1b79fe --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxConnectionHandler.js @@ -0,0 +1,2240 @@ +/** + * Copyright (c) 2006-2016, JGraph Ltd + * Copyright (c) 2006-2016, Gaudenz Alder + */ +/** + * Class: mxConnectionHandler + * + * Graph event handler that creates new connections. Uses + * for finding and highlighting the source and target vertices and + * to create the edge instance. This handler is built-into + * and enabled using . + * + * Example: + * + * (code) + * new mxConnectionHandler(graph, function(source, target, style) + * { + * edge = new mxCell('', new mxGeometry()); + * edge.setEdge(true); + * edge.setStyle(style); + * edge.geometry.relative = true; + * return edge; + * }); + * (end) + * + * Here is an alternative solution that just sets a specific user object for + * new edges by overriding . + * + * (code) + * mxConnectionHandlerInsertEdge = mxConnectionHandler.prototype.insertEdge; + * mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style) + * { + * value = 'Test'; + * + * return mxConnectionHandlerInsertEdge.apply(this, arguments); + * }; + * (end) + * + * Using images to trigger connections: + * + * This handler uses mxTerminalMarker to find the source and target cell for + * the new connection and creates a new edge using . The new edge is + * created using which in turn uses or creates a + * new default edge. + * + * The handler uses a "highlight-paradigm" for indicating if a cell is being + * used as a source or target terminal, as seen in other diagramming products. + * In order to allow both, moving and connecting cells at the same time, + * is used in the handler to determine the hotspot + * of a cell, that is, the region of the cell which is used to trigger a new + * connection. The constant is a value between 0 and 1 that specifies the + * amount of the width and height around the center to be used for the hotspot + * of a cell and its default value is 0.5. In addition, + * defines the minimum number of pixels for the + * width and height of the hotspot. + * + * This solution, while standards compliant, may be somewhat confusing because + * there is no visual indicator for the hotspot and the highlight is seen to + * switch on and off while the mouse is being moved in and out. Furthermore, + * this paradigm does not allow to create different connections depending on + * the highlighted hotspot as there is only one hotspot per cell and it + * normally does not allow cells to be moved and connected at the same time as + * there is no clear indication of the connectable area of the cell. + * + * To come across these issues, the handle has an additional hook + * with a default implementation that allows to create one icon to be used to + * trigger new connections. If this icon is specified, then new connections can + * only be created if the image is clicked while the cell is being highlighted. + * The hook may be overridden to create more than one + * for creating new connections, but the default implementation + * supports one image and is used as follows: + * + * In order to display the "connect image" whenever the mouse is over the cell, + * an DEFAULT_HOTSPOT of 1 should be used: + * + * (code) + * mxConstants.DEFAULT_HOTSPOT = 1; + * (end) + * + * In order to avoid confusion with the highlighting, the highlight color + * should not be used with a connect image: + * + * (code) + * mxConstants.HIGHLIGHT_COLOR = null; + * (end) + * + * To install the image, the connectImage field of the mxConnectionHandler must + * be assigned a new instance: + * + * (code) + * mxConnectionHandler.prototype.connectImage = new mxImage('images/green-dot.gif', 14, 14); + * (end) + * + * This will use the green-dot.gif with a width and height of 14 pixels as the + * image to trigger new connections. In createIcons the icon field of the + * handler will be set in order to remember the icon that has been clicked for + * creating the new connection. This field will be available under selectedIcon + * in the connect method, which may be overridden to take the icon that + * triggered the new connection into account. This is useful if more than one + * icon may be used to create a connection. + * + * Group: Events + * + * Event: mxEvent.START + * + * Fires when a new connection is being created by the user. The state + * property contains the state of the source cell. + * + * Event: mxEvent.CONNECT + * + * Fires between begin- and endUpdate in . The cell + * property contains the inserted edge, the event and target + * properties contain the respective arguments that were passed to (where + * target corresponds to the dropTarget argument). Finally, the terminal + * property corresponds to the target argument in or the clone of the source + * terminal if is enabled. + * + * Note that the target is the cell under the mouse where the mouse button was released. + * Depending on the logic in the handler, this doesn't necessarily have to be the target + * of the inserted edge. To print the source, target or any optional ports IDs that the + * edge is connected to, the following code can be used. To get more details about the + * actual connection point, can be used. To resolve + * the port IDs, use . + * + * (code) + * graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt) + * { + * var edge = evt.getProperty('cell'); + * var source = graph.getModel().getTerminal(edge, true); + * var target = graph.getModel().getTerminal(edge, false); + * + * var style = graph.getCellStyle(edge); + * var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT]; + * var targetPortId = style[mxConstants.STYLE_TARGET_PORT]; + * + * mxLog.show(); + * mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId); + * }); + * (end) + * + * Event: mxEvent.RESET + * + * Fires when the method is invoked. + * + * Constructor: mxConnectionHandler + * + * Constructs an event handler that connects vertices using the specified + * factory method to create the new edges. Modify + * to setup the region on a cell which triggers + * the creation of a new connection or use connect icons as explained + * above. + * + * Parameters: + * + * graph - Reference to the enclosing . + * factoryMethod - Optional function to create the edge. The function takes + * the source and target as the first and second argument and an + * optional cell style from the preview as the third argument. It returns + * the that represents the new edge. + */ +function mxConnectionHandler(graph, factoryMethod) +{ + mxEventSource.call(this); + + if (graph != null) + { + this.graph = graph; + this.factoryMethod = factoryMethod; + this.init(); + + // Handles escape keystrokes + this.escapeHandler = mxUtils.bind(this, function(sender, evt) + { + this.reset(); + }); + + this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); + } +}; + +/** + * Extends mxEventSource. + */ +mxUtils.extend(mxConnectionHandler, mxEventSource); + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxConnectionHandler.prototype.graph = null; + +/** + * Variable: factoryMethod + * + * Function that is used for creating new edges. The function takes the + * source and target as the first and second argument and returns + * a new that represents the edge. This is used in . + */ +mxConnectionHandler.prototype.factoryMethod = true; + +/** + * Variable: moveIconFront + * + * Specifies if icons should be displayed inside the graph container instead + * of the overlay pane. This is used for HTML labels on vertices which hide + * the connect icon. This has precendence over when set + * to true. Default is false. + */ +mxConnectionHandler.prototype.moveIconFront = false; + +/** + * Variable: moveIconBack + * + * Specifies if icons should be moved to the back of the overlay pane. This can + * be set to true if the icons of the connection handler conflict with other + * handles, such as the vertex label move handle. Default is false. + */ +mxConnectionHandler.prototype.moveIconBack = false; + +/** + * Variable: connectImage + * + * that is used to trigger the creation of a new connection. This + * is used in . Default is null. + */ +mxConnectionHandler.prototype.connectImage = null; + +/** + * Variable: targetConnectImage + * + * Specifies if the connect icon should be centered on the target state + * while connections are being previewed. Default is false. + */ +mxConnectionHandler.prototype.targetConnectImage = false; + +/** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ +mxConnectionHandler.prototype.enabled = true; + +/** + * Variable: select + * + * Specifies if new edges should be selected. Default is true. + */ +mxConnectionHandler.prototype.select = true; + +/** + * Variable: createTarget + * + * Specifies if should be called if no target was under the + * mouse for the new connection. Setting this to true means the connection + * will be drawn as valid if no target is under the mouse, and + * will be called before the connection is created between + * the source cell and the newly created vertex in , which + * can be overridden to create a new target. Default is false. + */ +mxConnectionHandler.prototype.createTarget = false; + +/** + * Variable: marker + * + * Holds the used for finding source and target cells. + */ +mxConnectionHandler.prototype.marker = null; + +/** + * Variable: constraintHandler + * + * Holds the used for drawing and highlighting + * constraints. + */ +mxConnectionHandler.prototype.constraintHandler = null; + +/** + * Variable: error + * + * Holds the current validation error while connections are being created. + */ +mxConnectionHandler.prototype.error = null; + +/** + * Variable: waypointsEnabled + * + * Specifies if single clicks should add waypoints on the new edge. Default is + * false. + */ +mxConnectionHandler.prototype.waypointsEnabled = false; + +/** + * Variable: ignoreMouseDown + * + * Specifies if the connection handler should ignore the state of the mouse + * button when highlighting the source. Default is false, that is, the + * handler only highlights the source if no button is being pressed. + */ +mxConnectionHandler.prototype.ignoreMouseDown = false; + +/** + * Variable: first + * + * Holds the where the mouseDown took place while the handler is + * active. + */ +mxConnectionHandler.prototype.first = null; + +/** + * Variable: connectIconOffset + * + * Holds the offset for connect icons during connection preview. + * Default is mxPoint(0, ). + * Note that placing the icon under the mouse pointer with an + * offset of (0,0) will affect hit detection. + */ +mxConnectionHandler.prototype.connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET); + +/** + * Variable: edgeState + * + * Optional that represents the preview edge while the + * handler is active. This is created in . + */ +mxConnectionHandler.prototype.edgeState = null; + +/** + * Variable: changeHandler + * + * Holds the change event listener for later removal. + */ +mxConnectionHandler.prototype.changeHandler = null; + +/** + * Variable: drillHandler + * + * Holds the drill event listener for later removal. + */ +mxConnectionHandler.prototype.drillHandler = null; + +/** + * Variable: mouseDownCounter + * + * Counts the number of mouseDown events since the start. The initial mouse + * down event counts as 1. + */ +mxConnectionHandler.prototype.mouseDownCounter = 0; + +/** + * Variable: movePreviewAway + * + * Switch to enable moving the preview away from the mousepointer. This is required in browsers + * where the preview cannot be made transparent to events and if the built-in hit detection on + * the HTML elements in the page should be used. Default is the value of . + */ +mxConnectionHandler.prototype.movePreviewAway = mxClient.IS_VML; + +/** + * Variable: outlineConnect + * + * Specifies if connections to the outline of a highlighted target should be + * enabled. This will allow to place the connection point along the outline of + * the highlighted target. Default is false. + */ +mxConnectionHandler.prototype.outlineConnect = false; + +/** + * Variable: livePreview + * + * Specifies if the actual shape of the edge state should be used for the preview. + * Default is false. (Ignored if no edge state is created in .) + */ +mxConnectionHandler.prototype.livePreview = false; + +/** + * Variable: cursor + * + * Specifies the cursor to be used while the handler is active. Default is null. + */ +mxConnectionHandler.prototype.cursor = null; + +/** + * Variable: insertBeforeSource + * + * Specifies if new edges should be inserted before the source vertex in the + * cell hierarchy. Default is false for backwards compatibility. + */ +mxConnectionHandler.prototype.insertBeforeSource = false; + +/** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns . + */ +mxConnectionHandler.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates . + * + * Parameters: + * + * enabled - Boolean that specifies the new enabled state. + */ +mxConnectionHandler.prototype.setEnabled = function(enabled) +{ + this.enabled = enabled; +}; + +/** + * Function: isInsertBefore + * + * Returns for non-loops and false for loops. + * + * Parameters: + * + * edge - that represents the edge to be inserted. + * source - that represents the source terminal. + * target - that represents the target terminal. + * evt - Mousedown event of the connect gesture. + * dropTarget - that represents the cell under the mouse when it was + * released. + */ +mxConnectionHandler.prototype.isInsertBefore = function(edge, source, target, evt, dropTarget) +{ + return this.insertBeforeSource && source != target; +}; + +/** + * Function: isCreateTarget + * + * Returns . + * + * Parameters: + * + * evt - Current active native pointer event. + */ +mxConnectionHandler.prototype.isCreateTarget = function(evt) +{ + return this.createTarget; +}; + +/** + * Function: setCreateTarget + * + * Sets . + */ +mxConnectionHandler.prototype.setCreateTarget = function(value) +{ + this.createTarget = value; +}; + +/** + * Function: createShape + * + * Creates the preview shape for new connections. + */ +mxConnectionHandler.prototype.createShape = function() +{ + // Creates the edge preview + var shape = (this.livePreview && this.edgeState != null) ? + this.graph.cellRenderer.createShape(this.edgeState) : + new mxPolyline([], mxConstants.INVALID_COLOR); + shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + shape.scale = this.graph.view.scale; + shape.pointerEvents = false; + shape.isDashed = true; + shape.init(this.graph.getView().getOverlayPane()); + mxEvent.redirectMouseEvents(shape.node, this.graph, null); + + return shape; +}; + +/** + * Function: init + * + * Initializes the shapes required for this connection handler. This should + * be invoked if is assigned after the connection + * handler has been created. + */ +mxConnectionHandler.prototype.init = function() +{ + this.graph.addMouseListener(this); + this.marker = this.createMarker(); + this.constraintHandler = new mxConstraintHandler(this.graph); + + // Redraws the icons if the graph changes + this.changeHandler = mxUtils.bind(this, function(sender) + { + if (this.iconState != null) + { + this.iconState = this.graph.getView().getState(this.iconState.cell); + } + + if (this.iconState != null) + { + this.redrawIcons(this.icons, this.iconState); + this.constraintHandler.reset(); + } + else if (this.previous != null && this.graph.view.getState(this.previous.cell) == null) + { + this.reset(); + } + }); + + this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler); + this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler); + this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler); + this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler); + + // Removes the icon if we step into/up or start editing + this.drillHandler = mxUtils.bind(this, function(sender) + { + this.reset(); + }); + + this.graph.addListener(mxEvent.START_EDITING, this.drillHandler); + this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler); + this.graph.getView().addListener(mxEvent.UP, this.drillHandler); +}; + +/** + * Function: isConnectableCell + * + * Returns true if the given cell is connectable. This is a hook to + * disable floating connections. This implementation returns true. + */ +mxConnectionHandler.prototype.isConnectableCell = function(cell) +{ + return true; +}; + +/** + * Function: createMarker + * + * Creates and returns the used in . + */ +mxConnectionHandler.prototype.createMarker = function() +{ + var marker = new mxCellMarker(this.graph); + marker.hotspotEnabled = true; + + // Overrides to return cell at location only if valid (so that + // there is no highlight for invalid cells) + marker.getCell = mxUtils.bind(this, function(me) + { + var cell = mxCellMarker.prototype.getCell.apply(marker, arguments); + this.error = null; + + // Checks for cell at preview point (with grid) + if (cell == null && this.currentPoint != null) + { + cell = this.graph.getCellAt(this.currentPoint.x, this.currentPoint.y); + } + + // Uses connectable parent vertex if one exists + if (cell != null && !this.graph.isCellConnectable(cell)) + { + var parent = this.graph.getModel().getParent(cell); + + if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) + { + cell = parent; + } + } + + if ((this.graph.isSwimlane(cell) && this.currentPoint != null && + this.graph.hitsSwimlaneContent(cell, this.currentPoint.x, this.currentPoint.y)) || + !this.isConnectableCell(cell)) + { + cell = null; + } + + if (cell != null) + { + if (this.isConnecting()) + { + if (this.previous != null) + { + this.error = this.validateConnection(this.previous.cell, cell); + + if (this.error != null && this.error.length == 0) + { + cell = null; + + // Enables create target inside groups + if (this.isCreateTarget(me.getEvent())) + { + this.error = null; + } + } + } + } + else if (!this.isValidSource(cell, me)) + { + cell = null; + } + } + else if (this.isConnecting() && !this.isCreateTarget(me.getEvent()) && + !this.graph.allowDanglingEdges) + { + this.error = ''; + } + + return cell; + }); + + // Sets the highlight color according to validateConnection + marker.isValidState = mxUtils.bind(this, function(state) + { + if (this.isConnecting()) + { + return this.error == null; + } + else + { + return mxCellMarker.prototype.isValidState.apply(marker, arguments); + } + }); + + // Overrides to use marker color only in highlight mode or for + // target selection + marker.getMarkerColor = mxUtils.bind(this, function(evt, state, isValid) + { + return (this.connectImage == null || this.isConnecting()) ? + mxCellMarker.prototype.getMarkerColor.apply(marker, arguments) : + null; + }); + + // Overrides to use hotspot only for source selection otherwise + // intersects always returns true when over a cell + marker.intersects = mxUtils.bind(this, function(state, evt) + { + if (this.connectImage != null || this.isConnecting()) + { + return true; + } + + return mxCellMarker.prototype.intersects.apply(marker, arguments); + }); + + return marker; +}; + +/** + * Function: start + * + * Starts a new connection for the given state and coordinates. + */ +mxConnectionHandler.prototype.start = function(state, x, y, edgeState) +{ + this.previous = state; + this.first = new mxPoint(x, y); + this.edgeState = (edgeState != null) ? edgeState : this.createEdgeState(null); + + // Marks the source state + this.marker.currentColor = this.marker.validColor; + this.marker.markedState = state; + this.marker.mark(); + + this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous)); +}; + +/** + * Function: isConnecting + * + * Returns true if the source terminal has been clicked and a new + * connection is currently being previewed. + */ +mxConnectionHandler.prototype.isConnecting = function() +{ + return this.first != null && this.shape != null; +}; + +/** + * Function: isValidSource + * + * Returns for the given source terminal. + * + * Parameters: + * + * cell - that represents the source terminal. + * me - that is associated with this call. + */ +mxConnectionHandler.prototype.isValidSource = function(cell, me) +{ + return this.graph.isValidSource(cell); +}; + +/** + * Function: isValidTarget + * + * Returns true. The call to is implicit by calling + * in . This is an + * additional hook for disabling certain targets in this specific handler. + * + * Parameters: + * + * cell - that represents the target terminal. + */ +mxConnectionHandler.prototype.isValidTarget = function(cell) +{ + return true; +}; + +/** + * Function: validateConnection + * + * Returns the error message or an empty string if the connection for the + * given source target pair is not valid. Otherwise it returns null. This + * implementation uses . + * + * Parameters: + * + * source - that represents the source terminal. + * target - that represents the target terminal. + */ +mxConnectionHandler.prototype.validateConnection = function(source, target) +{ + if (!this.isValidTarget(target)) + { + return ''; + } + + return this.graph.getEdgeValidationError(null, source, target); +}; + +/** + * Function: getConnectImage + * + * Hook to return the used for the connection icon of the given + * . This implementation returns . + * + * Parameters: + * + * state - whose connect image should be returned. + */ +mxConnectionHandler.prototype.getConnectImage = function(state) +{ + return this.connectImage; +}; + +/** + * Function: isMoveIconToFrontForState + * + * Returns true if the state has a HTML label in the graph's container, otherwise + * it returns . + * + * Parameters: + * + * state - whose connect icons should be returned. + */ +mxConnectionHandler.prototype.isMoveIconToFrontForState = function(state) +{ + if (state.text != null && state.text.node.parentNode == this.graph.container) + { + return true; + } + + return this.moveIconFront; +}; + +/** + * Function: createIcons + * + * Creates the array that represent the connect icons for + * the given . + * + * Parameters: + * + * state - whose connect icons should be returned. + */ +mxConnectionHandler.prototype.createIcons = function(state) +{ + var image = this.getConnectImage(state); + + if (image != null && state != null) + { + this.iconState = state; + var icons = []; + + // Cannot use HTML for the connect icons because the icon receives all + // mouse move events in IE, must use VML and SVG instead even if the + // connect-icon appears behind the selection border and the selection + // border consumes the events before the icon gets a chance + var bounds = new mxRectangle(0, 0, image.width, image.height); + var icon = new mxImageShape(bounds, image.src, null, null, 0); + icon.preserveImageAspect = false; + + if (this.isMoveIconToFrontForState(state)) + { + icon.dialect = mxConstants.DIALECT_STRICTHTML; + icon.init(this.graph.container); + } + else + { + icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML; + icon.init(this.graph.getView().getOverlayPane()); + + // Move the icon back in the overlay pane + if (this.moveIconBack && icon.node.previousSibling != null) + { + icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild); + } + } + + icon.node.style.cursor = mxConstants.CURSOR_CONNECT; + + // Events transparency + var getState = mxUtils.bind(this, function() + { + return (this.currentState != null) ? this.currentState : state; + }); + + // Updates the local icon before firing the mouse down event. + var mouseDown = mxUtils.bind(this, function(evt) + { + if (!mxEvent.isConsumed(evt)) + { + this.icon = icon; + this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, + new mxMouseEvent(evt, getState())); + } + }); + + mxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown); + + icons.push(icon); + this.redrawIcons(icons, this.iconState); + + return icons; + } + + return null; +}; + +/** + * Function: redrawIcons + * + * Redraws the given array of . + * + * Parameters: + * + * icons - Optional array of to be redrawn. + */ +mxConnectionHandler.prototype.redrawIcons = function(icons, state) +{ + if (icons != null && icons[0] != null && state != null) + { + var pos = this.getIconPosition(icons[0], state); + icons[0].bounds.x = pos.x; + icons[0].bounds.y = pos.y; + icons[0].redraw(); + } +}; + +/** + * Function: redrawIcons + * + * Redraws the given array of . + * + * Parameters: + * + * icons - Optional array of to be redrawn. + */ +mxConnectionHandler.prototype.getIconPosition = function(icon, state) +{ + var scale = this.graph.getView().scale; + var cx = state.getCenterX(); + var cy = state.getCenterY(); + + if (this.graph.isSwimlane(state.cell)) + { + var size = this.graph.getStartSize(state.cell); + + cx = (size.width != 0) ? state.x + size.width * scale / 2 : cx; + cy = (size.height != 0) ? state.y + size.height * scale / 2 : cy; + + var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0); + + if (alpha != 0) + { + var cos = Math.cos(alpha); + var sin = Math.sin(alpha); + var ct = new mxPoint(state.getCenterX(), state.getCenterY()); + var pt = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin, ct); + cx = pt.x; + cy = pt.y; + } + } + + return new mxPoint(cx - icon.bounds.width / 2, + cy - icon.bounds.height / 2); +}; + +/** + * Function: destroyIcons + * + * Destroys the connect icons and resets the respective state. + */ +mxConnectionHandler.prototype.destroyIcons = function() +{ + if (this.icons != null) + { + for (var i = 0; i < this.icons.length; i++) + { + this.icons[i].destroy(); + } + + this.icons = null; + this.icon = null; + this.selectedIcon = null; + this.iconState = null; + } +}; + +/** + * Function: isStartEvent + * + * Returns true if the given mouse down event should start this handler. The + * This implementation returns true if the event does not force marquee + * selection, and the currentConstraint and currentFocus of the + * are not null, or and are not null and + * is null or and are not null. + */ +mxConnectionHandler.prototype.isStartEvent = function(me) +{ + return ((this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) || + (this.previous != null && this.error == null && (this.icons == null || (this.icons != null && + this.icon != null)))); +}; + +/** + * Function: mouseDown + * + * Handles the event by initiating a new connection. + */ +mxConnectionHandler.prototype.mouseDown = function(sender, me) +{ + this.mouseDownCounter++; + + if (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() && + !this.isConnecting() && this.isStartEvent(me)) + { + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null && + this.constraintHandler.currentPoint != null) + { + this.sourceConstraint = this.constraintHandler.currentConstraint; + this.previous = this.constraintHandler.currentFocus; + this.first = this.constraintHandler.currentPoint.clone(); + } + else + { + // Stores the location of the initial mousedown + this.first = new mxPoint(me.getGraphX(), me.getGraphY()); + } + + this.edgeState = this.createEdgeState(me); + this.mouseDownCounter = 1; + + if (this.waypointsEnabled && this.shape == null) + { + this.waypoints = null; + this.shape = this.createShape(); + + if (this.edgeState != null) + { + this.shape.apply(this.edgeState); + } + } + + // Stores the starting point in the geometry of the preview + if (this.previous == null && this.edgeState != null) + { + var pt = this.graph.getPointForEvent(me.getEvent()); + this.edgeState.cell.geometry.setTerminalPoint(pt, true); + } + + this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous)); + + me.consume(); + } + + this.selectedIcon = this.icon; + this.icon = null; +}; + +/** + * Function: isImmediateConnectSource + * + * Returns true if a tap on the given source state should immediately start + * connecting. This implementation returns true if the state is not movable + * in the graph. + */ +mxConnectionHandler.prototype.isImmediateConnectSource = function(state) +{ + return !this.graph.isCellMovable(state.cell); +}; + +/** + * Function: createEdgeState + * + * Hook to return an which may be used during the preview. + * This implementation returns null. + * + * Use the following code to create a preview for an existing edge style: + * + * (code) + * graph.connectionHandler.createEdgeState = function(me) + * { + * var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle'); + * + * return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge)); + * }; + * (end) + */ +mxConnectionHandler.prototype.createEdgeState = function(me) +{ + return null; +}; + +/** + * Function: isOutlineConnectEvent + * + * Returns true if is true and the source of the event is the outline shape + * or shift is pressed. + */ +mxConnectionHandler.prototype.isOutlineConnectEvent = function(me) +{ + var offset = mxUtils.getOffset(this.graph.container); + var evt = me.getEvent(); + + var clientX = mxEvent.getClientX(evt); + var clientY = mxEvent.getClientY(evt); + + var doc = document.documentElement; + var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0); + var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); + + var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left; + var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top; + + return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) && + (me.isSource(this.marker.highlight.shape) || + (mxEvent.isAltDown(me.getEvent()) && me.getState() != null) || + this.marker.highlight.isHighlightAt(clientX, clientY) || + ((gridX != clientX || gridY != clientY) && me.getState() == null && + this.marker.highlight.isHighlightAt(gridX, gridY))); +}; + +/** + * Function: updateCurrentState + * + * Updates the current state for a given mouse move event by using + * the . + */ +mxConnectionHandler.prototype.updateCurrentState = function(me, point) +{ + this.constraintHandler.update(me, this.first == null, false, (this.first == null || + me.isSource(this.marker.highlight.shape)) ? null : point); + + if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) + { + // Handles special case where grid is large and connection point is at actual point in which + // case the outline is not followed as long as we're < gridSize / 2 away from that point + if (this.marker.highlight != null && this.marker.highlight.state != null && + this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell) + { + // Direct repaint needed if cell already highlighted + if (this.marker.highlight.shape.stroke != 'transparent') + { + this.marker.highlight.shape.stroke = 'transparent'; + this.marker.highlight.repaint(); + } + } + else + { + this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent'); + } + + // Updates validation state + if (this.previous != null) + { + this.error = this.validateConnection(this.previous.cell, this.constraintHandler.currentFocus.cell); + + if (this.error == null) + { + this.currentState = this.constraintHandler.currentFocus; + } + else + { + this.constraintHandler.reset(); + } + } + } + else + { + if (this.graph.isIgnoreTerminalEvent(me.getEvent())) + { + this.marker.reset(); + this.currentState = null; + } + else + { + this.marker.process(me); + this.currentState = this.marker.getValidState(); + + if (this.currentState != null && !this.isCellEnabled(this.currentState.cell)) + { + this.currentState = null; + } + } + + var outline = this.isOutlineConnectEvent(me); + + if (this.currentState != null && outline) + { + // Handles special case where mouse is on outline away from actual end point + // in which case the grid is ignored and mouse point is used instead + if (me.isSource(this.marker.highlight.shape)) + { + point = new mxPoint(me.getGraphX(), me.getGraphY()); + } + + var constraint = this.graph.getOutlineConstraint(point, this.currentState, me); + this.constraintHandler.setFocus(me, this.currentState, false); + this.constraintHandler.currentConstraint = constraint; + this.constraintHandler.currentPoint = point; + } + + if (this.outlineConnect) + { + if (this.marker.highlight != null && this.marker.highlight.shape != null) + { + var s = this.graph.view.scale; + + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null) + { + this.marker.highlight.shape.stroke = mxConstants.OUTLINE_HIGHLIGHT_COLOR; + this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s; + this.marker.highlight.repaint(); + } + else if (this.marker.hasValidState()) + { + // Handles special case where actual end point of edge and current mouse point + // are not equal (due to grid snapping) and there is no hit on shape or highlight + if (this.marker.getValidState() != me.getState()) + { + this.marker.highlight.shape.stroke = 'transparent'; + this.currentState = null; + } + else + { + this.marker.highlight.shape.stroke = mxConstants.DEFAULT_VALID_COLOR; + } + + this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s; + this.marker.highlight.repaint(); + } + } + } + } +}; + +/** + * Function: isCellEnabled + * + * Returns true if the given cell does not allow new connections to be created. + */ +mxConnectionHandler.prototype.isCellEnabled = function(cell) +{ + return true; +}; + +/** + * Function: convertWaypoint + * + * Converts the given point from screen coordinates to model coordinates. + */ +mxConnectionHandler.prototype.convertWaypoint = function(point) +{ + var scale = this.graph.getView().getScale(); + var tr = this.graph.getView().getTranslate(); + + point.x = point.x / scale - tr.x; + point.y = point.y / scale - tr.y; +}; + +/** + * Function: snapToPreview + * + * Called to snap the given point to the current preview. This snaps to the + * first point of the preview if alt is not pressed. + */ +mxConnectionHandler.prototype.snapToPreview = function(me, point) +{ + if (!mxEvent.isAltDown(me.getEvent()) && this.previous != null) + { + var tol = this.graph.gridSize * this.graph.view.scale / 2; + var tmp = (this.sourceConstraint != null) ? this.first : + new mxPoint(this.previous.getCenterX(), this.previous.getCenterY()); + + if (Math.abs(tmp.x - me.getGraphX()) < tol) + { + point.x = tmp.x; + } + + if (Math.abs(tmp.y - me.getGraphY()) < tol) + { + point.y = tmp.y; + } + } +}; + +/** + * Function: mouseMove + * + * Handles the event by updating the preview edge or by highlighting + * a possible source or target terminal. + */ +mxConnectionHandler.prototype.mouseMove = function(sender, me) +{ + if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown)) + { + // Handles special case when handler is disabled during highlight + if (!this.isEnabled() && this.currentState != null) + { + this.destroyIcons(); + this.currentState = null; + } + + var view = this.graph.getView(); + var scale = view.scale; + var tr = view.translate; + var point = new mxPoint(me.getGraphX(), me.getGraphY()); + this.error = null; + + if (this.graph.isGridEnabledEvent(me.getEvent())) + { + point = new mxPoint((this.graph.snap(point.x / scale - tr.x) + tr.x) * scale, + (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale); + } + + this.snapToPreview(me, point); + this.currentPoint = point; + + if ((this.first != null || (this.isEnabled() && this.graph.isEnabled())) && + (this.shape != null || this.first == null || + Math.abs(me.getGraphX() - this.first.x) > this.graph.tolerance || + Math.abs(me.getGraphY() - this.first.y) > this.graph.tolerance)) + { + this.updateCurrentState(me, point); + } + + if (this.first != null) + { + var constraint = null; + var current = point; + + // Uses the current point from the constraint handler if available + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null && + this.constraintHandler.currentPoint != null) + { + constraint = this.constraintHandler.currentConstraint; + current = this.constraintHandler.currentPoint.clone(); + } + else if (this.previous != null && !this.graph.isIgnoreTerminalEvent(me.getEvent()) && + mxEvent.isShiftDown(me.getEvent())) + { + if (Math.abs(this.previous.getCenterX() - point.x) < + Math.abs(this.previous.getCenterY() - point.y)) + { + point.x = this.previous.getCenterX(); + } + else + { + point.y = this.previous.getCenterY(); + } + } + + var pt2 = this.first; + + // Moves the connect icon with the mouse + if (this.selectedIcon != null) + { + var w = this.selectedIcon.bounds.width; + var h = this.selectedIcon.bounds.height; + + if (this.currentState != null && this.targetConnectImage) + { + var pos = this.getIconPosition(this.selectedIcon, this.currentState); + this.selectedIcon.bounds.x = pos.x; + this.selectedIcon.bounds.y = pos.y; + } + else + { + var bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x, + me.getGraphY() + this.connectIconOffset.y, w, h); + this.selectedIcon.bounds = bounds; + } + + this.selectedIcon.redraw(); + } + + // Uses edge state to compute the terminal points + if (this.edgeState != null) + { + this.updateEdgeState(current, constraint); + current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1]; + pt2 = this.edgeState.absolutePoints[0]; + } + else + { + if (this.currentState != null) + { + if (this.constraintHandler.currentConstraint == null) + { + var tmp = this.getTargetPerimeterPoint(this.currentState, me); + + if (tmp != null) + { + current = tmp; + } + } + } + + // Computes the source perimeter point + if (this.sourceConstraint == null && this.previous != null) + { + var next = (this.waypoints != null && this.waypoints.length > 0) ? + this.waypoints[0] : current; + var tmp = this.getSourcePerimeterPoint(this.previous, next, me); + + if (tmp != null) + { + pt2 = tmp; + } + } + } + + // Makes sure the cell under the mousepointer can be detected + // by moving the preview shape away from the mouse. This + // makes sure the preview shape does not prevent the detection + // of the cell under the mousepointer even for slow gestures. + if (this.currentState == null && this.movePreviewAway) + { + var tmp = pt2; + + if (this.edgeState != null && this.edgeState.absolutePoints.length >= 2) + { + var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2]; + + if (tmp2 != null) + { + tmp = tmp2; + } + } + + var dx = current.x - tmp.x; + var dy = current.y - tmp.y; + + var len = Math.sqrt(dx * dx + dy * dy); + + if (len == 0) + { + return; + } + + // Stores old point to reuse when creating edge + this.originalPoint = current.clone(); + current.x -= dx * 4 / len; + current.y -= dy * 4 / len; + } + else + { + this.originalPoint = null; + } + + // Creates the preview shape (lazy) + if (this.shape == null) + { + var dx = Math.abs(me.getGraphX() - this.first.x); + var dy = Math.abs(me.getGraphY() - this.first.y); + + if (dx > this.graph.tolerance || dy > this.graph.tolerance) + { + this.shape = this.createShape(); + + if (this.edgeState != null) + { + this.shape.apply(this.edgeState); + } + + // Revalidates current connection + this.updateCurrentState(me, point); + } + } + + // Updates the points in the preview edge + if (this.shape != null) + { + if (this.edgeState != null) + { + this.shape.points = this.edgeState.absolutePoints; + } + else + { + var pts = [pt2]; + + if (this.waypoints != null) + { + pts = pts.concat(this.waypoints); + } + + pts.push(current); + this.shape.points = pts; + } + + this.drawPreview(); + } + + // Makes sure endpoint of edge is visible during connect + if (this.cursor != null) + { + this.graph.container.style.cursor = this.cursor; + } + + mxEvent.consume(me.getEvent()); + me.consume(); + } + else if (!this.isEnabled() || !this.graph.isEnabled()) + { + this.constraintHandler.reset(); + } + else if (this.previous != this.currentState && this.edgeState == null) + { + this.destroyIcons(); + + // Sets the cursor on the current shape + if (this.currentState != null && this.error == null && this.constraintHandler.currentConstraint == null) + { + this.icons = this.createIcons(this.currentState); + + if (this.icons == null) + { + this.currentState.setCursor(mxConstants.CURSOR_CONNECT); + me.consume(); + } + } + + this.previous = this.currentState; + } + else if (this.previous == this.currentState && this.currentState != null && this.icons == null && + !this.graph.isMouseDown) + { + // Makes sure that no cursors are changed + me.consume(); + } + + if (!this.graph.isMouseDown && this.currentState != null && this.icons != null) + { + var hitsIcon = false; + var target = me.getSource(); + + for (var i = 0; i < this.icons.length && !hitsIcon; i++) + { + hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node; + } + + if (!hitsIcon) + { + this.updateIcons(this.currentState, this.icons, me); + } + } + } + else + { + this.constraintHandler.reset(); + } +}; + +/** + * Function: updateEdgeState + * + * Updates . + */ +mxConnectionHandler.prototype.updateEdgeState = function(current, constraint) +{ + // TODO: Use generic method for writing constraint to style + if (this.sourceConstraint != null && this.sourceConstraint.point != null) + { + this.edgeState.style[mxConstants.STYLE_EXIT_X] = this.sourceConstraint.point.x; + this.edgeState.style[mxConstants.STYLE_EXIT_Y] = this.sourceConstraint.point.y; + } + + if (constraint != null && constraint.point != null) + { + this.edgeState.style[mxConstants.STYLE_ENTRY_X] = constraint.point.x; + this.edgeState.style[mxConstants.STYLE_ENTRY_Y] = constraint.point.y; + } + else + { + delete this.edgeState.style[mxConstants.STYLE_ENTRY_X]; + delete this.edgeState.style[mxConstants.STYLE_ENTRY_Y]; + } + + this.edgeState.absolutePoints = [null, (this.currentState != null) ? null : current]; + this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint); + + if (this.currentState != null) + { + if (constraint == null) + { + constraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false); + } + + this.edgeState.setAbsoluteTerminalPoint(null, false); + this.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint); + } + + // Scales and translates the waypoints to the model + var realPoints = null; + + if (this.waypoints != null) + { + realPoints = []; + + for (var i = 0; i < this.waypoints.length; i++) + { + var pt = this.waypoints[i].clone(); + this.convertWaypoint(pt); + realPoints[i] = pt; + } + } + + this.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState); + this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState); +}; + +/** + * Function: getTargetPerimeterPoint + * + * Returns the perimeter point for the given target state. + * + * Parameters: + * + * state - that represents the target cell state. + * me - that represents the mouse move. + */ +mxConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me) +{ + var result = null; + var view = state.view; + var targetPerimeter = view.getPerimeterFunction(state); + + if (targetPerimeter != null) + { + var next = (this.waypoints != null && this.waypoints.length > 0) ? + this.waypoints[this.waypoints.length - 1] : + new mxPoint(this.previous.getCenterX(), this.previous.getCenterY()); + var tmp = targetPerimeter(view.getPerimeterBounds(state), + this.edgeState, next, false); + + if (tmp != null) + { + result = tmp; + } + } + else + { + result = new mxPoint(state.getCenterX(), state.getCenterY()); + } + + return result; +}; + +/** + * Function: getSourcePerimeterPoint + * + * Hook to update the icon position(s) based on a mouseOver event. This is + * an empty implementation. + * + * Parameters: + * + * state - that represents the target cell state. + * next - that represents the next point along the previewed edge. + * me - that represents the mouse move. + */ +mxConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me) +{ + var result = null; + var view = state.view; + var sourcePerimeter = view.getPerimeterFunction(state); + var c = new mxPoint(state.getCenterX(), state.getCenterY()); + + if (sourcePerimeter != null) + { + var theta = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0); + var rad = -theta * (Math.PI / 180); + + if (theta != 0) + { + next = mxUtils.getRotatedPoint(new mxPoint(next.x, next.y), Math.cos(rad), Math.sin(rad), c); + } + + var tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false); + + if (tmp != null) + { + if (theta != 0) + { + tmp = mxUtils.getRotatedPoint(new mxPoint(tmp.x, tmp.y), Math.cos(-rad), Math.sin(-rad), c); + } + + result = tmp; + } + } + else + { + result = c; + } + + return result; +}; + + +/** + * Function: updateIcons + * + * Hook to update the icon position(s) based on a mouseOver event. This is + * an empty implementation. + * + * Parameters: + * + * state - under the mouse. + * icons - Array of currently displayed icons. + * me - that contains the mouse event. + */ +mxConnectionHandler.prototype.updateIcons = function(state, icons, me) +{ + // empty +}; + +/** + * Function: isStopEvent + * + * Returns true if the given mouse up event should stop this handler. The + * connection will be created if is null. Note that this is only + * called if is true. This implemtation returns true + * if there is a cell state in the given event. + */ +mxConnectionHandler.prototype.isStopEvent = function(me) +{ + return me.getState() != null; +}; + +/** + * Function: addWaypoint + * + * Adds the waypoint for the given event to . + */ +mxConnectionHandler.prototype.addWaypointForEvent = function(me) +{ + var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY()); + var dx = Math.abs(point.x - this.first.x); + var dy = Math.abs(point.y - this.first.y); + var addPoint = this.waypoints != null || (this.mouseDownCounter > 1 && + (dx > this.graph.tolerance || dy > this.graph.tolerance)); + + if (addPoint) + { + if (this.waypoints == null) + { + this.waypoints = []; + } + + var scale = this.graph.view.scale; + var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale, + this.graph.snap(me.getGraphY() / scale) * scale); + this.waypoints.push(point); + } +}; + +/** + * Function: checkConstraints + * + * Returns true if the connection for the given constraints is valid. This + * implementation returns true if the constraints are not pointing to the + * same fixed connection point. + */ +mxConnectionHandler.prototype.checkConstraints = function(c1, c2) +{ + return (c1 == null || c2 == null || c1.point == null || c2.point == null || + !c1.point.equals(c2.point) || c1.perimeter != c2.perimeter); +}; + +/** + * Function: mouseUp + * + * Handles the event by inserting the new connection. + */ +mxConnectionHandler.prototype.mouseUp = function(sender, me) +{ + if (!me.isConsumed() && this.isConnecting()) + { + if (this.waypointsEnabled && !this.isStopEvent(me)) + { + this.addWaypointForEvent(me); + me.consume(); + + return; + } + + var c1 = this.sourceConstraint; + var c2 = this.constraintHandler.currentConstraint; + + var source = (this.previous != null) ? this.previous.cell : null; + var target = null; + + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null) + { + target = this.constraintHandler.currentFocus.cell; + } + + if (target == null && this.currentState != null) + { + target = this.currentState.cell; + } + + // Inserts the edge if no validation error exists and if constraints differ + if (this.error == null && (source == null || target == null || + source != target || this.checkConstraints(c1, c2))) + { + this.connect(source, target, me.getEvent(), me.getCell()); + } + else + { + // Selects the source terminal for self-references + if (this.previous != null && this.marker.validState != null && + this.previous.cell == this.marker.validState.cell) + { + this.graph.selectCellForEvent(this.marker.source, me.getEvent()); + } + + // Displays the error message if it is not an empty string, + // for empty error messages, the event is silently dropped + if (this.error != null && this.error.length > 0) + { + this.graph.validationAlert(this.error); + } + } + + // Redraws the connect icons and resets the handler state + this.destroyIcons(); + me.consume(); + } + + if (this.first != null) + { + this.reset(); + } +}; + +/** + * Function: reset + * + * Resets the state of this handler. + */ +mxConnectionHandler.prototype.reset = function() +{ + if (this.shape != null) + { + this.shape.destroy(); + this.shape = null; + } + + // Resets the cursor on the container + if (this.cursor != null && this.graph.container != null) + { + this.graph.container.style.cursor = ''; + } + + this.destroyIcons(); + this.marker.reset(); + this.constraintHandler.reset(); + this.originalPoint = null; + this.currentPoint = null; + this.edgeState = null; + this.previous = null; + this.error = null; + this.sourceConstraint = null; + this.mouseDownCounter = 0; + this.first = null; + + this.fireEvent(new mxEventObject(mxEvent.RESET)); +}; + +/** + * Function: drawPreview + * + * Redraws the preview edge using the color and width returned by + * and . + */ +mxConnectionHandler.prototype.drawPreview = function() +{ + this.updatePreview(this.error == null); + this.shape.redraw(); +}; + +/** + * Function: getEdgeColor + * + * Returns the color used to draw the preview edge. This returns green if + * there is no edge validation error and red otherwise. + * + * Parameters: + * + * valid - Boolean indicating if the color for a valid edge should be + * returned. + */ +mxConnectionHandler.prototype.updatePreview = function(valid) +{ + this.shape.strokewidth = this.getEdgeWidth(valid); + this.shape.stroke = this.getEdgeColor(valid); +}; + +/** + * Function: getEdgeColor + * + * Returns the color used to draw the preview edge. This returns green if + * there is no edge validation error and red otherwise. + * + * Parameters: + * + * valid - Boolean indicating if the color for a valid edge should be + * returned. + */ +mxConnectionHandler.prototype.getEdgeColor = function(valid) +{ + return (valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR; +}; + +/** + * Function: getEdgeWidth + * + * Returns the width used to draw the preview edge. This returns 3 if + * there is no edge validation error and 1 otherwise. + * + * Parameters: + * + * valid - Boolean indicating if the width for a valid edge should be + * returned. + */ +mxConnectionHandler.prototype.getEdgeWidth = function(valid) +{ + return (valid) ? 3 : 1; +}; + +/** + * Function: connect + * + * Connects the given source and target using a new edge. This + * implementation uses to create the edge. + * + * Parameters: + * + * source - that represents the source terminal. + * target - that represents the target terminal. + * evt - Mousedown event of the connect gesture. + * dropTarget - that represents the cell under the mouse when it was + * released. + */ +mxConnectionHandler.prototype.connect = function(source, target, evt, dropTarget) +{ + if (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges) + { + // Uses the common parent of source and target or + // the default parent to insert the edge + var model = this.graph.getModel(); + var terminalInserted = false; + var edge = null; + + model.beginUpdate(); + try + { + if (source != null && target == null && !this.graph.isIgnoreTerminalEvent(evt) && this.isCreateTarget(evt)) + { + target = this.createTargetVertex(evt, source); + + if (target != null) + { + dropTarget = this.graph.getDropTarget([target], evt, dropTarget); + terminalInserted = true; + + // Disables edges as drop targets if the target cell was created + // FIXME: Should not shift if vertex was aligned (same in Java) + if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget)) + { + var pstate = this.graph.getView().getState(dropTarget); + + if (pstate != null) + { + var tmp = model.getGeometry(target); + tmp.x -= pstate.origin.x; + tmp.y -= pstate.origin.y; + } + } + else + { + dropTarget = this.graph.getDefaultParent(); + } + + this.graph.addCell(target, dropTarget); + } + } + + var parent = this.graph.getDefaultParent(); + + if (source != null && target != null && + model.getParent(source) == model.getParent(target) && + model.getParent(model.getParent(source)) != model.getRoot()) + { + parent = model.getParent(source); + + if ((source.geometry != null && source.geometry.relative) && + (target.geometry != null && target.geometry.relative)) + { + parent = model.getParent(parent); + } + } + + // Uses the value of the preview edge state for inserting + // the new edge into the graph + var value = null; + var style = null; + + if (this.edgeState != null) + { + value = this.edgeState.cell.value; + style = this.edgeState.cell.style; + } + + edge = this.insertEdge(parent, null, value, source, target, style); + + if (edge != null) + { + // Updates the connection constraints + this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint); + this.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint); + + // Uses geometry of the preview edge state + if (this.edgeState != null) + { + model.setGeometry(edge, this.edgeState.cell.geometry); + } + + var parent = model.getParent(source); + + // Inserts edge before source + if (this.isInsertBefore(edge, source, target, evt, dropTarget)) + { + var index = null; + var tmp = source; + + while (tmp.parent != null && tmp.geometry != null && + tmp.geometry.relative && tmp.parent != edge.parent) + { + tmp = this.graph.model.getParent(tmp); + } + + if (tmp != null && tmp.parent != null && tmp.parent == edge.parent) + { + model.add(parent, edge, tmp.parent.getIndex(tmp)); + } + } + + // Makes sure the edge has a non-null, relative geometry + var geo = model.getGeometry(edge); + + if (geo == null) + { + geo = new mxGeometry(); + geo.relative = true; + + model.setGeometry(edge, geo); + } + + // Uses scaled waypoints in geometry + if (this.waypoints != null && this.waypoints.length > 0) + { + var s = this.graph.view.scale; + var tr = this.graph.view.translate; + geo.points = []; + + for (var i = 0; i < this.waypoints.length; i++) + { + var pt = this.waypoints[i]; + geo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y)); + } + } + + if (target == null) + { + var t = this.graph.view.translate; + var s = this.graph.view.scale; + var pt = (this.originalPoint != null) ? + new mxPoint(this.originalPoint.x / s - t.x, this.originalPoint.y / s - t.y) : + new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y); + pt.x -= this.graph.panDx / this.graph.view.scale; + pt.y -= this.graph.panDy / this.graph.view.scale; + geo.setTerminalPoint(pt, false); + } + + this.fireEvent(new mxEventObject(mxEvent.CONNECT, 'cell', edge, 'terminal', target, + 'event', evt, 'target', dropTarget, 'terminalInserted', terminalInserted)); + } + } + catch (e) + { + mxLog.show(); + mxLog.debug(e.message); + } + finally + { + model.endUpdate(); + } + + if (this.select) + { + this.selectCells(edge, (terminalInserted) ? target : null); + } + } +}; + +/** + * Function: selectCells + * + * Selects the given edge after adding a new connection. The target argument + * contains the target vertex if one has been inserted. + */ +mxConnectionHandler.prototype.selectCells = function(edge, target) +{ + this.graph.setSelectionCell(edge); +}; + +/** + * Function: insertEdge + * + * Creates, inserts and returns the new edge for the given parameters. This + * implementation does only use if is defined, + * otherwise will be used. + */ +mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style) +{ + if (this.factoryMethod == null) + { + return this.graph.insertEdge(parent, id, value, source, target, style); + } + else + { + var edge = this.createEdge(value, source, target, style); + edge = this.graph.addEdge(edge, parent, source, target); + + return edge; + } +}; + +/** + * Function: createTargetVertex + * + * Hook method for creating new vertices on the fly if no target was + * under the mouse. This is only called if is true and + * returns null. + * + * Parameters: + * + * evt - Mousedown event of the connect gesture. + * source - that represents the source terminal. + */ +mxConnectionHandler.prototype.createTargetVertex = function(evt, source) +{ + // Uses the first non-relative source + var geo = this.graph.getCellGeometry(source); + + while (geo != null && geo.relative) + { + source = this.graph.getModel().getParent(source); + geo = this.graph.getCellGeometry(source); + } + + var clone = this.graph.cloneCell(source); + var geo = this.graph.getModel().getGeometry(clone); + + if (geo != null) + { + var t = this.graph.view.translate; + var s = this.graph.view.scale; + var point = new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y); + geo.x = Math.round(point.x - geo.width / 2 - this.graph.panDx / s); + geo.y = Math.round(point.y - geo.height / 2 - this.graph.panDy / s); + + // Aligns with source if within certain tolerance + var tol = this.getAlignmentTolerance(); + + if (tol > 0) + { + var sourceState = this.graph.view.getState(source); + + if (sourceState != null) + { + var x = sourceState.x / s - t.x; + var y = sourceState.y / s - t.y; + + if (Math.abs(x - geo.x) <= tol) + { + geo.x = Math.round(x); + } + + if (Math.abs(y - geo.y) <= tol) + { + geo.y = Math.round(y); + } + } + } + } + + return clone; +}; + +/** + * Function: getAlignmentTolerance + * + * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2. + */ +mxConnectionHandler.prototype.getAlignmentTolerance = function(evt) +{ + return (this.graph.isGridEnabled()) ? this.graph.gridSize / 2 : this.graph.tolerance; +}; + +/** + * Function: createEdge + * + * Creates and returns a new edge using if one exists. If + * no factory method is defined, then a new default edge is returned. The + * source and target arguments are informal, the actual connection is + * setup later by the caller of this function. + * + * Parameters: + * + * value - Value to be used for creating the edge. + * source - that represents the source terminal. + * target - that represents the target terminal. + * style - Optional style from the preview edge. + */ +mxConnectionHandler.prototype.createEdge = function(value, source, target, style) +{ + var edge = null; + + // Creates a new edge using the factoryMethod + if (this.factoryMethod != null) + { + edge = this.factoryMethod(source, target, style); + } + + if (edge == null) + { + edge = new mxCell(value || ''); + edge.setEdge(true); + edge.setStyle(style); + + var geo = new mxGeometry(); + geo.relative = true; + edge.setGeometry(geo); + } + + return edge; +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. This should be + * called on all instances. It is called automatically for the built-in + * instance created for each . + */ +mxConnectionHandler.prototype.destroy = function() +{ + this.graph.removeMouseListener(this); + + if (this.shape != null) + { + this.shape.destroy(); + this.shape = null; + } + + if (this.marker != null) + { + this.marker.destroy(); + this.marker = null; + } + + if (this.constraintHandler != null) + { + this.constraintHandler.destroy(); + this.constraintHandler = null; + } + + if (this.changeHandler != null) + { + this.graph.getModel().removeListener(this.changeHandler); + this.graph.getView().removeListener(this.changeHandler); + this.changeHandler = null; + } + + if (this.drillHandler != null) + { + this.graph.removeListener(this.drillHandler); + this.graph.getView().removeListener(this.drillHandler); + this.drillHandler = null; + } + + if (this.escapeHandler != null) + { + this.graph.removeListener(this.escapeHandler); + this.escapeHandler = null; + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxConstraintHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxConstraintHandler.js new file mode 100644 index 0000000..a4d2cb2 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxConstraintHandler.js @@ -0,0 +1,517 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxConstraintHandler + * + * Handles constraints on connection targets. This class is in charge of + * showing fixed points when the mouse is over a vertex and handles constraints + * to establish new connections. + * + * Constructor: mxConstraintHandler + * + * Constructs an new constraint handler. + * + * Parameters: + * + * graph - Reference to the enclosing . + * factoryMethod - Optional function to create the edge. The function takes + * the source and target as the first and second argument and + * returns the that represents the new edge. + */ +function mxConstraintHandler(graph) +{ + this.graph = graph; + + // Adds a graph model listener to update the current focus on changes + this.resetHandler = mxUtils.bind(this, function(sender, evt) + { + if (this.currentFocus != null && this.graph.view.getState(this.currentFocus.cell) == null) + { + this.reset(); + } + else + { + this.redraw(); + } + }); + + this.graph.model.addListener(mxEvent.CHANGE, this.resetHandler); + this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.resetHandler); + this.graph.view.addListener(mxEvent.TRANSLATE, this.resetHandler); + this.graph.view.addListener(mxEvent.SCALE, this.resetHandler); + this.graph.addListener(mxEvent.ROOT, this.resetHandler); +}; + +/** + * Variable: pointImage + * + * to be used as the image for fixed connection points. + */ +mxConstraintHandler.prototype.pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5); + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxConstraintHandler.prototype.graph = null; + +/** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ +mxConstraintHandler.prototype.enabled = true; + +/** + * Variable: highlightColor + * + * Specifies the color for the highlight. Default is . + */ +mxConstraintHandler.prototype.highlightColor = mxConstants.DEFAULT_VALID_COLOR; + +/** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns . + */ +mxConstraintHandler.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates . + * + * Parameters: + * + * enabled - Boolean that specifies the new enabled state. + */ +mxConstraintHandler.prototype.setEnabled = function(enabled) +{ + this.enabled = enabled; +}; + +/** + * Function: reset + * + * Resets the state of this handler. + */ +mxConstraintHandler.prototype.reset = function() +{ + if (this.focusIcons != null) + { + for (var i = 0; i < this.focusIcons.length; i++) + { + this.focusIcons[i].destroy(); + } + + this.focusIcons = null; + } + + if (this.focusHighlight != null) + { + this.focusHighlight.destroy(); + this.focusHighlight = null; + } + + this.currentConstraint = null; + this.currentFocusArea = null; + this.currentPoint = null; + this.currentFocus = null; + this.focusPoints = null; +}; + +/** + * Function: getTolerance + * + * Returns the tolerance to be used for intersecting connection points. This + * implementation returns . + * + * Parameters: + * + * me - whose tolerance should be returned. + */ +mxConstraintHandler.prototype.getTolerance = function(me) +{ + return this.graph.getTolerance(); +}; + +/** + * Function: getImageForConstraint + * + * Returns the tolerance to be used for intersecting connection points. + */ +mxConstraintHandler.prototype.getImageForConstraint = function(state, constraint, point) +{ + return this.pointImage; +}; + +/** + * Function: isEventIgnored + * + * Returns true if the given should be ignored in . This + * implementation always returns false. + */ +mxConstraintHandler.prototype.isEventIgnored = function(me, source) +{ + return false; +}; + +/** + * Function: isStateIgnored + * + * Returns true if the given state should be ignored. This always returns false. + */ +mxConstraintHandler.prototype.isStateIgnored = function(state, source) +{ + return false; +}; + +/** + * Function: destroyIcons + * + * Destroys the if they exist. + */ +mxConstraintHandler.prototype.destroyIcons = function() +{ + if (this.focusIcons != null) + { + for (var i = 0; i < this.focusIcons.length; i++) + { + this.focusIcons[i].destroy(); + } + + this.focusIcons = null; + this.focusPoints = null; + } +}; + +/** + * Function: destroyFocusHighlight + * + * Destroys the if one exists. + */ +mxConstraintHandler.prototype.destroyFocusHighlight = function() +{ + if (this.focusHighlight != null) + { + this.focusHighlight.destroy(); + this.focusHighlight = null; + } +}; + +/** + * Function: isKeepFocusEvent + * + * Returns true if the current focused state should not be changed for the given event. + * This returns true if shift and alt are pressed. + */ +mxConstraintHandler.prototype.isKeepFocusEvent = function(me) +{ + return mxEvent.isShiftDown(me.getEvent()); +}; + +/** + * Function: getCellForEvent + * + * Returns the cell for the given event. + */ +mxConstraintHandler.prototype.getCellForEvent = function(me, point) +{ + var cell = me.getCell(); + + // Gets cell under actual point if different from event location + if (cell == null && point != null && (me.getGraphX() != point.x || me.getGraphY() != point.y)) + { + cell = this.graph.getCellAt(point.x, point.y); + } + + // Uses connectable parent vertex if one exists + if (cell != null && !this.graph.isCellConnectable(cell)) + { + var parent = this.graph.getModel().getParent(cell); + + if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) + { + cell = parent; + } + } + + return (this.graph.isCellLocked(cell)) ? null : cell; +}; + +/** + * Function: update + * + * Updates the state of this handler based on the given . + * Source is a boolean indicating if the cell is a source or target. + */ +mxConstraintHandler.prototype.update = function(me, source, existingEdge, point) +{ + if (this.isEnabled() && !this.isEventIgnored(me)) + { + // Lazy installation of mouseleave handler + if (this.mouseleaveHandler == null && this.graph.container != null) + { + this.mouseleaveHandler = mxUtils.bind(this, function() + { + this.reset(); + }); + + mxEvent.addListener(this.graph.container, 'mouseleave', this.resetHandler); + } + + var tol = this.getTolerance(me); + var x = (point != null) ? point.x : me.getGraphX(); + var y = (point != null) ? point.y : me.getGraphY(); + var grid = new mxRectangle(x - tol, y - tol, 2 * tol, 2 * tol); + var mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol); + var state = this.graph.view.getState(this.getCellForEvent(me, point)); + + // Keeps focus icons visible while over vertex bounds and no other cell under mouse or shift is pressed + if (!this.isKeepFocusEvent(me) && (this.currentFocusArea == null || this.currentFocus == null || + (state != null) || !this.graph.getModel().isVertex(this.currentFocus.cell) || + !mxUtils.intersects(this.currentFocusArea, mouse)) && (state != this.currentFocus)) + { + this.currentFocusArea = null; + this.currentFocus = null; + this.setFocus(me, state, source); + } + + this.currentConstraint = null; + this.currentPoint = null; + var minDistSq = null; + + if (this.focusIcons != null && this.constraints != null && + (state == null || this.currentFocus == state)) + { + var cx = mouse.getCenterX(); + var cy = mouse.getCenterY(); + + for (var i = 0; i < this.focusIcons.length; i++) + { + var dx = cx - this.focusIcons[i].bounds.getCenterX(); + var dy = cy - this.focusIcons[i].bounds.getCenterY(); + var tmp = dx * dx + dy * dy; + + if ((this.intersects(this.focusIcons[i], mouse, source, existingEdge) || (point != null && + this.intersects(this.focusIcons[i], grid, source, existingEdge))) && + (minDistSq == null || tmp < minDistSq)) + { + this.currentConstraint = this.constraints[i]; + this.currentPoint = this.focusPoints[i]; + minDistSq = tmp; + + var tmp = this.focusIcons[i].bounds.clone(); + tmp.grow(mxConstants.HIGHLIGHT_SIZE + 1); + tmp.width -= 1; + tmp.height -= 1; + + if (this.focusHighlight == null) + { + var hl = this.createHighlightShape(); + hl.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML; + hl.pointerEvents = false; + + hl.init(this.graph.getView().getOverlayPane()); + this.focusHighlight = hl; + + var getState = mxUtils.bind(this, function() + { + return (this.currentFocus != null) ? this.currentFocus : state; + }); + + mxEvent.redirectMouseEvents(hl.node, this.graph, getState); + } + + this.focusHighlight.bounds = tmp; + this.focusHighlight.redraw(); + } + } + } + + if (this.currentConstraint == null) + { + this.destroyFocusHighlight(); + } + } + else + { + this.currentConstraint = null; + this.currentFocus = null; + this.currentPoint = null; + } +}; + +/** + * Function: redraw + * + * Transfers the focus to the given state as a source or target terminal. If + * the handler is not enabled then the outline is painted, but the constraints + * are ignored. + */ +mxConstraintHandler.prototype.redraw = function() +{ + if (this.currentFocus != null && this.constraints != null && this.focusIcons != null) + { + var state = this.graph.view.getState(this.currentFocus.cell); + this.currentFocus = state; + this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height); + + for (var i = 0; i < this.constraints.length; i++) + { + var cp = this.graph.getConnectionPoint(state, this.constraints[i]); + var img = this.getImageForConstraint(state, this.constraints[i], cp); + + var bounds = new mxRectangle(Math.round(cp.x - img.width / 2), + Math.round(cp.y - img.height / 2), img.width, img.height); + this.focusIcons[i].bounds = bounds; + this.focusIcons[i].redraw(); + this.currentFocusArea.add(this.focusIcons[i].bounds); + this.focusPoints[i] = cp; + } + } +}; + +/** + * Function: setFocus + * + * Transfers the focus to the given state as a source or target terminal. If + * the handler is not enabled then the outline is painted, but the constraints + * are ignored. + */ +mxConstraintHandler.prototype.setFocus = function(me, state, source) +{ + this.constraints = (state != null && !this.isStateIgnored(state, source) && + this.graph.isCellConnectable(state.cell)) ? ((this.isEnabled()) ? + (this.graph.getAllConnectionConstraints(state, source) || []) : []) : null; + + // Only uses cells which have constraints + if (this.constraints != null) + { + this.currentFocus = state; + this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height); + + if (this.focusIcons != null) + { + for (var i = 0; i < this.focusIcons.length; i++) + { + this.focusIcons[i].destroy(); + } + + this.focusIcons = null; + this.focusPoints = null; + } + + this.focusPoints = []; + this.focusIcons = []; + + for (var i = 0; i < this.constraints.length; i++) + { + var cp = this.graph.getConnectionPoint(state, this.constraints[i]); + var img = this.getImageForConstraint(state, this.constraints[i], cp); + + var src = img.src; + var bounds = new mxRectangle(Math.round(cp.x - img.width / 2), + Math.round(cp.y - img.height / 2), img.width, img.height); + var icon = new mxImageShape(bounds, src); + icon.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + icon.preserveImageAspect = false; + icon.init(this.graph.getView().getDecoratorPane()); + + // Fixes lost event tracking for images in quirks / IE8 standards + if (mxClient.IS_QUIRKS || document.documentMode == 8) + { + mxEvent.addListener(icon.node, 'dragstart', function(evt) + { + mxEvent.consume(evt); + + return false; + }); + } + + // Move the icon behind all other overlays + if (icon.node.previousSibling != null) + { + icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild); + } + + var getState = mxUtils.bind(this, function() + { + return (this.currentFocus != null) ? this.currentFocus : state; + }); + + icon.redraw(); + + mxEvent.redirectMouseEvents(icon.node, this.graph, getState); + this.currentFocusArea.add(icon.bounds); + this.focusIcons.push(icon); + this.focusPoints.push(cp); + } + + this.currentFocusArea.grow(this.getTolerance(me)); + } + else + { + this.destroyIcons(); + this.destroyFocusHighlight(); + } +}; + +/** + * Function: createHighlightShape + * + * Create the shape used to paint the highlight. + * + * Returns true if the given icon intersects the given point. + */ +mxConstraintHandler.prototype.createHighlightShape = function() +{ + var hl = new mxRectangleShape(null, this.highlightColor, this.highlightColor, mxConstants.HIGHLIGHT_STROKEWIDTH); + hl.opacity = mxConstants.HIGHLIGHT_OPACITY; + + return hl; +}; + +/** + * Function: intersects + * + * Returns true if the given icon intersects the given rectangle. + */ +mxConstraintHandler.prototype.intersects = function(icon, mouse, source, existingEdge) +{ + return mxUtils.intersects(icon.bounds, mouse); +}; + +/** + * Function: destroy + * + * Destroy this handler. + */ +mxConstraintHandler.prototype.destroy = function() +{ + this.reset(); + + if (this.resetHandler != null) + { + this.graph.model.removeListener(this.resetHandler); + this.graph.view.removeListener(this.resetHandler); + this.graph.removeListener(this.resetHandler); + this.resetHandler = null; + } + + if (this.mouseleaveHandler != null && this.graph.container != null) + { + mxEvent.removeListener(this.graph.container, 'mouseleave', this.mouseleaveHandler); + this.mouseleaveHandler = null; + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxEdgeHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxEdgeHandler.js new file mode 100644 index 0000000..442541c --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxEdgeHandler.js @@ -0,0 +1,2450 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxEdgeHandler + * + * Graph event handler that reconnects edges and modifies control points and + * the edge label location. Uses for finding and + * highlighting new source and target vertices. This handler is automatically + * created in for each selected edge. + * + * To enable adding/removing control points, the following code can be used: + * + * (code) + * mxEdgeHandler.prototype.addEnabled = true; + * mxEdgeHandler.prototype.removeEnabled = true; + * (end) + * + * Note: This experimental feature is not recommended for production use. + * + * Constructor: mxEdgeHandler + * + * Constructs an edge handler for the specified . + * + * Parameters: + * + * state - of the cell to be handled. + */ +function mxEdgeHandler(state) +{ + if (state != null) + { + this.state = state; + this.init(); + + // Handles escape keystrokes + this.escapeHandler = mxUtils.bind(this, function(sender, evt) + { + var dirty = this.index != null; + this.reset(); + + if (dirty) + { + this.graph.cellRenderer.redraw(this.state, false, state.view.isRendering()); + } + }); + + this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); + } +}; + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxEdgeHandler.prototype.graph = null; + +/** + * Variable: state + * + * Reference to the being modified. + */ +mxEdgeHandler.prototype.state = null; + +/** + * Variable: marker + * + * Holds the which is used for highlighting terminals. + */ +mxEdgeHandler.prototype.marker = null; + +/** + * Variable: constraintHandler + * + * Holds the used for drawing and highlighting + * constraints. + */ +mxEdgeHandler.prototype.constraintHandler = null; + +/** + * Variable: error + * + * Holds the current validation error while a connection is being changed. + */ +mxEdgeHandler.prototype.error = null; + +/** + * Variable: shape + * + * Holds the that represents the preview edge. + */ +mxEdgeHandler.prototype.shape = null; + +/** + * Variable: bends + * + * Holds the that represent the points. + */ +mxEdgeHandler.prototype.bends = null; + +/** + * Variable: labelShape + * + * Holds the that represents the label position. + */ +mxEdgeHandler.prototype.labelShape = null; + +/** + * Variable: cloneEnabled + * + * Specifies if cloning by control-drag is enabled. Default is true. + */ +mxEdgeHandler.prototype.cloneEnabled = true; + +/** + * Variable: addEnabled + * + * Specifies if adding bends by shift-click is enabled. Default is false. + * Note: This experimental feature is not recommended for production use. + */ +mxEdgeHandler.prototype.addEnabled = false; + +/** + * Variable: removeEnabled + * + * Specifies if removing bends by shift-click is enabled. Default is false. + * Note: This experimental feature is not recommended for production use. + */ +mxEdgeHandler.prototype.removeEnabled = false; + +/** + * Variable: dblClickRemoveEnabled + * + * Specifies if removing bends by double click is enabled. Default is false. + */ +mxEdgeHandler.prototype.dblClickRemoveEnabled = false; + +/** + * Variable: mergeRemoveEnabled + * + * Specifies if removing bends by dropping them on other bends is enabled. + * Default is false. + */ +mxEdgeHandler.prototype.mergeRemoveEnabled = false; + +/** + * Variable: straightRemoveEnabled + * + * Specifies if removing bends by creating straight segments should be enabled. + * If enabled, this can be overridden by holding down the alt key while moving. + * Default is false. + */ +mxEdgeHandler.prototype.straightRemoveEnabled = false; + +/** + * Variable: virtualBendsEnabled + * + * Specifies if virtual bends should be added in the center of each + * segments. These bends can then be used to add new waypoints. + * Default is false. + */ +mxEdgeHandler.prototype.virtualBendsEnabled = false; + +/** + * Variable: virtualBendOpacity + * + * Opacity to be used for virtual bends (see ). + * Default is 20. + */ +mxEdgeHandler.prototype.virtualBendOpacity = 20; + +/** + * Variable: parentHighlightEnabled + * + * Specifies if the parent should be highlighted if a child cell is selected. + * Default is false. + */ +mxEdgeHandler.prototype.parentHighlightEnabled = false; + +/** + * Variable: preferHtml + * + * Specifies if bends should be added to the graph container. This is updated + * in based on whether the edge or one of its terminals has an HTML + * label in the container. + */ +mxEdgeHandler.prototype.preferHtml = false; + +/** + * Variable: allowHandleBoundsCheck + * + * Specifies if the bounds of handles should be used for hit-detection in IE + * Default is true. + */ +mxEdgeHandler.prototype.allowHandleBoundsCheck = true; + +/** + * Variable: snapToTerminals + * + * Specifies if waypoints should snap to the routing centers of terminals. + * Default is false. + */ +mxEdgeHandler.prototype.snapToTerminals = false; + +/** + * Variable: handleImage + * + * Optional to be used as handles. Default is null. + */ +mxEdgeHandler.prototype.handleImage = null; + +/** + * Variable: tolerance + * + * Optional tolerance for hit-detection in . Default is 0. + */ +mxEdgeHandler.prototype.tolerance = 0; + +/** + * Variable: outlineConnect + * + * Specifies if connections to the outline of a highlighted target should be + * enabled. This will allow to place the connection point along the outline of + * the highlighted target. Default is false. + */ +mxEdgeHandler.prototype.outlineConnect = false; + +/** + * Variable: manageLabelHandle + * + * Specifies if the label handle should be moved if it intersects with another + * handle. Uses for checking and moving. Default is false. + */ +mxEdgeHandler.prototype.manageLabelHandle = false; + +/** + * Function: init + * + * Initializes the shapes required for this edge handler. + */ +mxEdgeHandler.prototype.init = function() +{ + this.graph = this.state.view.graph; + this.marker = this.createMarker(); + this.constraintHandler = new mxConstraintHandler(this.graph); + + // Clones the original points from the cell + // and makes sure at least one point exists + this.points = []; + + // Uses the absolute points of the state + // for the initial configuration and preview + this.abspoints = this.getSelectionPoints(this.state); + this.shape = this.createSelectionShape(this.abspoints); + this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + this.shape.init(this.graph.getView().getOverlayPane()); + this.shape.pointerEvents = false; + this.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE); + mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state); + + // Updates preferHtml + this.preferHtml = this.state.text != null && + this.state.text.node.parentNode == this.graph.container; + + if (!this.preferHtml) + { + // Checks source terminal + var sourceState = this.state.getVisibleTerminalState(true); + + if (sourceState != null) + { + this.preferHtml = sourceState.text != null && + sourceState.text.node.parentNode == this.graph.container; + } + + if (!this.preferHtml) + { + // Checks target terminal + var targetState = this.state.getVisibleTerminalState(false); + + if (targetState != null) + { + this.preferHtml = targetState.text != null && + targetState.text.node.parentNode == this.graph.container; + } + } + } + + // Adds highlight for parent group + if (this.parentHighlightEnabled) + { + var parent = this.graph.model.getParent(this.state.cell); + + if (this.graph.model.isVertex(parent)) + { + var pstate = this.graph.view.getState(parent); + + if (pstate != null) + { + this.parentHighlight = this.createParentHighlightShape(pstate); + // VML dialect required here for event transparency in IE + this.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + this.parentHighlight.pointerEvents = false; + this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0'); + this.parentHighlight.init(this.graph.getView().getOverlayPane()); + } + } + } + + // Creates bends for the non-routed absolute points + // or bends that don't correspond to points + if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells || + mxGraphHandler.prototype.maxCells <= 0) + { + this.bends = this.createBends(); + + if (this.isVirtualBendsEnabled()) + { + this.virtualBends = this.createVirtualBends(); + } + } + + // Adds a rectangular handle for the label position + this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y); + this.labelShape = this.createLabelHandleShape(); + this.initBend(this.labelShape); + this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE); + + this.customHandles = this.createCustomHandles(); + + this.redraw(); +}; + +/** + * Function: createCustomHandles + * + * Returns an array of custom handles. This implementation returns null. + */ +mxEdgeHandler.prototype.createCustomHandles = function() +{ + return null; +}; + +/** + * Function: isVirtualBendsEnabled + * + * Returns true if virtual bends should be added. This returns true if + * is true and the current style allows and + * renders custom waypoints. + */ +mxEdgeHandler.prototype.isVirtualBendsEnabled = function(evt) +{ + return this.virtualBendsEnabled && (this.state.style[mxConstants.STYLE_EDGE] == null || + this.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE || + this.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1) && + mxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow'; +}; + +/** + * Function: isAddPointEvent + * + * Returns true if the given event is a trigger to add a new point. This + * implementation returns true if shift is pressed. + */ +mxEdgeHandler.prototype.isAddPointEvent = function(evt) +{ + return mxEvent.isShiftDown(evt); +}; + +/** + * Function: isRemovePointEvent + * + * Returns true if the given event is a trigger to remove a point. This + * implementation returns true if shift is pressed. + */ +mxEdgeHandler.prototype.isRemovePointEvent = function(evt) +{ + return mxEvent.isShiftDown(evt); +}; + +/** + * Function: getSelectionPoints + * + * Returns the list of points that defines the selection stroke. + */ +mxEdgeHandler.prototype.getSelectionPoints = function(state) +{ + return state.absolutePoints; +}; + +/** + * Function: createSelectionShape + * + * Creates the shape used to draw the selection border. + */ +mxEdgeHandler.prototype.createParentHighlightShape = function(bounds) +{ + var shape = new mxRectangleShape(bounds, null, this.getSelectionColor()); + shape.strokewidth = this.getSelectionStrokeWidth(); + shape.isDashed = this.isSelectionDashed(); + + return shape; +}; + +/** + * Function: createSelectionShape + * + * Creates the shape used to draw the selection border. + */ +mxEdgeHandler.prototype.createSelectionShape = function(points) +{ + var shape = new this.state.shape.constructor(); + shape.outline = true; + shape.apply(this.state); + + shape.isDashed = this.isSelectionDashed(); + shape.stroke = this.getSelectionColor(); + shape.isShadow = false; + + return shape; +}; + +/** + * Function: getSelectionColor + * + * Returns . + */ +mxEdgeHandler.prototype.getSelectionColor = function() +{ + return mxConstants.EDGE_SELECTION_COLOR; +}; + +/** + * Function: getSelectionStrokeWidth + * + * Returns . + */ +mxEdgeHandler.prototype.getSelectionStrokeWidth = function() +{ + return mxConstants.EDGE_SELECTION_STROKEWIDTH; +}; + +/** + * Function: isSelectionDashed + * + * Returns . + */ +mxEdgeHandler.prototype.isSelectionDashed = function() +{ + return mxConstants.EDGE_SELECTION_DASHED; +}; + +/** + * Function: isConnectableCell + * + * Returns true if the given cell is connectable. This is a hook to + * disable floating connections. This implementation returns true. + */ +mxEdgeHandler.prototype.isConnectableCell = function(cell) +{ + return true; +}; + +/** + * Function: getCellAt + * + * Creates and returns the used in . + */ +mxEdgeHandler.prototype.getCellAt = function(x, y) +{ + return (!this.outlineConnect) ? this.graph.getCellAt(x, y) : null; +}; + +/** + * Function: createMarker + * + * Creates and returns the used in . + */ +mxEdgeHandler.prototype.createMarker = function() +{ + var marker = new mxCellMarker(this.graph); + var self = this; // closure + + // Only returns edges if they are connectable and never returns + // the edge that is currently being modified + marker.getCell = function(me) + { + var cell = mxCellMarker.prototype.getCell.apply(this, arguments); + + // Checks for cell at preview point (with grid) + if ((cell == self.state.cell || cell == null) && self.currentPoint != null) + { + cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y); + } + + // Uses connectable parent vertex if one exists + if (cell != null && !this.graph.isCellConnectable(cell)) + { + var parent = this.graph.getModel().getParent(cell); + + if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) + { + cell = parent; + } + } + + var model = self.graph.getModel(); + + if ((this.graph.isSwimlane(cell) && self.currentPoint != null && + this.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) || + (!self.isConnectableCell(cell)) || (cell == self.state.cell || + (cell != null && !self.graph.connectableEdges && model.isEdge(cell))) || + model.isAncestor(self.state.cell, cell)) + { + cell = null; + } + + if (!this.graph.isCellConnectable(cell)) + { + cell = null; + } + + return cell; + }; + + // Sets the highlight color according to validateConnection + marker.isValidState = function(state) + { + var model = self.graph.getModel(); + var other = self.graph.view.getTerminalPort(state, + self.graph.view.getState(model.getTerminal(self.state.cell, + !self.isSource)), !self.isSource); + var otherCell = (other != null) ? other.cell : null; + var source = (self.isSource) ? state.cell : otherCell; + var target = (self.isSource) ? otherCell : state.cell; + + // Updates the error message of the handler + self.error = self.validateConnection(source, target); + + return self.error == null; + }; + + return marker; +}; + +/** + * Function: validateConnection + * + * Returns the error message or an empty string if the connection for the + * given source, target pair is not valid. Otherwise it returns null. This + * implementation uses . + * + * Parameters: + * + * source - that represents the source terminal. + * target - that represents the target terminal. + */ +mxEdgeHandler.prototype.validateConnection = function(source, target) +{ + return this.graph.getEdgeValidationError(this.state.cell, source, target); +}; + +/** + * Function: createBends + * + * Creates and returns the bends used for modifying the edge. This is + * typically an array of . + */ + mxEdgeHandler.prototype.createBends = function() + { + var cell = this.state.cell; + var bends = []; + + for (var i = 0; i < this.abspoints.length; i++) + { + if (this.isHandleVisible(i)) + { + var source = i == 0; + var target = i == this.abspoints.length - 1; + var terminal = source || target; + + if (terminal || this.graph.isCellBendable(cell)) + { + (mxUtils.bind(this, function(index) + { + var bend = this.createHandleShape(index); + this.initBend(bend, mxUtils.bind(this, mxUtils.bind(this, function() + { + if (this.dblClickRemoveEnabled) + { + this.removePoint(this.state, index); + } + }))); + + if (this.isHandleEnabled(i)) + { + bend.setCursor((terminal) ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE); + } + + bends.push(bend); + + if (!terminal) + { + this.points.push(new mxPoint(0,0)); + bend.node.style.visibility = 'hidden'; + } + }))(i); + } + } + } + + return bends; +}; + +/** + * Function: createVirtualBends + * + * Creates and returns the bends used for modifying the edge. This is + * typically an array of . + */ + mxEdgeHandler.prototype.createVirtualBends = function() + { + var cell = this.state.cell; + var last = this.abspoints[0]; + var bends = []; + + if (this.graph.isCellBendable(cell)) + { + for (var i = 1; i < this.abspoints.length; i++) + { + (mxUtils.bind(this, function(bend) + { + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE); + bends.push(bend); + }))(this.createHandleShape()); + } + } + + return bends; +}; + +/** + * Function: isHandleEnabled + * + * Creates the shape used to display the given bend. + */ +mxEdgeHandler.prototype.isHandleEnabled = function(index) +{ + return true; +}; + +/** + * Function: isHandleVisible + * + * Returns true if the handle at the given index is visible. + */ +mxEdgeHandler.prototype.isHandleVisible = function(index) +{ + var source = this.state.getVisibleTerminalState(true); + var target = this.state.getVisibleTerminalState(false); + var geo = this.graph.getCellGeometry(this.state.cell); + var edgeStyle = (geo != null) ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null; + + return edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1; +}; + +/** + * Function: createHandleShape + * + * Creates the shape used to display the given bend. Note that the index may be + * null for special cases, such as when called from + * . Only images and rectangles should be + * returned if support for HTML labels with not foreign objects is required. + * Index if null for virtual handles. + */ +mxEdgeHandler.prototype.createHandleShape = function(index) +{ + if (this.handleImage != null) + { + var shape = new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src); + + // Allows HTML rendering of the images + shape.preserveImageAspect = false; + + return shape; + } + else + { + var s = mxConstants.HANDLE_SIZE; + + if (this.preferHtml) + { + s -= 1; + } + + return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); + } +}; + +/** + * Function: createLabelHandleShape + * + * Creates the shape used to display the the label handle. + */ +mxEdgeHandler.prototype.createLabelHandleShape = function() +{ + if (this.labelHandleImage != null) + { + var shape = new mxImageShape(new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src); + + // Allows HTML rendering of the images + shape.preserveImageAspect = false; + + return shape; + } + else + { + var s = mxConstants.LABEL_HANDLE_SIZE; + return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); + } +}; + +/** + * Function: initBend + * + * Helper method to initialize the given bend. + * + * Parameters: + * + * bend - that represents the bend to be initialized. + */ +mxEdgeHandler.prototype.initBend = function(bend, dblClick) +{ + if (this.preferHtml) + { + bend.dialect = mxConstants.DIALECT_STRICTHTML; + bend.init(this.graph.container); + } + else + { + bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + bend.init(this.graph.getView().getOverlayPane()); + } + + mxEvent.redirectMouseEvents(bend.node, this.graph, this.state, + null, null, null, dblClick); + + // Fixes lost event tracking for images in quirks / IE8 standards + if (mxClient.IS_QUIRKS || document.documentMode == 8) + { + mxEvent.addListener(bend.node, 'dragstart', function(evt) + { + mxEvent.consume(evt); + + return false; + }); + } + + if (mxClient.IS_TOUCH) + { + bend.node.setAttribute('pointer-events', 'none'); + } +}; + +/** + * Function: getHandleForEvent + * + * Returns the index of the handle for the given event. + */ +mxEdgeHandler.prototype.getHandleForEvent = function(me) +{ + // Connection highlight may consume events before they reach sizer handle + var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1; + var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ? + new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null; + var minDistSq = null; + var result = null; + + function checkShape(shape) + { + if (shape != null && shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden' && + (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit)))) + { + var dx = me.getGraphX() - shape.bounds.getCenterX(); + var dy = me.getGraphY() - shape.bounds.getCenterY(); + var tmp = dx * dx + dy * dy; + + if (minDistSq == null || tmp <= minDistSq) + { + minDistSq = tmp; + + return true; + } + } + + return false; + } + + if (this.customHandles != null && this.isCustomHandleEvent(me)) + { + // Inverse loop order to match display order + for (var i = this.customHandles.length - 1; i >= 0; i--) + { + if (checkShape(this.customHandles[i].shape)) + { + // LATER: Return reference to active shape + return mxEvent.CUSTOM_HANDLE - i; + } + } + } + + if (me.isSource(this.state.text) || checkShape(this.labelShape)) + { + result = mxEvent.LABEL_HANDLE; + } + + if (this.bends != null) + { + for (var i = 0; i < this.bends.length; i++) + { + if (checkShape(this.bends[i])) + { + result = i; + } + } + } + + if (this.virtualBends != null && this.isAddVirtualBendEvent(me)) + { + for (var i = 0; i < this.virtualBends.length; i++) + { + if (checkShape(this.virtualBends[i])) + { + result = mxEvent.VIRTUAL_HANDLE - i; + } + } + } + + return result; +}; + +/** + * Function: isAddVirtualBendEvent + * + * Returns true if the given event allows virtual bends to be added. This + * implementation returns true. + */ +mxEdgeHandler.prototype.isAddVirtualBendEvent = function(me) +{ + return true; +}; + +/** + * Function: isCustomHandleEvent + * + * Returns true if the given event allows custom handles to be changed. This + * implementation returns true. + */ +mxEdgeHandler.prototype.isCustomHandleEvent = function(me) +{ + return true; +}; + +/** + * Function: mouseDown + * + * Handles the event by checking if a special element of the handler + * was clicked, in which case the index parameter is non-null. The + * indices may be one of or the number of the respective + * control point. The source and target points are used for reconnecting + * the edge. + */ +mxEdgeHandler.prototype.mouseDown = function(sender, me) +{ + var handle = this.getHandleForEvent(me); + + if (this.bends != null && this.bends[handle] != null) + { + var b = this.bends[handle].bounds; + this.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY()); + } + + if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent())) + { + this.addPoint(this.state, me.getEvent()); + me.consume(); + } + else if (handle != null && !me.isConsumed() && this.graph.isEnabled()) + { + if (this.removeEnabled && this.isRemovePointEvent(me.getEvent())) + { + this.removePoint(this.state, handle); + } + else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell())) + { + if (handle <= mxEvent.VIRTUAL_HANDLE) + { + mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100); + } + + this.start(me.getX(), me.getY(), handle); + } + + me.consume(); + } +}; + +/** + * Function: start + * + * Starts the handling of the mouse gesture. + */ +mxEdgeHandler.prototype.start = function(x, y, index) +{ + this.startX = x; + this.startY = y; + + this.isSource = (this.bends == null) ? false : index == 0; + this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1; + this.isLabel = index == mxEvent.LABEL_HANDLE; + + if (this.isSource || this.isTarget) + { + var cell = this.state.cell; + var terminal = this.graph.model.getTerminal(cell, this.isSource); + + if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) || + (terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource))) + { + this.index = index; + } + } + else + { + this.index = index; + } + + // Hides other custom handles + if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) + { + if (this.customHandles != null) + { + for (var i = 0; i < this.customHandles.length; i++) + { + if (i != mxEvent.CUSTOM_HANDLE - this.index) + { + this.customHandles[i].setVisible(false); + } + } + } + } +}; + +/** + * Function: clonePreviewState + * + * Returns a clone of the current preview state for the given point and terminal. + */ +mxEdgeHandler.prototype.clonePreviewState = function(point, terminal) +{ + return this.state.clone(); +}; + +/** + * Function: getSnapToTerminalTolerance + * + * Returns the tolerance for the guides. Default value is + * gridSize * scale / 2. + */ +mxEdgeHandler.prototype.getSnapToTerminalTolerance = function() +{ + return this.graph.gridSize * this.graph.view.scale / 2; +}; + +/** + * Function: updateHint + * + * Hook for subclassers do show details while the handler is active. + */ +mxEdgeHandler.prototype.updateHint = function(me, point) { }; + +/** + * Function: removeHint + * + * Hooks for subclassers to hide details when the handler gets inactive. + */ +mxEdgeHandler.prototype.removeHint = function() { }; + +/** + * Function: roundLength + * + * Hook for rounding the unscaled width or height. This uses Math.round. + */ +mxEdgeHandler.prototype.roundLength = function(length) +{ + return Math.round(length); +}; + +/** + * Function: isSnapToTerminalsEvent + * + * Returns true if is true and if alt is not pressed. + */ +mxEdgeHandler.prototype.isSnapToTerminalsEvent = function(me) +{ + return this.snapToTerminals && !mxEvent.isAltDown(me.getEvent()); +}; + +/** + * Function: getPointForEvent + * + * Returns the point for the given event. + */ +mxEdgeHandler.prototype.getPointForEvent = function(me) +{ + var view = this.graph.getView(); + var scale = view.scale; + var point = new mxPoint(this.roundLength(me.getGraphX() / scale) * scale, + this.roundLength(me.getGraphY() / scale) * scale); + + var tt = this.getSnapToTerminalTolerance(); + var overrideX = false; + var overrideY = false; + + if (tt > 0 && this.isSnapToTerminalsEvent(me)) + { + function snapToPoint(pt) + { + if (pt != null) + { + var x = pt.x; + + if (Math.abs(point.x - x) < tt) + { + point.x = x; + overrideX = true; + } + + var y = pt.y; + + if (Math.abs(point.y - y) < tt) + { + point.y = y; + overrideY = true; + } + } + } + + // Temporary function + function snapToTerminal(terminal) + { + if (terminal != null) + { + snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal), + view.getRoutingCenterY(terminal))); + } + }; + + snapToTerminal.call(this, this.state.getVisibleTerminalState(true)); + snapToTerminal.call(this, this.state.getVisibleTerminalState(false)); + + if (this.state.absolutePoints != null) + { + for (var i = 0; i < this.state.absolutePoints.length; i++) + { + snapToPoint.call(this, this.state.absolutePoints[i]); + } + } + } + + if (this.graph.isGridEnabledEvent(me.getEvent())) + { + var tr = view.translate; + + if (!overrideX) + { + point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale; + } + + if (!overrideY) + { + point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale; + } + } + + return point; +}; + +/** + * Function: getPreviewTerminalState + * + * Updates the given preview state taking into account the state of the constraint handler. + */ +mxEdgeHandler.prototype.getPreviewTerminalState = function(me) +{ + this.constraintHandler.update(me, this.isSource, true, me.isSource(this.marker.highlight.shape) ? null : this.currentPoint); + + if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) + { + // Handles special case where grid is large and connection point is at actual point in which + // case the outline is not followed as long as we're < gridSize / 2 away from that point + if (this.marker.highlight != null && this.marker.highlight.state != null && + this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell) + { + // Direct repaint needed if cell already highlighted + if (this.marker.highlight.shape.stroke != 'transparent') + { + this.marker.highlight.shape.stroke = 'transparent'; + this.marker.highlight.repaint(); + } + } + else + { + this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent'); + } + + var model = this.graph.getModel(); + var other = this.graph.view.getTerminalPort(this.state, + this.graph.view.getState(model.getTerminal(this.state.cell, + !this.isSource)), !this.isSource); + var otherCell = (other != null) ? other.cell : null; + var source = (this.isSource) ? this.constraintHandler.currentFocus.cell : otherCell; + var target = (this.isSource) ? otherCell : this.constraintHandler.currentFocus.cell; + + // Updates the error message of the handler + this.error = this.validateConnection(source, target); + var result = null; + + if (this.error == null) + { + result = this.constraintHandler.currentFocus; + } + else + { + this.constraintHandler.reset(); + } + + return result; + } + else if (!this.graph.isIgnoreTerminalEvent(me.getEvent())) + { + this.marker.process(me); + var state = this.marker.getValidState(); + + if (state != null && this.graph.isCellLocked(state.cell)) + { + this.marker.reset(); + } + + return this.marker.getValidState(); + } + else + { + this.marker.reset(); + + return null; + } +}; + +/** + * Function: getPreviewPoints + * + * Updates the given preview state taking into account the state of the constraint handler. + * + * Parameters: + * + * pt - that contains the current pointer position. + * me - Optional that contains the current event. + */ +mxEdgeHandler.prototype.getPreviewPoints = function(pt, me) +{ + var geometry = this.graph.getCellGeometry(this.state.cell); + var points = (geometry.points != null) ? geometry.points.slice() : null; + var point = new mxPoint(pt.x, pt.y); + var result = null; + + if (!this.isSource && !this.isTarget) + { + this.convertPoint(point, false); + + if (points == null) + { + points = [point]; + } + else + { + // Adds point from virtual bend + if (this.index <= mxEvent.VIRTUAL_HANDLE) + { + points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point); + } + + // Removes point if dragged on terminal point + if (!this.isSource && !this.isTarget) + { + for (var i = 0; i < this.bends.length; i++) + { + if (i != this.index) + { + var bend = this.bends[i]; + + if (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y)) + { + if (this.index <= mxEvent.VIRTUAL_HANDLE) + { + points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1); + } + else + { + points.splice(this.index - 1, 1); + } + + result = points; + } + } + } + + // Removes point if user tries to straighten a segment + if (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent()))) + { + var tol = this.graph.tolerance * this.graph.tolerance; + var abs = this.state.absolutePoints.slice(); + abs[this.index] = pt; + + // Handes special case where removing waypoint affects tolerance (flickering) + var src = this.state.getVisibleTerminalState(true); + + if (src != null) + { + var c = this.graph.getConnectionConstraint(this.state, src, true); + + // Checks if point is not fixed + if (c == null || this.graph.getConnectionPoint(src, c) == null) + { + abs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src)); + } + } + + var trg = this.state.getVisibleTerminalState(false); + + if (trg != null) + { + var c = this.graph.getConnectionConstraint(this.state, trg, false); + + // Checks if point is not fixed + if (c == null || this.graph.getConnectionPoint(trg, c) == null) + { + abs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg)); + } + } + + function checkRemove(idx, tmp) + { + if (idx > 0 && idx < abs.length - 1 && + mxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y, + abs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol) + { + points.splice(idx - 1, 1); + result = points; + } + }; + + // LATER: Check if other points can be removed if a segment is made straight + checkRemove(this.index, pt); + } + } + + // Updates existing point + if (result == null && this.index > mxEvent.VIRTUAL_HANDLE) + { + points[this.index - 1] = point; + } + } + } + else if (this.graph.resetEdgesOnConnect) + { + points = null; + } + + return (result != null) ? result : points; +}; + +/** + * Function: isOutlineConnectEvent + * + * Returns true if is true and the source of the event is the outline shape + * or shift is pressed. + */ +mxEdgeHandler.prototype.isOutlineConnectEvent = function(me) +{ + var offset = mxUtils.getOffset(this.graph.container); + var evt = me.getEvent(); + + var clientX = mxEvent.getClientX(evt); + var clientY = mxEvent.getClientY(evt); + + var doc = document.documentElement; + var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0); + var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); + + var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left; + var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top; + + return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) && + (me.isSource(this.marker.highlight.shape) || + (mxEvent.isAltDown(me.getEvent()) && me.getState() != null) || + this.marker.highlight.isHighlightAt(clientX, clientY) || + ((gridX != clientX || gridY != clientY) && me.getState() == null && + this.marker.highlight.isHighlightAt(gridX, gridY))); +}; + +/** + * Function: updatePreviewState + * + * Updates the given preview state taking into account the state of the constraint handler. + */ +mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState, me, outline) +{ + // Computes the points for the edge style and terminals + var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true); + var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false); + + var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true); + var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false); + + var constraint = this.constraintHandler.currentConstraint; + + if (constraint == null && outline) + { + if (terminalState != null) + { + // Handles special case where mouse is on outline away from actual end point + // in which case the grid is ignored and mouse point is used instead + if (me.isSource(this.marker.highlight.shape)) + { + point = new mxPoint(me.getGraphX(), me.getGraphY()); + } + + constraint = this.graph.getOutlineConstraint(point, terminalState, me); + this.constraintHandler.setFocus(me, terminalState, this.isSource); + this.constraintHandler.currentConstraint = constraint; + this.constraintHandler.currentPoint = point; + } + else + { + constraint = new mxConnectionConstraint(); + } + } + + if (this.outlineConnect && this.marker.highlight != null && this.marker.highlight.shape != null) + { + var s = this.graph.view.scale; + + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null) + { + this.marker.highlight.shape.stroke = (outline) ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent'; + this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s; + this.marker.highlight.repaint(); + } + else if (this.marker.hasValidState()) + { + this.marker.highlight.shape.stroke = (this.marker.getValidState() == me.getState()) ? + mxConstants.DEFAULT_VALID_COLOR : 'transparent'; + this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s; + this.marker.highlight.repaint(); + } + } + + if (this.isSource) + { + sourceConstraint = constraint; + } + else if (this.isTarget) + { + targetConstraint = constraint; + } + + if (this.isSource || this.isTarget) + { + if (constraint != null && constraint.point != null) + { + edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x; + edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y; + } + else + { + delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X]; + delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y]; + } + } + + edge.setVisibleTerminalState(sourceState, true); + edge.setVisibleTerminalState(targetState, false); + + if (!this.isSource || sourceState != null) + { + edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint); + } + + if (!this.isTarget || targetState != null) + { + edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint); + } + + if ((this.isSource || this.isTarget) && terminalState == null) + { + edge.setAbsoluteTerminalPoint(point, this.isSource); + + if (this.marker.getMarkedState() == null) + { + this.error = (this.graph.allowDanglingEdges) ? null : ''; + } + } + + edge.view.updatePoints(edge, this.points, sourceState, targetState); + edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState); +}; + +/** + * Function: mouseMove + * + * Handles the event by updating the preview. + */ +mxEdgeHandler.prototype.mouseMove = function(sender, me) +{ + if (this.index != null && this.marker != null) + { + this.currentPoint = this.getPointForEvent(me); + this.error = null; + + // Uses the current point from the constraint handler if available + if (!this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) && this.snapPoint != null) + { + if (Math.abs(this.snapPoint.x - this.currentPoint.x) < Math.abs(this.snapPoint.y - this.currentPoint.y)) + { + this.currentPoint.x = this.snapPoint.x; + } + else + { + this.currentPoint.y = this.snapPoint.y; + } + } + + if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) + { + if (this.customHandles != null) + { + this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me); + } + } + else if (this.isLabel) + { + this.label.x = this.currentPoint.x; + this.label.y = this.currentPoint.y; + } + else + { + this.points = this.getPreviewPoints(this.currentPoint, me); + var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null; + + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null && + this.constraintHandler.currentPoint != null) + { + this.currentPoint = this.constraintHandler.currentPoint.clone(); + } + else if (this.outlineConnect) + { + // Need to check outline before cloning terminal state + var outline = (this.isSource || this.isTarget) ? this.isOutlineConnectEvent(me) : false + + if (outline) + { + terminalState = this.marker.highlight.state; + } + else if (terminalState != null && terminalState != me.getState() && this.marker.highlight.shape != null) + { + this.marker.highlight.shape.stroke = 'transparent'; + this.marker.highlight.repaint(); + terminalState = null; + } + } + + if (terminalState != null && this.graph.isCellLocked(terminalState.cell)) + { + terminalState = null; + this.marker.reset(); + } + + var clone = this.clonePreviewState(this.currentPoint, (terminalState != null) ? terminalState.cell : null); + this.updatePreviewState(clone, this.currentPoint, terminalState, me, outline); + + // Sets the color of the preview to valid or invalid, updates the + // points of the preview and redraws + var color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor; + this.setPreviewColor(color); + this.abspoints = clone.absolutePoints; + this.active = true; + } + + // This should go before calling isOutlineConnectEvent above. As a workaround + // we add an offset of gridSize to the hint to avoid problem with hit detection + // in highlight.isHighlightAt (which uses comonentFromPoint) + this.updateHint(me, this.currentPoint); + this.drawPreview(); + mxEvent.consume(me.getEvent()); + me.consume(); + } + // Workaround for disabling the connect highlight when over handle + else if (mxClient.IS_IE && this.getHandleForEvent(me) != null) + { + me.consume(false); + } +}; + +/** + * Function: mouseUp + * + * Handles the event to applying the previewed changes on the edge by + * using , or . + */ +mxEdgeHandler.prototype.mouseUp = function(sender, me) +{ + // Workaround for wrong event source in Webkit + if (this.index != null && this.marker != null) + { + var edge = this.state.cell; + + // Ignores event if mouse has not been moved + if (me.getX() != this.startX || me.getY() != this.startY) + { + var clone = !this.graph.isIgnoreTerminalEvent(me.getEvent()) && this.graph.isCloneEvent(me.getEvent()) && + this.cloneEnabled && this.graph.isCellsCloneable(); + + // Displays the reason for not carriying out the change + // if there is an error message with non-zero length + if (this.error != null) + { + if (this.error.length > 0) + { + this.graph.validationAlert(this.error); + } + } + else if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) + { + if (this.customHandles != null) + { + var model = this.graph.getModel(); + + model.beginUpdate(); + try + { + this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute(); + } + finally + { + model.endUpdate(); + } + } + } + else if (this.isLabel) + { + this.moveLabel(this.state, this.label.x, this.label.y); + } + else if (this.isSource || this.isTarget) + { + var terminal = null; + + if (this.constraintHandler.currentConstraint != null && + this.constraintHandler.currentFocus != null) + { + terminal = this.constraintHandler.currentFocus.cell; + } + + if (terminal == null && this.marker.hasValidState() && this.marker.highlight != null && + this.marker.highlight.shape != null && + this.marker.highlight.shape.stroke != 'transparent' && + this.marker.highlight.shape.stroke != 'white') + { + terminal = this.marker.validState.cell; + } + + if (terminal != null) + { + var model = this.graph.getModel(); + var parent = model.getParent(edge); + + model.beginUpdate(); + try + { + // Clones and adds the cell + if (clone) + { + var geo = model.getGeometry(edge); + var clone = this.graph.cloneCell(edge); + model.add(parent, clone, model.getChildCount(parent)); + + if (geo != null) + { + geo = geo.clone(); + model.setGeometry(clone, geo); + } + + var other = model.getTerminal(edge, !this.isSource); + this.graph.connectCell(clone, other, !this.isSource); + + edge = clone; + } + + edge = this.connect(edge, terminal, this.isSource, clone, me); + } + finally + { + model.endUpdate(); + } + } + else if (this.graph.isAllowDanglingEdges()) + { + var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1]; + pt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x); + pt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y); + + var pstate = this.graph.getView().getState( + this.graph.getModel().getParent(edge)); + + if (pstate != null) + { + pt.x -= pstate.origin.x; + pt.y -= pstate.origin.y; + } + + pt.x -= this.graph.panDx / this.graph.view.scale; + pt.y -= this.graph.panDy / this.graph.view.scale; + + // Destroys and recreates this handler + edge = this.changeTerminalPoint(edge, pt, this.isSource, clone); + } + } + else if (this.active) + { + edge = this.changePoints(edge, this.points, clone); + } + else + { + this.graph.getView().invalidate(this.state.cell); + this.graph.getView().validate(this.state.cell); + } + } + + // Resets the preview color the state of the handler if this + // handler has not been recreated + if (this.marker != null) + { + this.reset(); + + // Updates the selection if the edge has been cloned + if (edge != this.state.cell) + { + this.graph.setSelectionCell(edge); + } + } + + me.consume(); + } +}; + +/** + * Function: reset + * + * Resets the state of this handler. + */ +mxEdgeHandler.prototype.reset = function() +{ + if (this.active) + { + this.refresh(); + } + + this.error = null; + this.index = null; + this.label = null; + this.points = null; + this.snapPoint = null; + this.isLabel = false; + this.isSource = false; + this.isTarget = false; + this.active = false; + + if (this.livePreview && this.sizers != null) + { + for (var i = 0; i < this.sizers.length; i++) + { + if (this.sizers[i] != null) + { + this.sizers[i].node.style.display = ''; + } + } + } + + if (this.marker != null) + { + this.marker.reset(); + } + + if (this.constraintHandler != null) + { + this.constraintHandler.reset(); + } + + if (this.customHandles != null) + { + for (var i = 0; i < this.customHandles.length; i++) + { + this.customHandles[i].reset(); + } + } + + this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR); + this.removeHint(); + this.redraw(); +}; + +/** + * Function: setPreviewColor + * + * Sets the color of the preview to the given value. + */ +mxEdgeHandler.prototype.setPreviewColor = function(color) +{ + if (this.shape != null) + { + this.shape.stroke = color; + } +}; + + +/** + * Function: convertPoint + * + * Converts the given point in-place from screen to unscaled, untranslated + * graph coordinates and applies the grid. Returns the given, modified + * point instance. + * + * Parameters: + * + * point - to be converted. + * gridEnabled - Boolean that specifies if the grid should be applied. + */ +mxEdgeHandler.prototype.convertPoint = function(point, gridEnabled) +{ + var scale = this.graph.getView().getScale(); + var tr = this.graph.getView().getTranslate(); + + if (gridEnabled) + { + point.x = this.graph.snap(point.x); + point.y = this.graph.snap(point.y); + } + + point.x = Math.round(point.x / scale - tr.x); + point.y = Math.round(point.y / scale - tr.y); + + var pstate = this.graph.getView().getState( + this.graph.getModel().getParent(this.state.cell)); + + if (pstate != null) + { + point.x -= pstate.origin.x; + point.y -= pstate.origin.y; + } + + return point; +}; + +/** + * Function: moveLabel + * + * Changes the coordinates for the label of the given edge. + * + * Parameters: + * + * edge - that represents the edge. + * x - Integer that specifies the x-coordinate of the new location. + * y - Integer that specifies the y-coordinate of the new location. + */ +mxEdgeHandler.prototype.moveLabel = function(edgeState, x, y) +{ + var model = this.graph.getModel(); + var geometry = model.getGeometry(edgeState.cell); + + if (geometry != null) + { + var scale = this.graph.getView().scale; + geometry = geometry.clone(); + + if (geometry.relative) + { + // Resets the relative location stored inside the geometry + var pt = this.graph.getView().getRelativePoint(edgeState, x, y); + geometry.x = Math.round(pt.x * 10000) / 10000; + geometry.y = Math.round(pt.y); + + // Resets the offset inside the geometry to find the offset + // from the resulting point + geometry.offset = new mxPoint(0, 0); + var pt = this.graph.view.getPoint(edgeState, geometry); + geometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale)); + } + else + { + var points = edgeState.absolutePoints; + var p0 = points[0]; + var pe = points[points.length - 1]; + + if (p0 != null && pe != null) + { + var cx = p0.x + (pe.x - p0.x) / 2; + var cy = p0.y + (pe.y - p0.y) / 2; + + geometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale)); + geometry.x = 0; + geometry.y = 0; + } + } + + model.setGeometry(edgeState.cell, geometry); + } +}; + +/** + * Function: connect + * + * Changes the terminal or terminal point of the given edge in the graph + * model. + * + * Parameters: + * + * edge - that represents the edge to be reconnected. + * terminal - that represents the new terminal. + * isSource - Boolean indicating if the new terminal is the source or + * target terminal. + * isClone - Boolean indicating if the new connection should be a clone of + * the old edge. + * me - that contains the mouse up event. + */ +mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me) +{ + var model = this.graph.getModel(); + var parent = model.getParent(edge); + + model.beginUpdate(); + try + { + var constraint = this.constraintHandler.currentConstraint; + + if (constraint == null) + { + constraint = new mxConnectionConstraint(); + } + + this.graph.connectCell(edge, terminal, isSource, constraint); + } + finally + { + model.endUpdate(); + } + + return edge; +}; + +/** + * Function: changeTerminalPoint + * + * Changes the terminal point of the given edge. + */ +mxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource, clone) +{ + var model = this.graph.getModel(); + + model.beginUpdate(); + try + { + if (clone) + { + var parent = model.getParent(edge); + var terminal = model.getTerminal(edge, !isSource); + edge = this.graph.cloneCell(edge); + model.add(parent, edge, model.getChildCount(parent)); + model.setTerminal(edge, terminal, !isSource); + } + + var geo = model.getGeometry(edge); + + if (geo != null) + { + geo = geo.clone(); + geo.setTerminalPoint(point, isSource); + model.setGeometry(edge, geo); + this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint()); + } + } + finally + { + model.endUpdate(); + } + + return edge; +}; + +/** + * Function: changePoints + * + * Changes the control points of the given edge in the graph model. + */ +mxEdgeHandler.prototype.changePoints = function(edge, points, clone) +{ + var model = this.graph.getModel(); + model.beginUpdate(); + try + { + if (clone) + { + var parent = model.getParent(edge); + var source = model.getTerminal(edge, true); + var target = model.getTerminal(edge, false); + edge = this.graph.cloneCell(edge); + model.add(parent, edge, model.getChildCount(parent)); + model.setTerminal(edge, source, true); + model.setTerminal(edge, target, false); + } + + var geo = model.getGeometry(edge); + + if (geo != null) + { + geo = geo.clone(); + geo.points = points; + + model.setGeometry(edge, geo); + } + } + finally + { + model.endUpdate(); + } + + return edge; +}; + +/** + * Function: addPoint + * + * Adds a control point for the given state and event. + */ +mxEdgeHandler.prototype.addPoint = function(state, evt) +{ + var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), + mxEvent.getClientY(evt)); + var gridEnabled = this.graph.isGridEnabledEvent(evt); + this.convertPoint(pt, gridEnabled); + this.addPointAt(state, pt.x, pt.y); + mxEvent.consume(evt); +}; + +/** + * Function: addPointAt + * + * Adds a control point at the given point. + */ +mxEdgeHandler.prototype.addPointAt = function(state, x, y) +{ + var geo = this.graph.getCellGeometry(state.cell); + var pt = new mxPoint(x, y); + + if (geo != null) + { + geo = geo.clone(); + var t = this.graph.view.translate; + var s = this.graph.view.scale; + var offset = new mxPoint(t.x * s, t.y * s); + + var parent = this.graph.model.getParent(this.state.cell); + + if (this.graph.model.isVertex(parent)) + { + var pState = this.graph.view.getState(parent); + offset = new mxPoint(pState.x, pState.y); + } + + var index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y); + + if (geo.points == null) + { + geo.points = [pt]; + } + else + { + geo.points.splice(index, 0, pt); + } + + this.graph.getModel().setGeometry(state.cell, geo); + this.refresh(); + this.redraw(); + } +}; + +/** + * Function: removePoint + * + * Removes the control point at the given index from the given state. + */ +mxEdgeHandler.prototype.removePoint = function(state, index) +{ + if (index > 0 && index < this.abspoints.length - 1) + { + var geo = this.graph.getCellGeometry(this.state.cell); + + if (geo != null && geo.points != null) + { + geo = geo.clone(); + geo.points.splice(index - 1, 1); + this.graph.getModel().setGeometry(state.cell, geo); + this.refresh(); + this.redraw(); + } + } +}; + +/** + * Function: getHandleFillColor + * + * Returns the fillcolor for the handle at the given index. + */ +mxEdgeHandler.prototype.getHandleFillColor = function(index) +{ + var isSource = index == 0; + var cell = this.state.cell; + var terminal = this.graph.getModel().getTerminal(cell, isSource); + var color = mxConstants.HANDLE_FILLCOLOR; + + if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) || + (terminal == null && !this.graph.isTerminalPointMovable(cell, isSource))) + { + color = mxConstants.LOCKED_HANDLE_FILLCOLOR; + } + else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource)) + { + color = mxConstants.CONNECT_HANDLE_FILLCOLOR; + } + + return color; +}; + +/** + * Function: redraw + * + * Redraws the preview, and the bends- and label control points. + */ +mxEdgeHandler.prototype.redraw = function() +{ + this.abspoints = this.state.absolutePoints.slice(); + this.redrawHandles(); + + var g = this.graph.getModel().getGeometry(this.state.cell); + var pts = g.points; + + if (this.bends != null && this.bends.length > 0) + { + if (pts != null) + { + if (this.points == null) + { + this.points = []; + } + + for (var i = 1; i < this.bends.length - 1; i++) + { + if (this.bends[i] != null && this.abspoints[i] != null) + { + this.points[i - 1] = pts[i - 1]; + } + } + } + } + + this.drawPreview(); +}; + +/** + * Function: redrawHandles + * + * Redraws the handles. + */ +mxEdgeHandler.prototype.redrawHandles = function() +{ + var cell = this.state.cell; + + // Updates the handle for the label position + var b = this.labelShape.bounds; + this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y); + this.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b.width / 2), + Math.round(this.label.y - b.height / 2), b.width, b.height); + + // Shows or hides the label handle depending on the label + var lab = this.graph.getLabel(cell); + this.labelShape.visible = (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell)); + + if (this.bends != null && this.bends.length > 0) + { + var n = this.abspoints.length - 1; + + var p0 = this.abspoints[0]; + var x0 = p0.x; + var y0 = p0.y; + + b = this.bends[0].bounds; + this.bends[0].bounds = new mxRectangle(Math.floor(x0 - b.width / 2), + Math.floor(y0 - b.height / 2), b.width, b.height); + this.bends[0].fill = this.getHandleFillColor(0); + this.bends[0].redraw(); + + if (this.manageLabelHandle) + { + this.checkLabelHandle(this.bends[0].bounds); + } + + var pe = this.abspoints[n]; + var xn = pe.x; + var yn = pe.y; + + var bn = this.bends.length - 1; + b = this.bends[bn].bounds; + this.bends[bn].bounds = new mxRectangle(Math.floor(xn - b.width / 2), + Math.floor(yn - b.height / 2), b.width, b.height); + this.bends[bn].fill = this.getHandleFillColor(bn); + this.bends[bn].redraw(); + + if (this.manageLabelHandle) + { + this.checkLabelHandle(this.bends[bn].bounds); + } + + this.redrawInnerBends(p0, pe); + } + + if (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0) + { + var last = this.abspoints[0]; + + for (var i = 0; i < this.virtualBends.length; i++) + { + if (this.virtualBends[i] != null && this.abspoints[i + 1] != null) + { + var pt = this.abspoints[i + 1]; + var b = this.virtualBends[i]; + var x = last.x + (pt.x - last.x) / 2; + var y = last.y + (pt.y - last.y) / 2; + b.bounds = new mxRectangle(Math.floor(x - b.bounds.width / 2), + Math.floor(y - b.bounds.height / 2), b.bounds.width, b.bounds.height); + b.redraw(); + mxUtils.setOpacity(b.node, this.virtualBendOpacity); + last = pt; + + if (this.manageLabelHandle) + { + this.checkLabelHandle(b.bounds); + } + } + } + } + + if (this.labelShape != null) + { + this.labelShape.redraw(); + } + + if (this.customHandles != null) + { + for (var i = 0; i < this.customHandles.length; i++) + { + this.customHandles[i].redraw(); + } + } +}; + +/** + * Function: hideHandles + * + * Shortcut to . + */ +mxEdgeHandler.prototype.setHandlesVisible = function(visible) +{ + if (this.bends != null) + { + for (var i = 0; i < this.bends.length; i++) + { + this.bends[i].node.style.display = (visible) ? '' : 'none'; + } + } + + if (this.virtualBends != null) + { + for (var i = 0; i < this.virtualBends.length; i++) + { + this.virtualBends[i].node.style.display = (visible) ? '' : 'none'; + } + } + + if (this.labelShape != null) + { + this.labelShape.node.style.display = (visible) ? '' : 'none'; + } + + if (this.customHandles != null) + { + for (var i = 0; i < this.customHandles.length; i++) + { + this.customHandles[i].setVisible(visible); + } + } +}; + +/** + * Function: redrawInnerBends + * + * Updates and redraws the inner bends. + * + * Parameters: + * + * p0 - that represents the location of the first point. + * pe - that represents the location of the last point. + */ +mxEdgeHandler.prototype.redrawInnerBends = function(p0, pe) +{ + for (var i = 1; i < this.bends.length - 1; i++) + { + if (this.bends[i] != null) + { + if (this.abspoints[i] != null) + { + var x = this.abspoints[i].x; + var y = this.abspoints[i].y; + + var b = this.bends[i].bounds; + this.bends[i].node.style.visibility = 'visible'; + this.bends[i].bounds = new mxRectangle(Math.round(x - b.width / 2), + Math.round(y - b.height / 2), b.width, b.height); + + if (this.manageLabelHandle) + { + this.checkLabelHandle(this.bends[i].bounds); + } + else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds)) + { + w = mxConstants.HANDLE_SIZE + 3; + h = mxConstants.HANDLE_SIZE + 3; + this.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h); + } + + this.bends[i].redraw(); + } + else + { + this.bends[i].destroy(); + this.bends[i] = null; + } + } + } +}; + +/** + * Function: checkLabelHandle + * + * Checks if the label handle intersects the given bounds and moves it if it + * intersects. + */ +mxEdgeHandler.prototype.checkLabelHandle = function(b) +{ + if (this.labelShape != null) + { + var b2 = this.labelShape.bounds; + + if (mxUtils.intersects(b, b2)) + { + if (b.getCenterY() < b2.getCenterY()) + { + b2.y = b.y + b.height; + } + else + { + b2.y = b.y - b2.height; + } + } + } +}; + +/** + * Function: drawPreview + * + * Redraws the preview. + */ +mxEdgeHandler.prototype.drawPreview = function() +{ + if (this.isLabel) + { + var b = this.labelShape.bounds; + var bounds = new mxRectangle(Math.round(this.label.x - b.width / 2), + Math.round(this.label.y - b.height / 2), b.width, b.height); + this.labelShape.bounds = bounds; + this.labelShape.redraw(); + } + else if (this.shape != null) + { + this.shape.apply(this.state); + this.shape.points = this.abspoints; + this.shape.scale = this.state.view.scale; + this.shape.isDashed = this.isSelectionDashed(); + this.shape.stroke = this.getSelectionColor(); + this.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale; + this.shape.isShadow = false; + this.shape.redraw(); + } + + if (this.parentHighlight != null) + { + this.parentHighlight.redraw(); + } +}; + +/** + * Function: refresh + * + * Refreshes the bends of this handler. + */ +mxEdgeHandler.prototype.refresh = function() +{ + this.abspoints = this.getSelectionPoints(this.state); + this.points = []; + + if (this.shape != null) + { + this.shape.points = this.abspoints; + } + + if (this.bends != null) + { + this.destroyBends(this.bends); + this.bends = this.createBends(); + } + + if (this.virtualBends != null) + { + this.destroyBends(this.virtualBends); + this.virtualBends = this.createVirtualBends(); + } + + if (this.customHandles != null) + { + this.destroyBends(this.customHandles); + this.customHandles = this.createCustomHandles(); + } + + // Puts label node on top of bends + if (this.labelShape != null && this.labelShape.node != null && this.labelShape.node.parentNode != null) + { + this.labelShape.node.parentNode.appendChild(this.labelShape.node); + } +}; + +/** + * Function: destroyBends + * + * Destroys all elements in . + */ +mxEdgeHandler.prototype.destroyBends = function(bends) +{ + if (bends != null) + { + for (var i = 0; i < bends.length; i++) + { + if (bends[i] != null) + { + bends[i].destroy(); + } + } + } +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. This does + * normally not need to be called as handlers are destroyed automatically + * when the corresponding cell is deselected. + */ +mxEdgeHandler.prototype.destroy = function() +{ + if (this.escapeHandler != null) + { + this.state.view.graph.removeListener(this.escapeHandler); + this.escapeHandler = null; + } + + if (this.marker != null) + { + this.marker.destroy(); + this.marker = null; + } + + if (this.shape != null) + { + this.shape.destroy(); + this.shape = null; + } + + if (this.parentHighlight != null) + { + this.parentHighlight.destroy(); + this.parentHighlight = null; + } + + if (this.labelShape != null) + { + this.labelShape.destroy(); + this.labelShape = null; + } + + if (this.constraintHandler != null) + { + this.constraintHandler.destroy(); + this.constraintHandler = null; + } + + this.destroyBends(this.virtualBends); + this.virtualBends = null; + + this.destroyBends(this.customHandles); + this.customHandles = null; + + this.destroyBends(this.bends); + this.bends = null; + + this.removeHint(); +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxEdgeSegmentHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxEdgeSegmentHandler.js new file mode 100644 index 0000000..a3cab36 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxEdgeSegmentHandler.js @@ -0,0 +1,413 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +function mxEdgeSegmentHandler(state) +{ + mxEdgeHandler.call(this, state); +}; + +/** + * Extends mxEdgeHandler. + */ +mxUtils.extend(mxEdgeSegmentHandler, mxElbowEdgeHandler); + +/** + * Function: getCurrentPoints + * + * Returns the current absolute points. + */ +mxEdgeSegmentHandler.prototype.getCurrentPoints = function() +{ + var pts = this.state.absolutePoints; + + if (pts != null) + { + // Special case for straight edges where we add a virtual middle handle for moving the edge + var tol = Math.max(1, this.graph.view.scale); + + if (pts.length == 2 || (pts.length == 3 && + (Math.abs(pts[0].x - pts[1].x) < tol && Math.abs(pts[1].x - pts[2].x) < tol || + Math.abs(pts[0].y - pts[1].y) < tol && Math.abs(pts[1].y - pts[2].y) < tol))) + { + var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; + var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; + + pts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]]; + } + } + + return pts; +}; + +/** + * Function: getPreviewPoints + * + * Updates the given preview state taking into account the state of the constraint handler. + */ +mxEdgeSegmentHandler.prototype.getPreviewPoints = function(point) +{ + if (this.isSource || this.isTarget) + { + return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments); + } + else + { + var pts = this.getCurrentPoints(); + var last = this.convertPoint(pts[0].clone(), false); + point = this.convertPoint(point.clone(), false); + var result = []; + + for (var i = 1; i < pts.length; i++) + { + var pt = this.convertPoint(pts[i].clone(), false); + + if (i == this.index) + { + if (Math.round(last.x - pt.x) == 0) + { + last.x = point.x; + pt.x = point.x; + } + + if (Math.round(last.y - pt.y) == 0) + { + last.y = point.y; + pt.y = point.y; + } + } + + if (i < pts.length - 1) + { + result.push(pt); + } + + last = pt; + } + + // Replaces single point that intersects with source or target + if (result.length == 1) + { + var source = this.state.getVisibleTerminalState(true); + var target = this.state.getVisibleTerminalState(false); + var scale = this.state.view.getScale(); + var tr = this.state.view.getTranslate(); + + var x = result[0].x * scale + tr.x; + var y = result[0].y * scale + tr.y; + + if ((source != null && mxUtils.contains(source, x, y)) || + (target != null && mxUtils.contains(target, x, y))) + { + result = [point, point]; + } + } + + return result; + } +}; + +/** + * Function: updatePreviewState + * + * Overridden to perform optimization of the edge style result. + */ +mxEdgeSegmentHandler.prototype.updatePreviewState = function(edge, point, terminalState, me) +{ + mxEdgeHandler.prototype.updatePreviewState.apply(this, arguments); + + // Checks and corrects preview by running edge style again + if (!this.isSource && !this.isTarget) + { + point = this.convertPoint(point.clone(), false); + var pts = edge.absolutePoints; + var pt0 = pts[0]; + var pt1 = pts[1]; + + var result = []; + + for (var i = 2; i < pts.length; i++) + { + var pt2 = pts[i]; + + // Merges adjacent segments only if more than 2 to allow for straight edges + if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) && + (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)) + { + result.push(this.convertPoint(pt1.clone(), false)); + } + + pt0 = pt1; + pt1 = pt2; + } + + var source = this.state.getVisibleTerminalState(true); + var target = this.state.getVisibleTerminalState(false); + var rpts = this.state.absolutePoints; + + // A straight line is represented by 3 handles + if (result.length == 0 && (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 || + Math.round(pts[0].y - pts[pts.length - 1].y) == 0)) + { + result = [point, point]; + } + // Handles special case of transitions from straight vertical to routed + else if (pts.length == 5 && result.length == 2 && source != null && target != null && + rpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0) + { + var view = this.graph.getView(); + var scale = view.getScale(); + var tr = view.getTranslate(); + + var y0 = view.getRoutingCenterY(source) / scale - tr.y; + + // Use fixed connection point y-coordinate if one exists + var sc = this.graph.getConnectionConstraint(edge, source, true); + + if (sc != null) + { + var pt = this.graph.getConnectionPoint(source, sc); + + if (pt != null) + { + this.convertPoint(pt, false); + y0 = pt.y; + } + } + + var ye = view.getRoutingCenterY(target) / scale - tr.y; + + // Use fixed connection point y-coordinate if one exists + var tc = this.graph.getConnectionConstraint(edge, target, false); + + if (tc) + { + var pt = this.graph.getConnectionPoint(target, tc); + + if (pt != null) + { + this.convertPoint(pt, false); + ye = pt.y; + } + } + + result = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)]; + } + + this.points = result; + + // LATER: Check if points and result are different + edge.view.updateFixedTerminalPoints(edge, source, target); + edge.view.updatePoints(edge, this.points, source, target); + edge.view.updateFloatingTerminalPoints(edge, source, target); + } +}; + +/** + * Overriden to merge edge segments. + */ +mxEdgeSegmentHandler.prototype.connect = function(edge, terminal, isSource, isClone, me) +{ + var model = this.graph.getModel(); + var geo = model.getGeometry(edge); + var result = null; + + // Merges adjacent edge segments + if (geo != null && geo.points != null && geo.points.length > 0) + { + var pts = this.abspoints; + var pt0 = pts[0]; + var pt1 = pts[1]; + result = []; + + for (var i = 2; i < pts.length; i++) + { + var pt2 = pts[i]; + + // Merges adjacent segments only if more than 2 to allow for straight edges + if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) && + (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)) + { + result.push(this.convertPoint(pt1.clone(), false)); + } + + pt0 = pt1; + pt1 = pt2; + } + } + + model.beginUpdate(); + try + { + if (result != null) + { + var geo = model.getGeometry(edge); + + if (geo != null) + { + geo = geo.clone(); + geo.points = result; + + model.setGeometry(edge, geo); + } + } + + edge = mxEdgeHandler.prototype.connect.apply(this, arguments); + } + finally + { + model.endUpdate(); + } + + return edge; +}; + +/** + * Function: getTooltipForNode + * + * Returns no tooltips. + */ +mxEdgeSegmentHandler.prototype.getTooltipForNode = function(node) +{ + return null; +}; + +/** + * Function: createBends + * + * Adds custom bends for the center of each segment. + */ +mxEdgeSegmentHandler.prototype.start = function(x, y, index) +{ + mxEdgeHandler.prototype.start.apply(this, arguments); + + if (this.bends != null && this.bends[index] != null && + !this.isSource && !this.isTarget) + { + mxUtils.setOpacity(this.bends[index].node, 100); + } +}; + +/** + * Function: createBends + * + * Adds custom bends for the center of each segment. + */ +mxEdgeSegmentHandler.prototype.createBends = function() +{ + var bends = []; + + // Source + var bend = this.createHandleShape(0); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + var pts = this.getCurrentPoints(); + + // Waypoints (segment handles) + if (this.graph.isCellBendable(this.state.cell)) + { + if (this.points == null) + { + this.points = []; + } + + for (var i = 0; i < pts.length - 1; i++) + { + bend = this.createVirtualBend(); + bends.push(bend); + var horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0; + + // Special case where dy is 0 as well + if (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2) + { + horizontal = Math.round(pts[i].x - pts[i + 2].x) == 0; + } + + bend.setCursor((horizontal) ? 'col-resize' : 'row-resize'); + this.points.push(new mxPoint(0,0)); + } + } + + // Target + var bend = this.createHandleShape(pts.length); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + return bends; +}; + +/** + * Function: redraw + * + * Overridden to invoke before the redraw. + */ +mxEdgeSegmentHandler.prototype.redraw = function() +{ + this.refresh(); + mxEdgeHandler.prototype.redraw.apply(this, arguments); +}; + +/** + * Function: redrawInnerBends + * + * Updates the position of the custom bends. + */ +mxEdgeSegmentHandler.prototype.redrawInnerBends = function(p0, pe) +{ + if (this.graph.isCellBendable(this.state.cell)) + { + var pts = this.getCurrentPoints(); + + if (pts != null && pts.length > 1) + { + var straight = false; + + // Puts handle in the center of straight edges + if (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0) + { + straight = true; + + if (Math.round(pts[0].y - pts[pts.length - 1].y) == 0) + { + var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2; + pts[1] = new mxPoint(cx, pts[1].y); + pts[2] = new mxPoint(cx, pts[2].y); + } + else + { + var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2; + pts[1] = new mxPoint(pts[1].x, cy); + pts[2] = new mxPoint(pts[2].x, cy); + } + } + + for (var i = 0; i < pts.length - 1; i++) + { + if (this.bends[i + 1] != null) + { + var p0 = pts[i]; + var pe = pts[i + 1]; + var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2); + var b = this.bends[i + 1].bounds; + this.bends[i + 1].bounds = new mxRectangle(Math.floor(pt.x - b.width / 2), + Math.floor(pt.y - b.height / 2), b.width, b.height); + this.bends[i + 1].redraw(); + + if (this.manageLabelHandle) + { + this.checkLabelHandle(this.bends[i + 1].bounds); + } + } + } + + if (straight) + { + mxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity); + mxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity); + } + } + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxElbowEdgeHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxElbowEdgeHandler.js new file mode 100644 index 0000000..e408f04 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxElbowEdgeHandler.js @@ -0,0 +1,229 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxElbowEdgeHandler + * + * Graph event handler that reconnects edges and modifies control points and + * the edge label location. Uses for finding and + * highlighting new source and target vertices. This handler is automatically + * created in . It extends . + * + * Constructor: mxEdgeHandler + * + * Constructs an edge handler for the specified . + * + * Parameters: + * + * state - of the cell to be modified. + */ +function mxElbowEdgeHandler(state) +{ + mxEdgeHandler.call(this, state); +}; + +/** + * Extends mxEdgeHandler. + */ +mxUtils.extend(mxElbowEdgeHandler, mxEdgeHandler); + +/** + * Specifies if a double click on the middle handle should call + * . Default is true. + */ +mxElbowEdgeHandler.prototype.flipEnabled = true; + +/** + * Variable: doubleClickOrientationResource + * + * Specifies the resource key for the tooltip to be displayed on the single + * control point for routed edges. If the resource for this key does not + * exist then the value is used as the error message. Default is + * 'doubleClickOrientation'. + */ +mxElbowEdgeHandler.prototype.doubleClickOrientationResource = + (mxClient.language != 'none') ? 'doubleClickOrientation' : ''; + +/** + * Function: createBends + * + * Overrides to create custom bends. + */ + mxElbowEdgeHandler.prototype.createBends = function() + { + var bends = []; + + // Source + var bend = this.createHandleShape(0); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + // Virtual + bends.push(this.createVirtualBend(mxUtils.bind(this, function(evt) + { + if (!mxEvent.isConsumed(evt) && this.flipEnabled) + { + this.graph.flipEdge(this.state.cell, evt); + mxEvent.consume(evt); + } + }))); + this.points.push(new mxPoint(0,0)); + + // Target + bend = this.createHandleShape(2); + this.initBend(bend); + bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE); + bends.push(bend); + + return bends; + }; + +/** + * Function: createVirtualBend + * + * Creates a virtual bend that supports double clicking and calls + * . + */ +mxElbowEdgeHandler.prototype.createVirtualBend = function(dblClickHandler) +{ + var bend = this.createHandleShape(); + this.initBend(bend, dblClickHandler); + + bend.setCursor(this.getCursorForBend()); + + if (!this.graph.isCellBendable(this.state.cell)) + { + bend.node.style.display = 'none'; + } + + return bend; +}; + +/** + * Function: getCursorForBend + * + * Returns the cursor to be used for the bend. + */ +mxElbowEdgeHandler.prototype.getCursorForBend = function() +{ + return (this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom || + this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM || + ((this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.ElbowConnector || + this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW)&& + this.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) ? + 'row-resize' : 'col-resize'; +}; + +/** + * Function: getTooltipForNode + * + * Returns the tooltip for the given node. + */ +mxElbowEdgeHandler.prototype.getTooltipForNode = function(node) +{ + var tip = null; + + if (this.bends != null && this.bends[1] != null && (node == this.bends[1].node || + node.parentNode == this.bends[1].node)) + { + tip = this.doubleClickOrientationResource; + tip = mxResources.get(tip) || tip; // translate + } + + return tip; +}; + +/** + * Function: convertPoint + * + * Converts the given point in-place from screen to unscaled, untranslated + * graph coordinates and applies the grid. + * + * Parameters: + * + * point - to be converted. + * gridEnabled - Boolean that specifies if the grid should be applied. + */ +mxElbowEdgeHandler.prototype.convertPoint = function(point, gridEnabled) +{ + var scale = this.graph.getView().getScale(); + var tr = this.graph.getView().getTranslate(); + var origin = this.state.origin; + + if (gridEnabled) + { + point.x = this.graph.snap(point.x); + point.y = this.graph.snap(point.y); + } + + point.x = Math.round(point.x / scale - tr.x - origin.x); + point.y = Math.round(point.y / scale - tr.y - origin.y); + + return point; +}; + +/** + * Function: redrawInnerBends + * + * Updates and redraws the inner bends. + * + * Parameters: + * + * p0 - that represents the location of the first point. + * pe - that represents the location of the last point. + */ +mxElbowEdgeHandler.prototype.redrawInnerBends = function(p0, pe) +{ + var g = this.graph.getModel().getGeometry(this.state.cell); + var pts = this.state.absolutePoints; + var pt = null; + + // Keeps the virtual bend on the edge shape + if (pts.length > 1) + { + p0 = pts[1]; + pe = pts[pts.length - 2]; + } + else if (g.points != null && g.points.length > 0) + { + pt = pts[0]; + } + + if (pt == null) + { + pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2); + } + else + { + pt = new mxPoint(this.graph.getView().scale * (pt.x + this.graph.getView().translate.x + this.state.origin.x), + this.graph.getView().scale * (pt.y + this.graph.getView().translate.y + this.state.origin.y)); + } + + // Makes handle slightly bigger if the yellow label handle + // exists and intersects this green handle + var b = this.bends[1].bounds; + var w = b.width; + var h = b.height; + var bounds = new mxRectangle(Math.round(pt.x - w / 2), Math.round(pt.y - h / 2), w, h); + + if (this.manageLabelHandle) + { + this.checkLabelHandle(bounds); + } + else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(bounds, this.labelShape.bounds)) + { + w = mxConstants.HANDLE_SIZE + 3; + h = mxConstants.HANDLE_SIZE + 3; + bounds = new mxRectangle(Math.floor(pt.x - w / 2), Math.floor(pt.y - h / 2), w, h); + } + + this.bends[1].bounds = bounds; + this.bends[1].redraw(); + + if (this.manageLabelHandle) + { + this.checkLabelHandle(this.bends[1].bounds); + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxGraphHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxGraphHandler.js new file mode 100644 index 0000000..306c674 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxGraphHandler.js @@ -0,0 +1,1103 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxGraphHandler + * + * Graph event handler that handles selection. Individual cells are handled + * separately using or one of the edge handlers. These + * handlers are created using in + * . + * + * To avoid the container to scroll a moved cell into view, set + * to false. + * + * Constructor: mxGraphHandler + * + * Constructs an event handler that creates handles for the + * selection cells. + * + * Parameters: + * + * graph - Reference to the enclosing . + */ +function mxGraphHandler(graph) +{ + this.graph = graph; + this.graph.addMouseListener(this); + + // Repaints the handler after autoscroll + this.panHandler = mxUtils.bind(this, function() + { + this.updatePreviewShape(); + this.updateHint(); + }); + + this.graph.addListener(mxEvent.PAN, this.panHandler); + + // Handles escape keystrokes + this.escapeHandler = mxUtils.bind(this, function(sender, evt) + { + this.reset(); + }); + + this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); + + // Updates the preview box for remote changes + this.refreshHandler = mxUtils.bind(this, function(sender, evt) + { + if (this.first != null) + { + try + { + this.bounds = this.graph.getView().getBounds(this.cells); + this.pBounds = this.getPreviewBounds(this.cells); + this.updatePreviewShape(); + } + catch (e) + { + // Resets the handler if cells have vanished + this.reset(); + } + } + }); + + this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler); +}; + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxGraphHandler.prototype.graph = null; + +/** + * Variable: maxCells + * + * Defines the maximum number of cells to paint subhandles + * for. Default is 50 for Firefox and 20 for IE. Set this + * to 0 if you want an unlimited number of handles to be + * displayed. This is only recommended if the number of + * cells in the graph is limited to a small number, eg. + * 500. + */ +mxGraphHandler.prototype.maxCells = (mxClient.IS_IE) ? 20 : 50; + +/** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ +mxGraphHandler.prototype.enabled = true; + +/** + * Variable: highlightEnabled + * + * Specifies if drop targets under the mouse should be enabled. Default is + * true. + */ +mxGraphHandler.prototype.highlightEnabled = true; + +/** + * Variable: cloneEnabled + * + * Specifies if cloning by control-drag is enabled. Default is true. + */ +mxGraphHandler.prototype.cloneEnabled = true; + +/** + * Variable: moveEnabled + * + * Specifies if moving is enabled. Default is true. + */ +mxGraphHandler.prototype.moveEnabled = true; + +/** + * Variable: guidesEnabled + * + * Specifies if other cells should be used for snapping the right, center or + * left side of the current selection. Default is false. + */ +mxGraphHandler.prototype.guidesEnabled = false; + +/** + * Variable: guide + * + * Holds the instance that is used for alignment. + */ +mxGraphHandler.prototype.guide = null; + +/** + * Variable: currentDx + * + * Stores the x-coordinate of the current mouse move. + */ +mxGraphHandler.prototype.currentDx = null; + +/** + * Variable: currentDy + * + * Stores the y-coordinate of the current mouse move. + */ +mxGraphHandler.prototype.currentDy = null; + +/** + * Variable: updateCursor + * + * Specifies if a move cursor should be shown if the mouse is over a movable + * cell. Default is true. + */ +mxGraphHandler.prototype.updateCursor = true; + +/** + * Variable: selectEnabled + * + * Specifies if selecting is enabled. Default is true. + */ +mxGraphHandler.prototype.selectEnabled = true; + +/** + * Variable: removeCellsFromParent + * + * Specifies if cells may be moved out of their parents. Default is true. + */ +mxGraphHandler.prototype.removeCellsFromParent = true; + +/** + * Variable: connectOnDrop + * + * Specifies if drop events are interpreted as new connections if no other + * drop action is defined. Default is false. + */ +mxGraphHandler.prototype.connectOnDrop = false; + +/** + * Variable: scrollOnMove + * + * Specifies if the view should be scrolled so that a moved cell is + * visible. Default is true. + */ +mxGraphHandler.prototype.scrollOnMove = true; + +/** + * Variable: minimumSize + * + * Specifies the minimum number of pixels for the width and height of a + * selection border. Default is 6. + */ +mxGraphHandler.prototype.minimumSize = 6; + +/** + * Variable: previewColor + * + * Specifies the color of the preview shape. Default is black. + */ +mxGraphHandler.prototype.previewColor = 'black'; + +/** + * Variable: htmlPreview + * + * Specifies if the graph container should be used for preview. If this is used + * then drop target detection relies entirely on because + * the HTML preview does not "let events through". Default is false. + */ +mxGraphHandler.prototype.htmlPreview = false; + +/** + * Variable: shape + * + * Reference to the that represents the preview. + */ +mxGraphHandler.prototype.shape = null; + +/** + * Variable: scaleGrid + * + * Specifies if the grid should be scaled. Default is false. + */ +mxGraphHandler.prototype.scaleGrid = false; + +/** + * Variable: rotationEnabled + * + * Specifies if the bounding box should allow for rotation. Default is true. + */ +mxGraphHandler.prototype.rotationEnabled = true; + +/** + * Function: isEnabled + * + * Returns . + */ +mxGraphHandler.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setEnabled + * + * Sets . + */ +mxGraphHandler.prototype.setEnabled = function(value) +{ + this.enabled = value; +}; + +/** + * Function: isCloneEnabled + * + * Returns . + */ +mxGraphHandler.prototype.isCloneEnabled = function() +{ + return this.cloneEnabled; +}; + +/** + * Function: setCloneEnabled + * + * Sets . + * + * Parameters: + * + * value - Boolean that specifies the new clone enabled state. + */ +mxGraphHandler.prototype.setCloneEnabled = function(value) +{ + this.cloneEnabled = value; +}; + +/** + * Function: isMoveEnabled + * + * Returns . + */ +mxGraphHandler.prototype.isMoveEnabled = function() +{ + return this.moveEnabled; +}; + +/** + * Function: setMoveEnabled + * + * Sets . + */ +mxGraphHandler.prototype.setMoveEnabled = function(value) +{ + this.moveEnabled = value; +}; + +/** + * Function: isSelectEnabled + * + * Returns . + */ +mxGraphHandler.prototype.isSelectEnabled = function() +{ + return this.selectEnabled; +}; + +/** + * Function: setSelectEnabled + * + * Sets . + */ +mxGraphHandler.prototype.setSelectEnabled = function(value) +{ + this.selectEnabled = value; +}; + +/** + * Function: isRemoveCellsFromParent + * + * Returns . + */ +mxGraphHandler.prototype.isRemoveCellsFromParent = function() +{ + return this.removeCellsFromParent; +}; + +/** + * Function: setRemoveCellsFromParent + * + * Sets . + */ +mxGraphHandler.prototype.setRemoveCellsFromParent = function(value) +{ + this.removeCellsFromParent = value; +}; + +/** + * Function: getInitialCellForEvent + * + * Hook to return initial cell for the given event. + */ +mxGraphHandler.prototype.getInitialCellForEvent = function(me) +{ + return me.getCell(); +}; + +/** + * Function: isDelayedSelection + * + * Hook to return true for delayed selections. + */ +mxGraphHandler.prototype.isDelayedSelection = function(cell, me) +{ + return this.graph.isCellSelected(cell); +}; + +/** + * Function: consumeMouseEvent + * + * Consumes the given mouse event. NOTE: This may be used to enable click + * events for links in labels on iOS as follows as consuming the initial + * touchStart disables firing the subsequent click evnent on the link. + * + * + * mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me) + * { + * var source = mxEvent.getSource(me.getEvent()); + * + * if (!mxEvent.isTouchEvent(me.getEvent()) || source.nodeName != 'A') + * { + * me.consume(); + * } + * } + * + */ +mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me) +{ + me.consume(); +}; + +/** + * Function: mouseDown + * + * Handles the event by selecing the given cell and creating a handle for + * it. By consuming the event all subsequent events of the gesture are + * redirected to this handler. + */ +mxGraphHandler.prototype.mouseDown = function(sender, me) +{ + if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() && + me.getState() != null && !mxEvent.isMultiTouchEvent(me.getEvent())) + { + var cell = this.getInitialCellForEvent(me); + this.delayedSelection = this.isDelayedSelection(cell, me); + this.cell = null; + + if (this.isSelectEnabled() && !this.delayedSelection) + { + this.graph.selectCellForEvent(cell, me.getEvent()); + } + + if (this.isMoveEnabled()) + { + var model = this.graph.model; + var geo = model.getGeometry(cell); + + if (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 || + (geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null || + model.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges || + (this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable()))) + { + this.start(cell, me.getX(), me.getY()); + } + else if (this.delayedSelection) + { + this.cell = cell; + } + + this.cellWasClicked = true; + this.consumeMouseEvent(mxEvent.MOUSE_DOWN, me); + } + } +}; + +/** + * Function: getGuideStates + * + * Creates an array of cell states which should be used as guides. + */ +mxGraphHandler.prototype.getGuideStates = function() +{ + var parent = this.graph.getDefaultParent(); + var model = this.graph.getModel(); + + var filter = mxUtils.bind(this, function(cell) + { + return this.graph.view.getState(cell) != null && + model.isVertex(cell) && + model.getGeometry(cell) != null && + !model.getGeometry(cell).relative; + }); + + return this.graph.view.getCellStates(model.filterDescendants(filter, parent)); +}; + +/** + * Function: getCells + * + * Returns the cells to be modified by this handler. This implementation + * returns all selection cells that are movable, or the given initial cell if + * the given cell is not selected and movable. This handles the case of moving + * unselectable or unselected cells. + * + * Parameters: + * + * initialCell - that triggered this handler. + */ +mxGraphHandler.prototype.getCells = function(initialCell) +{ + if (!this.delayedSelection && this.graph.isCellMovable(initialCell)) + { + return [initialCell]; + } + else + { + return this.graph.getMovableCells(this.graph.getSelectionCells()); + } +}; + +/** + * Function: getPreviewBounds + * + * Returns the used as the preview bounds for + * moving the given cells. + */ +mxGraphHandler.prototype.getPreviewBounds = function(cells) +{ + var bounds = this.getBoundingBox(cells); + + if (bounds != null) + { + // Corrects width and height + bounds.width = Math.max(0, bounds.width - 1); + bounds.height = Math.max(0, bounds.height - 1); + + if (bounds.width < this.minimumSize) + { + var dx = this.minimumSize - bounds.width; + bounds.x -= dx / 2; + bounds.width = this.minimumSize; + } + else + { + bounds.x = Math.round(bounds.x); + bounds.width = Math.ceil(bounds.width); + } + + var tr = this.graph.view.translate; + var s = this.graph.view.scale; + + if (bounds.height < this.minimumSize) + { + var dy = this.minimumSize - bounds.height; + bounds.y -= dy / 2; + bounds.height = this.minimumSize; + } + else + { + bounds.y = Math.round(bounds.y); + bounds.height = Math.ceil(bounds.height); + } + } + + return bounds; +}; + +/** + * Function: getBoundingBox + * + * Returns the union of the for the given array of . + * For vertices, this method uses the bounding box of the corresponding shape + * if one exists. The bounding box of the corresponding text label and all + * controls and overlays are ignored. See also: and + * . + * + * Parameters: + * + * cells - Array of whose bounding box should be returned. + */ +mxGraphHandler.prototype.getBoundingBox = function(cells) +{ + var result = null; + + if (cells != null && cells.length > 0) + { + var model = this.graph.getModel(); + + for (var i = 0; i < cells.length; i++) + { + if (model.isVertex(cells[i]) || model.isEdge(cells[i])) + { + var state = this.graph.view.getState(cells[i]); + + if (state != null) + { + var bbox = state; + + if (model.isVertex(cells[i]) && state.shape != null && state.shape.boundingBox != null) + { + bbox = state.shape.boundingBox; + } + + if (result == null) + { + result = mxRectangle.fromRectangle(bbox); + } + else + { + result.add(bbox); + } + } + } + } + } + + return result; +}; + +/** + * Function: createPreviewShape + * + * Creates the shape used to draw the preview for the given bounds. + */ +mxGraphHandler.prototype.createPreviewShape = function(bounds) +{ + var shape = new mxRectangleShape(bounds, null, this.previewColor); + shape.isDashed = true; + + if (this.htmlPreview) + { + shape.dialect = mxConstants.DIALECT_STRICTHTML; + shape.init(this.graph.container); + } + else + { + // Makes sure to use either VML or SVG shapes in order to implement + // event-transparency on the background area of the rectangle since + // HTML shapes do not let mouseevents through even when transparent + shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + shape.init(this.graph.getView().getOverlayPane()); + shape.pointerEvents = false; + + // Workaround for artifacts on iOS + if (mxClient.IS_IOS) + { + shape.getSvgScreenOffset = function() + { + return 0; + }; + } + } + + return shape; +}; + +/** + * Function: start + * + * Starts the handling of the mouse gesture. + */ +mxGraphHandler.prototype.start = function(cell, x, y) +{ + this.cell = cell; + this.first = mxUtils.convertPoint(this.graph.container, x, y); + this.cells = this.getCells(this.cell); + this.bounds = this.graph.getView().getBounds(this.cells); + this.pBounds = this.getPreviewBounds(this.cells); + + if (this.guidesEnabled) + { + this.guide = new mxGuide(this.graph, this.getGuideStates()); + } +}; + +/** + * Function: useGuidesForEvent + * + * Returns true if the guides should be used for the given . + * This implementation returns . + */ +mxGraphHandler.prototype.useGuidesForEvent = function(me) +{ + return (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) : true; +}; + + +/** + * Function: snap + * + * Snaps the given vector to the grid and returns the given mxPoint instance. + */ +mxGraphHandler.prototype.snap = function(vector) +{ + var scale = (this.scaleGrid) ? this.graph.view.scale : 1; + + vector.x = this.graph.snap(vector.x / scale) * scale; + vector.y = this.graph.snap(vector.y / scale) * scale; + + return vector; +}; + +/** + * Function: getDelta + * + * Returns an that represents the vector for moving the cells + * for the given . + */ +mxGraphHandler.prototype.getDelta = function(me) +{ + var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY()); + var s = this.graph.view.scale; + + return new mxPoint(this.roundLength((point.x - this.first.x) / s) * s, + this.roundLength((point.y - this.first.y) / s) * s); +}; + +/** + * Function: updateHint + * + * Hook for subclassers do show details while the handler is active. + */ +mxGraphHandler.prototype.updateHint = function(me) { }; + +/** + * Function: removeHint + * + * Hooks for subclassers to hide details when the handler gets inactive. + */ +mxGraphHandler.prototype.removeHint = function() { }; + +/** + * Function: roundLength + * + * Hook for rounding the unscaled vector. This uses Math.round. + */ +mxGraphHandler.prototype.roundLength = function(length) +{ + return Math.round(length * 2) / 2; +}; + +/** + * Function: mouseMove + * + * Handles the event by highlighting possible drop targets and updating the + * preview. + */ +mxGraphHandler.prototype.mouseMove = function(sender, me) +{ + var graph = this.graph; + + if (!me.isConsumed() && graph.isMouseDown && this.cell != null && + this.first != null && this.bounds != null) + { + // Stops moving if a multi touch event is received + if (mxEvent.isMultiTouchEvent(me.getEvent())) + { + this.reset(); + return; + } + + var delta = this.getDelta(me); + var dx = delta.x; + var dy = delta.y; + var tol = graph.tolerance; + + if (this.shape != null || Math.abs(dx) > tol || Math.abs(dy) > tol) + { + // Highlight is used for highlighting drop targets + if (this.highlight == null) + { + this.highlight = new mxCellHighlight(this.graph, + mxConstants.DROP_TARGET_COLOR, 3); + } + + if (this.shape == null) + { + this.shape = this.createPreviewShape(this.bounds); + } + + var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled(); + var gridEnabled = graph.isGridEnabledEvent(me.getEvent()); + var hideGuide = true; + + if (this.guide != null && this.useGuidesForEvent(me)) + { + delta = this.guide.move(this.bounds, new mxPoint(dx, dy), gridEnabled, clone); + hideGuide = false; + dx = delta.x; + dy = delta.y; + } + else if (gridEnabled) + { + var trx = graph.getView().translate; + var scale = graph.getView().scale; + + var tx = this.bounds.x - (graph.snap(this.bounds.x / scale - trx.x) + trx.x) * scale; + var ty = this.bounds.y - (graph.snap(this.bounds.y / scale - trx.y) + trx.y) * scale; + var v = this.snap(new mxPoint(dx, dy)); + + dx = v.x - tx; + dy = v.y - ty; + } + + if (this.guide != null && hideGuide) + { + this.guide.hide(); + } + + // Constrained movement if shift key is pressed + if (graph.isConstrainedEvent(me.getEvent())) + { + if (Math.abs(dx) > Math.abs(dy)) + { + dy = 0; + } + else + { + dx = 0; + } + } + + this.currentDx = dx; + this.currentDy = dy; + this.updatePreviewShape(); + + var target = null; + var cell = me.getCell(); + + if (graph.isDropEnabled() && this.highlightEnabled) + { + // Contains a call to getCellAt to find the cell under the mouse + target = graph.getDropTarget(this.cells, me.getEvent(), cell, clone); + } + + var state = graph.getView().getState(target); + var highlight = false; + + if (state != null && (graph.model.getParent(this.cell) != target || clone)) + { + if (this.target != target) + { + this.target = target; + this.setHighlightColor(mxConstants.DROP_TARGET_COLOR); + } + + highlight = true; + } + else + { + this.target = null; + + if (this.connectOnDrop && cell != null && this.cells.length == 1 && + graph.getModel().isVertex(cell) && graph.isCellConnectable(cell)) + { + state = graph.getView().getState(cell); + + if (state != null) + { + var error = graph.getEdgeValidationError(null, this.cell, cell); + var color = (error == null) ? + mxConstants.VALID_COLOR : + mxConstants.INVALID_CONNECT_TARGET_COLOR; + this.setHighlightColor(color); + highlight = true; + } + } + } + + if (state != null && highlight) + { + this.highlight.highlight(state); + } + else + { + this.highlight.hide(); + } + } + + this.updateHint(me); + this.consumeMouseEvent(mxEvent.MOUSE_MOVE, me); + + // Cancels the bubbling of events to the container so + // that the droptarget is not reset due to an mouseMove + // fired on the container with no associated state. + mxEvent.consume(me.getEvent()); + } + else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor && !me.isConsumed() && + (me.getState() != null || me.sourceState != null) && !graph.isMouseDown) + { + var cursor = graph.getCursorForMouseEvent(me); + + if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell())) + { + if (graph.getModel().isEdge(me.getCell())) + { + cursor = mxConstants.CURSOR_MOVABLE_EDGE; + } + else + { + cursor = mxConstants.CURSOR_MOVABLE_VERTEX; + } + } + + // Sets the cursor on the original source state under the mouse + // instead of the event source state which can be the parent + if (cursor != null && me.sourceState != null) + { + me.sourceState.setCursor(cursor); + } + } +}; + +/** + * Function: updatePreviewShape + * + * Updates the bounds of the preview shape. + */ +mxGraphHandler.prototype.updatePreviewShape = function() +{ + if (this.shape != null) + { + this.shape.bounds = new mxRectangle(Math.round(this.pBounds.x + this.currentDx - this.graph.panDx), + Math.round(this.pBounds.y + this.currentDy - this.graph.panDy), this.pBounds.width, this.pBounds.height); + this.shape.redraw(); + } +}; + +/** + * Function: setHighlightColor + * + * Sets the color of the rectangle used to highlight drop targets. + * + * Parameters: + * + * color - String that represents the new highlight color. + */ +mxGraphHandler.prototype.setHighlightColor = function(color) +{ + if (this.highlight != null) + { + this.highlight.setHighlightColor(color); + } +}; + +/** + * Function: mouseUp + * + * Handles the event by applying the changes to the selection cells. + */ +mxGraphHandler.prototype.mouseUp = function(sender, me) +{ + if (!me.isConsumed()) + { + var graph = this.graph; + + if (this.cell != null && this.first != null && this.shape != null && + this.currentDx != null && this.currentDy != null) + { + var cell = me.getCell(); + + if (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) && + graph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell)) + { + graph.connectionHandler.connect(this.cell, cell, me.getEvent()); + } + else + { + var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled(); + var scale = graph.getView().scale; + var dx = this.roundLength(this.currentDx / scale); + var dy = this.roundLength(this.currentDy / scale); + var target = this.target; + + if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent())) + { + graph.splitEdge(target, this.cells, null, dx, dy); + } + else + { + this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent()); + } + } + } + else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null) + { + this.selectDelayed(me); + } + } + + // Consumes the event if a cell was initially clicked + if (this.cellWasClicked) + { + this.consumeMouseEvent(mxEvent.MOUSE_UP, me); + } + + this.reset(); +}; + +/** + * Function: selectDelayed + * + * Implements the delayed selection for the given mouse event. + */ +mxGraphHandler.prototype.selectDelayed = function(me) +{ + if (!this.graph.isCellSelected(this.cell) || !this.graph.popupMenuHandler.isPopupTrigger(me)) + { + this.graph.selectCellForEvent(this.cell, me.getEvent()); + } +}; + +/** + * Function: reset + * + * Resets the state of this handler. + */ +mxGraphHandler.prototype.reset = function() +{ + this.destroyShapes(); + this.removeHint(); + + this.cellWasClicked = false; + this.delayedSelection = false; + this.currentDx = null; + this.currentDy = null; + this.guides = null; + this.first = null; + this.cell = null; + this.target = null; +}; + +/** + * Function: shouldRemoveCellsFromParent + * + * Returns true if the given cells should be removed from the parent for the specified + * mousereleased event. + */ +mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt) +{ + if (this.graph.getModel().isVertex(parent)) + { + var pState = this.graph.getView().getState(parent); + + if (pState != null) + { + var pt = mxUtils.convertPoint(this.graph.container, + mxEvent.getClientX(evt), mxEvent.getClientY(evt)); + var alpha = mxUtils.toRadians(mxUtils.getValue(pState.style, mxConstants.STYLE_ROTATION) || 0); + + if (alpha != 0) + { + var cos = Math.cos(-alpha); + var sin = Math.sin(-alpha); + var cx = new mxPoint(pState.getCenterX(), pState.getCenterY()); + pt = mxUtils.getRotatedPoint(pt, cos, sin, cx); + } + + return !mxUtils.contains(pState, pt.x, pt.y); + } + } + + return false; +}; + +/** + * Function: moveCells + * + * Moves the given cells by the specified amount. + */ +mxGraphHandler.prototype.moveCells = function(cells, dx, dy, clone, target, evt) +{ + if (clone) + { + cells = this.graph.getCloneableCells(cells); + } + + // Removes cells from parent + if (target == null && this.isRemoveCellsFromParent() && + this.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell), cells, evt)) + { + target = this.graph.getDefaultParent(); + } + + // Cloning into locked cells is not allowed + clone = clone && !this.graph.isCellLocked(target || this.graph.getDefaultParent()); + + // Passes all selected cells in order to correctly clone or move into + // the target cell. The method checks for each cell if its movable. + cells = this.graph.moveCells(cells, dx - this.graph.panDx / this.graph.view.scale, + dy - this.graph.panDy / this.graph.view.scale, clone, target, evt); + + if (this.isSelectEnabled() && this.scrollOnMove) + { + this.graph.scrollCellToVisible(cells[0]); + } + + // Selects the new cells if cells have been cloned + if (clone) + { + this.graph.setSelectionCells(cells); + } +}; + +/** + * Function: destroyShapes + * + * Destroy the preview and highlight shapes. + */ +mxGraphHandler.prototype.destroyShapes = function() +{ + // Destroys the preview dashed rectangle + if (this.shape != null) + { + this.shape.destroy(); + this.shape = null; + } + + if (this.guide != null) + { + this.guide.destroy(); + this.guide = null; + } + + // Destroys the drop target highlight + if (this.highlight != null) + { + this.highlight.destroy(); + this.highlight = null; + } +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ +mxGraphHandler.prototype.destroy = function() +{ + this.graph.removeMouseListener(this); + this.graph.removeListener(this.panHandler); + + if (this.escapeHandler != null) + { + this.graph.removeListener(this.escapeHandler); + this.escapeHandler = null; + } + + if (this.refreshHandler != null) + { + this.graph.getModel().removeListener(this.refreshHandler); + this.refreshHandler = null; + } + + this.destroyShapes(); + this.removeHint(); +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxHandle.js b/oaweb/public/cherry/drawio/src/js/handler/mxHandle.js new file mode 100644 index 0000000..302b48a --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxHandle.js @@ -0,0 +1,351 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxHandle + * + * Implements a single custom handle for vertices. + * + * Constructor: mxHandle + * + * Constructs a new handle for the given state. + * + * Parameters: + * + * state - of the cell to be handled. + */ +function mxHandle(state, cursor, image) +{ + this.graph = state.view.graph; + this.state = state; + this.cursor = (cursor != null) ? cursor : this.cursor; + this.image = (image != null) ? image : this.image; + this.init(); +}; + +/** + * Variable: cursor + * + * Specifies the cursor to be used for this handle. Default is 'default'. + */ +mxHandle.prototype.cursor = 'default'; + +/** + * Variable: image + * + * Specifies the to be used to render the handle. Default is null. + */ +mxHandle.prototype.image = null; + +/** + * Variable: image + * + * Specifies the to be used to render the handle. Default is null. + */ +mxHandle.prototype.ignoreGrid = false; + +/** + * Function: getPosition + * + * Hook for subclassers to return the current position of the handle. + */ +mxHandle.prototype.getPosition = function(bounds) { }; + +/** + * Function: setPosition + * + * Hooks for subclassers to update the style in the . + */ +mxHandle.prototype.setPosition = function(bounds, pt, me) { }; + +/** + * Function: execute + * + * Hook for subclassers to execute the handle. + */ +mxHandle.prototype.execute = function() { }; + +/** + * Function: copyStyle + * + * Sets the cell style with the given name to the corresponding value in . + */ +mxHandle.prototype.copyStyle = function(key) +{ + this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]); +}; + +/** + * Function: processEvent + * + * Processes the given and invokes . + */ +mxHandle.prototype.processEvent = function(me) +{ + var scale = this.graph.view.scale; + var tr = this.graph.view.translate; + var pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y); + + // Center shape on mouse cursor + if (this.shape != null && this.shape.bounds != null) + { + pt.x -= this.shape.bounds.width / scale / 4; + pt.y -= this.shape.bounds.height / scale / 4; + } + + // Snaps to grid for the rotated position then applies the rotation for the direction after that + var alpha1 = -mxUtils.toRadians(this.getRotation()); + var alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1; + pt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1), + this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2)); + this.setPosition(this.state.getPaintBounds(), pt, me); + this.positionChanged(); + this.redraw(); +}; + +/** + * Function: positionChanged + * + * Called after has been called in . This repaints + * the state using . + */ +mxHandle.prototype.positionChanged = function() +{ + if (this.state.text != null) + { + this.state.text.apply(this.state); + } + + if (this.state.shape != null) + { + this.state.shape.apply(this.state); + } + + this.graph.cellRenderer.redraw(this.state, true); +}; + +/** + * Function: getRotation + * + * Returns the rotation defined in the style of the cell. + */ +mxHandle.prototype.getRotation = function() +{ + if (this.state.shape != null) + { + return this.state.shape.getRotation(); + } + + return 0; +}; + +/** + * Function: getTotalRotation + * + * Returns the rotation from the style and the rotation from the direction of + * the cell. + */ +mxHandle.prototype.getTotalRotation = function() +{ + if (this.state.shape != null) + { + return this.state.shape.getShapeRotation(); + } + + return 0; +}; + +/** + * Function: init + * + * Creates and initializes the shapes required for this handle. + */ +mxHandle.prototype.init = function() +{ + var html = this.isHtmlRequired(); + + if (this.image != null) + { + this.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src); + this.shape.preserveImageAspect = false; + } + else + { + this.shape = this.createShape(html); + } + + this.initShape(html); +}; + +/** + * Function: createShape + * + * Creates and returns the shape for this handle. + */ +mxHandle.prototype.createShape = function(html) +{ + var bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE); + + return new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); +}; + +/** + * Function: initShape + * + * Initializes and sets its cursor. + */ +mxHandle.prototype.initShape = function(html) +{ + if (html && this.shape.isHtmlAllowed()) + { + this.shape.dialect = mxConstants.DIALECT_STRICTHTML; + this.shape.init(this.graph.container); + } + else + { + this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + + if (this.cursor != null) + { + this.shape.init(this.graph.getView().getOverlayPane()); + } + } + + mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state); + this.shape.node.style.cursor = this.cursor; +}; + +/** + * Function: redraw + * + * Renders the shape for this handle. + */ +mxHandle.prototype.redraw = function() +{ + if (this.shape != null && this.state.shape != null) + { + var pt = this.getPosition(this.state.getPaintBounds()); + + if (pt != null) + { + var alpha = mxUtils.toRadians(this.getTotalRotation()); + pt = this.rotatePoint(this.flipPoint(pt), alpha); + + var scale = this.graph.view.scale; + var tr = this.graph.view.translate; + this.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2); + this.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2); + + // Needed to force update of text bounds + this.shape.redraw(); + } + } +}; + +/** + * Function: isHtmlRequired + * + * Returns true if this handle should be rendered in HTML. This returns true if + * the text node is in the graph container. + */ +mxHandle.prototype.isHtmlRequired = function() +{ + return this.state.text != null && this.state.text.node.parentNode == this.graph.container; +}; + +/** + * Function: rotatePoint + * + * Rotates the point by the given angle. + */ +mxHandle.prototype.rotatePoint = function(pt, alpha) +{ + var bounds = this.state.getCellBounds(); + var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY()); + var cos = Math.cos(alpha); + var sin = Math.sin(alpha); + + return mxUtils.getRotatedPoint(pt, cos, sin, cx); +}; + +/** + * Function: flipPoint + * + * Flips the given point vertically and/or horizontally. + */ +mxHandle.prototype.flipPoint = function(pt) +{ + if (this.state.shape != null) + { + var bounds = this.state.getCellBounds(); + + if (this.state.shape.flipH) + { + pt.x = 2 * bounds.x + bounds.width - pt.x; + } + + if (this.state.shape.flipV) + { + pt.y = 2 * bounds.y + bounds.height - pt.y; + } + } + + return pt; +}; + +/** + * Function: snapPoint + * + * Snaps the given point to the grid if ignore is false. This modifies + * the given point in-place and also returns it. + */ +mxHandle.prototype.snapPoint = function(pt, ignore) +{ + if (!ignore) + { + pt.x = this.graph.snap(pt.x); + pt.y = this.graph.snap(pt.y); + } + + return pt; +}; + +/** + * Function: setVisible + * + * Shows or hides this handle. + */ +mxHandle.prototype.setVisible = function(visible) +{ + if (this.shape != null && this.shape.node != null) + { + this.shape.node.style.display = (visible) ? '' : 'none'; + } +}; + +/** + * Function: reset + * + * Resets the state of this handle by setting its visibility to true. + */ +mxHandle.prototype.reset = function() +{ + this.setVisible(true); + this.state.style = this.graph.getCellStyle(this.state.cell); + this.positionChanged(); +}; + +/** + * Function: destroy + * + * Destroys this handle. + */ +mxHandle.prototype.destroy = function() +{ + if (this.shape != null) + { + this.shape.destroy(); + this.shape = null; + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxKeyHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxKeyHandler.js new file mode 100644 index 0000000..6a391f0 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxKeyHandler.js @@ -0,0 +1,428 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxKeyHandler + * + * Event handler that listens to keystroke events. This is not a singleton, + * however, it is normally only required once if the target is the document + * element (default). + * + * This handler installs a key event listener in the topmost DOM node and + * processes all events that originate from descandants of + * or from the topmost DOM node. The latter means that all unhandled keystrokes + * are handled by this object regardless of the focused state of the . + * + * Example: + * + * The following example creates a key handler that listens to the delete key + * (46) and deletes the selection cells if the graph is enabled. + * + * (code) + * var keyHandler = new mxKeyHandler(graph); + * keyHandler.bindKey(46, function(evt) + * { + * if (graph.isEnabled()) + * { + * graph.removeCells(); + * } + * }); + * (end) + * + * Keycodes: + * + * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of + * keycodes or install a key event listener into the document element and print + * the key codes of the respective events to the console. + * + * To support the Command key and the Control key on the Mac, the following + * code can be used. + * + * (code) + * keyHandler.getFunction = function(evt) + * { + * if (evt != null) + * { + * return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode]; + * } + * + * return null; + * }; + * (end) + * + * Constructor: mxKeyHandler + * + * Constructs an event handler that executes functions bound to specific + * keystrokes. + * + * Parameters: + * + * graph - Reference to the associated . + * target - Optional reference to the event target. If null, the document + * element is used as the event target, that is, the object where the key + * event listener is installed. + */ +function mxKeyHandler(graph, target) +{ + if (graph != null) + { + this.graph = graph; + this.target = target || document.documentElement; + + // Creates the arrays to map from keycodes to functions + this.normalKeys = []; + this.shiftKeys = []; + this.controlKeys = []; + this.controlShiftKeys = []; + + this.keydownHandler = mxUtils.bind(this, function(evt) + { + this.keyDown(evt); + }); + + // Installs the keystroke listener in the target + mxEvent.addListener(this.target, 'keydown', this.keydownHandler); + + // Automatically deallocates memory in IE + if (mxClient.IS_IE) + { + mxEvent.addListener(window, 'unload', + mxUtils.bind(this, function() + { + this.destroy(); + }) + ); + } + } +}; + +/** + * Variable: graph + * + * Reference to the associated with this handler. + */ +mxKeyHandler.prototype.graph = null; + +/** + * Variable: target + * + * Reference to the target DOM, that is, the DOM node where the key event + * listeners are installed. + */ +mxKeyHandler.prototype.target = null; + +/** + * Variable: normalKeys + * + * Maps from keycodes to functions for non-pressed control keys. + */ +mxKeyHandler.prototype.normalKeys = null; + +/** + * Variable: shiftKeys + * + * Maps from keycodes to functions for pressed shift keys. + */ +mxKeyHandler.prototype.shiftKeys = null; + +/** + * Variable: controlKeys + * + * Maps from keycodes to functions for pressed control keys. + */ +mxKeyHandler.prototype.controlKeys = null; + +/** + * Variable: controlShiftKeys + * + * Maps from keycodes to functions for pressed control and shift keys. + */ +mxKeyHandler.prototype.controlShiftKeys = null; + +/** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ +mxKeyHandler.prototype.enabled = true; + +/** + * Function: isEnabled + * + * Returns true if events are handled. This implementation returns + * . + */ +mxKeyHandler.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setEnabled + * + * Enables or disables event handling by updating . + * + * Parameters: + * + * enabled - Boolean that specifies the new enabled state. + */ +mxKeyHandler.prototype.setEnabled = function(enabled) +{ + this.enabled = enabled; +}; + +/** + * Function: bindKey + * + * Binds the specified keycode to the given function. This binding is used + * if the control key is not pressed. + * + * Parameters: + * + * code - Integer that specifies the keycode. + * funct - JavaScript function that takes the key event as an argument. + */ +mxKeyHandler.prototype.bindKey = function(code, funct) +{ + this.normalKeys[code] = funct; +}; + +/** + * Function: bindShiftKey + * + * Binds the specified keycode to the given function. This binding is used + * if the shift key is pressed. + * + * Parameters: + * + * code - Integer that specifies the keycode. + * funct - JavaScript function that takes the key event as an argument. + */ +mxKeyHandler.prototype.bindShiftKey = function(code, funct) +{ + this.shiftKeys[code] = funct; +}; + +/** + * Function: bindControlKey + * + * Binds the specified keycode to the given function. This binding is used + * if the control key is pressed. + * + * Parameters: + * + * code - Integer that specifies the keycode. + * funct - JavaScript function that takes the key event as an argument. + */ +mxKeyHandler.prototype.bindControlKey = function(code, funct) +{ + this.controlKeys[code] = funct; +}; + +/** + * Function: bindControlShiftKey + * + * Binds the specified keycode to the given function. This binding is used + * if the control and shift key are pressed. + * + * Parameters: + * + * code - Integer that specifies the keycode. + * funct - JavaScript function that takes the key event as an argument. + */ +mxKeyHandler.prototype.bindControlShiftKey = function(code, funct) +{ + this.controlShiftKeys[code] = funct; +}; + +/** + * Function: isControlDown + * + * Returns true if the control key is pressed. This uses . + * + * Parameters: + * + * evt - Key event whose control key pressed state should be returned. + */ +mxKeyHandler.prototype.isControlDown = function(evt) +{ + return mxEvent.isControlDown(evt); +}; + +/** + * Function: getFunction + * + * Returns the function associated with the given key event or null if no + * function is associated with the given event. + * + * Parameters: + * + * evt - Key event whose associated function should be returned. + */ +mxKeyHandler.prototype.getFunction = function(evt) +{ + if (evt != null && !mxEvent.isAltDown(evt)) + { + if (this.isControlDown(evt)) + { + if (mxEvent.isShiftDown(evt)) + { + return this.controlShiftKeys[evt.keyCode]; + } + else + { + return this.controlKeys[evt.keyCode]; + } + } + else + { + if (mxEvent.isShiftDown(evt)) + { + return this.shiftKeys[evt.keyCode]; + } + else + { + return this.normalKeys[evt.keyCode]; + } + } + } + + return null; +}; + +/** + * Function: isGraphEvent + * + * Returns true if the event should be processed by this handler, that is, + * if the event source is either the target, one of its direct children, a + * descendant of the , or the of the + * . + * + * Parameters: + * + * evt - Key event that represents the keystroke. + */ +mxKeyHandler.prototype.isGraphEvent = function(evt) +{ + var source = mxEvent.getSource(evt); + + // Accepts events from the target object or + // in-place editing inside graph + if ((source == this.target || source.parentNode == this.target) || + (this.graph.cellEditor != null && this.graph.cellEditor.isEventSource(evt))) + { + return true; + } + + // Accepts events from inside the container + return mxUtils.isAncestorNode(this.graph.container, source); +}; + +/** + * Function: keyDown + * + * Handles the event by invoking the function bound to the respective keystroke + * if returns true for the given event and if + * returns false, except for escape for which + * is not invoked. + * + * Parameters: + * + * evt - Key event that represents the keystroke. + */ +mxKeyHandler.prototype.keyDown = function(evt) +{ + if (this.isEnabledForEvent(evt)) + { + // Cancels the editing if escape is pressed + if (evt.keyCode == 27 /* Escape */) + { + this.escape(evt); + } + + // Invokes the function for the keystroke + else if (!this.isEventIgnored(evt)) + { + var boundFunction = this.getFunction(evt); + + if (boundFunction != null) + { + boundFunction(evt); + mxEvent.consume(evt); + } + } + } +}; + +/** + * Function: isEnabledForEvent + * + * Returns true if the given event should be handled. is + * called later if the event is not an escape key stroke, in which case + * is called. This implementation returns true if + * returns true for both, this handler and , if the event is not + * consumed and if returns true. + * + * Parameters: + * + * evt - Key event that represents the keystroke. + */ +mxKeyHandler.prototype.isEnabledForEvent = function(evt) +{ + return (this.graph.isEnabled() && !mxEvent.isConsumed(evt) && + this.isGraphEvent(evt) && this.isEnabled()); +}; + +/** + * Function: isEventIgnored + * + * Returns true if the given keystroke should be ignored. This returns + * graph.isEditing(). + * + * Parameters: + * + * evt - Key event that represents the keystroke. + */ +mxKeyHandler.prototype.isEventIgnored = function(evt) +{ + return this.graph.isEditing(); +}; + +/** + * Function: escape + * + * Hook to process ESCAPE keystrokes. This implementation invokes + * to cancel the current editing, connecting + * and/or other ongoing modifications. + * + * Parameters: + * + * evt - Key event that represents the keystroke. Possible keycode in this + * case is 27 (ESCAPE). + */ +mxKeyHandler.prototype.escape = function(evt) +{ + if (this.graph.isEscapeEnabled()) + { + this.graph.escape(evt); + } +}; + +/** + * Function: destroy + * + * Destroys the handler and all its references into the DOM. This does + * normally not need to be called, it is called automatically when the + * window unloads (in IE). + */ +mxKeyHandler.prototype.destroy = function() +{ + if (this.target != null && this.keydownHandler != null) + { + mxEvent.removeListener(this.target, 'keydown', this.keydownHandler); + this.keydownHandler = null; + } + + this.target = null; +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxPanningHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxPanningHandler.js new file mode 100644 index 0000000..0c99a35 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxPanningHandler.js @@ -0,0 +1,485 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxPanningHandler + * + * Event handler that pans and creates popupmenus. To use the left + * mousebutton for panning without interfering with cell moving and + * resizing, use and . For grid size + * steps while panning, use . This handler is built-into + * and enabled using . + * + * Constructor: mxPanningHandler + * + * Constructs an event handler that creates a + * and pans the graph. + * + * Event: mxEvent.PAN_START + * + * Fires when the panning handler changes its state to true. The + * event property contains the corresponding . + * + * Event: mxEvent.PAN + * + * Fires while handle is processing events. The event property contains + * the corresponding . + * + * Event: mxEvent.PAN_END + * + * Fires when the panning handler changes its state to false. The + * event property contains the corresponding . + */ +function mxPanningHandler(graph) +{ + if (graph != null) + { + this.graph = graph; + this.graph.addMouseListener(this); + + // Handles force panning event + this.forcePanningHandler = mxUtils.bind(this, function(sender, evt) + { + var evtName = evt.getProperty('eventName'); + var me = evt.getProperty('event'); + + if (evtName == mxEvent.MOUSE_DOWN && this.isForcePanningEvent(me)) + { + this.start(me); + this.active = true; + this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me)); + me.consume(); + } + }); + + this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler); + + // Handles pinch gestures + this.gestureHandler = mxUtils.bind(this, function(sender, eo) + { + if (this.isPinchEnabled()) + { + var evt = eo.getProperty('event'); + + if (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart') + { + this.initialScale = this.graph.view.scale; + + // Forces start of panning when pinch gesture starts + if (!this.active && this.mouseDownEvent != null) + { + this.start(this.mouseDownEvent); + this.mouseDownEvent = null; + } + } + else if (evt.type == 'gestureend' && this.initialScale != null) + { + this.initialScale = null; + } + + if (this.initialScale != null) + { + var value = Math.round(this.initialScale * evt.scale * 100) / 100; + + if (this.minScale != null) + { + value = Math.max(this.minScale, value); + } + + if (this.maxScale != null) + { + value = Math.min(this.maxScale, value); + } + + if (this.graph.view.scale != value) + { + this.graph.zoomTo(value); + mxEvent.consume(evt); + } + } + } + }); + + this.graph.addListener(mxEvent.GESTURE, this.gestureHandler); + + this.mouseUpListener = mxUtils.bind(this, function() + { + if (this.active) + { + this.reset(); + } + }); + + // Stops scrolling on every mouseup anywhere in the document + mxEvent.addListener(document, 'mouseup', this.mouseUpListener); + } +}; + +/** + * Extends mxEventSource. + */ +mxPanningHandler.prototype = new mxEventSource(); +mxPanningHandler.prototype.constructor = mxPanningHandler; + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxPanningHandler.prototype.graph = null; + +/** + * Variable: useLeftButtonForPanning + * + * Specifies if panning should be active for the left mouse button. + * Setting this to true may conflict with . Default is false. + */ +mxPanningHandler.prototype.useLeftButtonForPanning = false; + +/** + * Variable: usePopupTrigger + * + * Specifies if should also be used for panning. + */ +mxPanningHandler.prototype.usePopupTrigger = true; + +/** + * Variable: ignoreCell + * + * Specifies if panning should be active even if there is a cell under the + * mousepointer. Default is false. + */ +mxPanningHandler.prototype.ignoreCell = false; + +/** + * Variable: previewEnabled + * + * Specifies if the panning should be previewed. Default is true. + */ +mxPanningHandler.prototype.previewEnabled = true; + +/** + * Variable: useGrid + * + * Specifies if the panning steps should be aligned to the grid size. + * Default is false. + */ +mxPanningHandler.prototype.useGrid = false; + +/** + * Variable: panningEnabled + * + * Specifies if panning should be enabled. Default is true. + */ +mxPanningHandler.prototype.panningEnabled = true; + +/** + * Variable: pinchEnabled + * + * Specifies if pinch gestures should be handled as zoom. Default is true. + */ +mxPanningHandler.prototype.pinchEnabled = true; + +/** + * Variable: maxScale + * + * Specifies the maximum scale. Default is 8. + */ +mxPanningHandler.prototype.maxScale = 8; + +/** + * Variable: minScale + * + * Specifies the minimum scale. Default is 0.01. + */ +mxPanningHandler.prototype.minScale = 0.01; + +/** + * Variable: dx + * + * Holds the current horizontal offset. + */ +mxPanningHandler.prototype.dx = null; + +/** + * Variable: dy + * + * Holds the current vertical offset. + */ +mxPanningHandler.prototype.dy = null; + +/** + * Variable: startX + * + * Holds the x-coordinate of the start point. + */ +mxPanningHandler.prototype.startX = 0; + +/** + * Variable: startY + * + * Holds the y-coordinate of the start point. + */ +mxPanningHandler.prototype.startY = 0; + +/** + * Function: isActive + * + * Returns true if the handler is currently active. + */ +mxPanningHandler.prototype.isActive = function() +{ + return this.active || this.initialScale != null; +}; + +/** + * Function: isPanningEnabled + * + * Returns . + */ +mxPanningHandler.prototype.isPanningEnabled = function() +{ + return this.panningEnabled; +}; + +/** + * Function: setPanningEnabled + * + * Sets . + */ +mxPanningHandler.prototype.setPanningEnabled = function(value) +{ + this.panningEnabled = value; +}; + +/** + * Function: isPinchEnabled + * + * Returns . + */ +mxPanningHandler.prototype.isPinchEnabled = function() +{ + return this.pinchEnabled; +}; + +/** + * Function: setPinchEnabled + * + * Sets . + */ +mxPanningHandler.prototype.setPinchEnabled = function(value) +{ + this.pinchEnabled = value; +}; + +/** + * Function: isPanningTrigger + * + * Returns true if the given event is a panning trigger for the optional + * given cell. This returns true if control-shift is pressed or if + * is true and the event is a popup trigger. + */ +mxPanningHandler.prototype.isPanningTrigger = function(me) +{ + var evt = me.getEvent(); + + return (this.useLeftButtonForPanning && me.getState() == null && + mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) && + mxEvent.isShiftDown(evt)) || (this.usePopupTrigger && mxEvent.isPopupTrigger(evt)); +}; + +/** + * Function: isForcePanningEvent + * + * Returns true if the given should start panning. This + * implementation always returns true if is true or for + * multi touch events. + */ +mxPanningHandler.prototype.isForcePanningEvent = function(me) +{ + return this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent()); +}; + +/** + * Function: mouseDown + * + * Handles the event by initiating the panning. By consuming the event all + * subsequent events of the gesture are redirected to this handler. + */ +mxPanningHandler.prototype.mouseDown = function(sender, me) +{ + this.mouseDownEvent = me; + + if (!me.isConsumed() && this.isPanningEnabled() && !this.active && this.isPanningTrigger(me)) + { + this.start(me); + this.consumePanningTrigger(me); + } +}; + +/** + * Function: start + * + * Starts panning at the given event. + */ +mxPanningHandler.prototype.start = function(me) +{ + this.dx0 = -this.graph.container.scrollLeft; + this.dy0 = -this.graph.container.scrollTop; + + // Stores the location of the trigger event + this.startX = me.getX(); + this.startY = me.getY(); + this.dx = null; + this.dy = null; + + this.panningTrigger = true; +}; + +/** + * Function: consumePanningTrigger + * + * Consumes the given if it was a panning trigger in + * . The default is to invoke . Note that this + * will block any further event processing. If you haven't disabled built-in + * context menus and require immediate selection of the cell on mouseDown in + * Safari and/or on the Mac, then use the following code: + * + * (code) + * mxPanningHandler.prototype.consumePanningTrigger = function(me) + * { + * if (me.evt.preventDefault) + * { + * me.evt.preventDefault(); + * } + * + * // Stops event processing in IE + * me.evt.returnValue = false; + * + * // Sets local consumed state + * if (!mxClient.IS_SF && !mxClient.IS_MAC) + * { + * me.consumed = true; + * } + * }; + * (end) + */ +mxPanningHandler.prototype.consumePanningTrigger = function(me) +{ + me.consume(); +}; + +/** + * Function: mouseMove + * + * Handles the event by updating the panning on the graph. + */ +mxPanningHandler.prototype.mouseMove = function(sender, me) +{ + this.dx = me.getX() - this.startX; + this.dy = me.getY() - this.startY; + + if (this.active) + { + if (this.previewEnabled) + { + // Applies the grid to the panning steps + if (this.useGrid) + { + this.dx = this.graph.snap(this.dx); + this.dy = this.graph.snap(this.dy); + } + + this.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0); + } + + this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me)); + } + else if (this.panningTrigger) + { + var tmp = this.active; + + // Panning is activated only if the mouse is moved + // beyond the graph tolerance + this.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance; + + if (!tmp && this.active) + { + this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me)); + } + } + + if (this.active || this.panningTrigger) + { + me.consume(); + } +}; + +/** + * Function: mouseUp + * + * Handles the event by setting the translation on the view or showing the + * popupmenu. + */ +mxPanningHandler.prototype.mouseUp = function(sender, me) +{ + if (this.active) + { + if (this.dx != null && this.dy != null) + { + // Ignores if scrollbars have been used for panning + if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container)) + { + var scale = this.graph.getView().scale; + var t = this.graph.getView().translate; + this.graph.panGraph(0, 0); + this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale); + } + + me.consume(); + } + + this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me)); + } + + this.reset(); +}; + +/** + * Function: mouseUp + * + * Handles the event by setting the translation on the view or showing the + * popupmenu. + */ +mxPanningHandler.prototype.reset = function() +{ + this.panningTrigger = false; + this.mouseDownEvent = null; + this.active = false; + this.dx = null; + this.dy = null; +}; + +/** + * Function: panGraph + * + * Pans by the given amount. + */ +mxPanningHandler.prototype.panGraph = function(dx, dy) +{ + this.graph.getView().setTranslate(dx, dy); +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ +mxPanningHandler.prototype.destroy = function() +{ + this.graph.removeMouseListener(this); + this.graph.removeListener(this.forcePanningHandler); + this.graph.removeListener(this.gestureHandler); + mxEvent.removeListener(document, 'mouseup', this.mouseUpListener); +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxPopupMenuHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxPopupMenuHandler.js new file mode 100644 index 0000000..2388319 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxPopupMenuHandler.js @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxPopupMenuHandler + * + * Event handler that creates popupmenus. + * + * Constructor: mxPopupMenuHandler + * + * Constructs an event handler that creates a . + */ +function mxPopupMenuHandler(graph, factoryMethod) +{ + if (graph != null) + { + this.graph = graph; + this.factoryMethod = factoryMethod; + this.graph.addMouseListener(this); + + // Does not show menu if any touch gestures take place after the trigger + this.gestureHandler = mxUtils.bind(this, function(sender, eo) + { + this.inTolerance = false; + }); + + this.graph.addListener(mxEvent.GESTURE, this.gestureHandler); + + this.init(); + } +}; + +/** + * Extends mxPopupMenu. + */ +mxPopupMenuHandler.prototype = new mxPopupMenu(); +mxPopupMenuHandler.prototype.constructor = mxPopupMenuHandler; + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxPopupMenuHandler.prototype.graph = null; + +/** + * Variable: selectOnPopup + * + * Specifies if cells should be selected if a popupmenu is displayed for + * them. Default is true. + */ +mxPopupMenuHandler.prototype.selectOnPopup = true; + +/** + * Variable: clearSelectionOnBackground + * + * Specifies if cells should be deselected if a popupmenu is displayed for + * the diagram background. Default is true. + */ +mxPopupMenuHandler.prototype.clearSelectionOnBackground = true; + +/** + * Variable: triggerX + * + * X-coordinate of the mouse down event. + */ +mxPopupMenuHandler.prototype.triggerX = null; + +/** + * Variable: triggerY + * + * Y-coordinate of the mouse down event. + */ +mxPopupMenuHandler.prototype.triggerY = null; + +/** + * Variable: screenX + * + * Screen X-coordinate of the mouse down event. + */ +mxPopupMenuHandler.prototype.screenX = null; + +/** + * Variable: screenY + * + * Screen Y-coordinate of the mouse down event. + */ +mxPopupMenuHandler.prototype.screenY = null; + +/** + * Function: init + * + * Initializes the shapes required for this vertex handler. + */ +mxPopupMenuHandler.prototype.init = function() +{ + // Supercall + mxPopupMenu.prototype.init.apply(this); + + // Hides the tooltip if the mouse is over + // the context menu + mxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt) + { + this.graph.tooltipHandler.hide(); + })); +}; + +/** + * Function: isSelectOnPopup + * + * Hook for returning if a cell should be selected for a given . + * This implementation returns . + */ +mxPopupMenuHandler.prototype.isSelectOnPopup = function(me) +{ + return this.selectOnPopup; +}; + +/** + * Function: mouseDown + * + * Handles the event by initiating the panning. By consuming the event all + * subsequent events of the gesture are redirected to this handler. + */ +mxPopupMenuHandler.prototype.mouseDown = function(sender, me) +{ + if (this.isEnabled() && !mxEvent.isMultiTouchEvent(me.getEvent())) + { + // Hides the popupmenu if is is being displayed + this.hideMenu(); + this.triggerX = me.getGraphX(); + this.triggerY = me.getGraphY(); + this.screenX = mxEvent.getMainEvent(me.getEvent()).screenX; + this.screenY = mxEvent.getMainEvent(me.getEvent()).screenY; + this.popupTrigger = this.isPopupTrigger(me); + this.inTolerance = true; + } +}; + +/** + * Function: mouseMove + * + * Handles the event by updating the panning on the graph. + */ +mxPopupMenuHandler.prototype.mouseMove = function(sender, me) +{ + // Popup trigger may change on mouseUp so ignore it + if (this.inTolerance && this.screenX != null && this.screenY != null) + { + if (Math.abs(mxEvent.getMainEvent(me.getEvent()).screenX - this.screenX) > this.graph.tolerance || + Math.abs(mxEvent.getMainEvent(me.getEvent()).screenY - this.screenY) > this.graph.tolerance) + { + this.inTolerance = false; + } + } +}; + +/** + * Function: mouseUp + * + * Handles the event by setting the translation on the view or showing the + * popupmenu. + */ +mxPopupMenuHandler.prototype.mouseUp = function(sender, me) +{ + if (this.popupTrigger && this.inTolerance && this.triggerX != null && this.triggerY != null) + { + var cell = this.getCellForPopupEvent(me); + + // Selects the cell for which the context menu is being displayed + if (this.graph.isEnabled() && this.isSelectOnPopup(me) && + cell != null && !this.graph.isCellSelected(cell)) + { + this.graph.setSelectionCell(cell); + } + else if (this.clearSelectionOnBackground && cell == null) + { + this.graph.clearSelection(); + } + + // Hides the tooltip if there is one + this.graph.tooltipHandler.hide(); + + // Menu is shifted by 1 pixel so that the mouse up event + // is routed via the underlying shape instead of the DIV + var origin = mxUtils.getScrollOrigin(); + this.popup(me.getX() + origin.x + 1, me.getY() + origin.y + 1, cell, me.getEvent()); + me.consume(); + } + + this.popupTrigger = false; + this.inTolerance = false; +}; + +/** + * Function: getCellForPopupEvent + * + * Hook to return the cell for the mouse up popup trigger handling. + */ +mxPopupMenuHandler.prototype.getCellForPopupEvent = function(me) +{ + return me.getCell(); +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ +mxPopupMenuHandler.prototype.destroy = function() +{ + this.graph.removeMouseListener(this); + this.graph.removeListener(this.gestureHandler); + + // Supercall + mxPopupMenu.prototype.destroy.apply(this); +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxRubberband.js b/oaweb/public/cherry/drawio/src/js/handler/mxRubberband.js new file mode 100644 index 0000000..37f68f0 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxRubberband.js @@ -0,0 +1,429 @@ +/** + * Copyright (c) 2006-2016, JGraph Ltd + * Copyright (c) 2006-2016, Gaudenz Alder + */ +/** + * Class: mxRubberband + * + * Event handler that selects rectangular regions. This is not built-into + * . To enable rubberband selection in a graph, use the following code. + * + * Example: + * + * (code) + * var rubberband = new mxRubberband(graph); + * (end) + * + * Constructor: mxRubberband + * + * Constructs an event handler that selects rectangular regions in the graph + * using rubberband selection. + */ +function mxRubberband(graph) +{ + if (graph != null) + { + this.graph = graph; + this.graph.addMouseListener(this); + + // Handles force rubberband event + this.forceRubberbandHandler = mxUtils.bind(this, function(sender, evt) + { + var evtName = evt.getProperty('eventName'); + var me = evt.getProperty('event'); + + if (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me)) + { + var offset = mxUtils.getOffset(this.graph.container); + var origin = mxUtils.getScrollOrigin(this.graph.container); + origin.x -= offset.x; + origin.y -= offset.y; + this.start(me.getX() + origin.x, me.getY() + origin.y); + me.consume(false); + } + }); + + this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler); + + // Repaints the marquee after autoscroll + this.panHandler = mxUtils.bind(this, function() + { + this.repaint(); + }); + + this.graph.addListener(mxEvent.PAN, this.panHandler); + + // Does not show menu if any touch gestures take place after the trigger + this.gestureHandler = mxUtils.bind(this, function(sender, eo) + { + if (this.first != null) + { + this.reset(); + } + }); + + this.graph.addListener(mxEvent.GESTURE, this.gestureHandler); + + // Automatic deallocation of memory + if (mxClient.IS_IE) + { + mxEvent.addListener(window, 'unload', + mxUtils.bind(this, function() + { + this.destroy(); + }) + ); + } + } +}; + +/** + * Variable: defaultOpacity + * + * Specifies the default opacity to be used for the rubberband div. Default + * is 20. + */ +mxRubberband.prototype.defaultOpacity = 20; + +/** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ +mxRubberband.prototype.enabled = true; + +/** + * Variable: div + * + * Holds the DIV element which is currently visible. + */ +mxRubberband.prototype.div = null; + +/** + * Variable: sharedDiv + * + * Holds the DIV element which is used to display the rubberband. + */ +mxRubberband.prototype.sharedDiv = null; + +/** + * Variable: currentX + * + * Holds the value of the x argument in the last call to . + */ +mxRubberband.prototype.currentX = 0; + +/** + * Variable: currentY + * + * Holds the value of the y argument in the last call to . + */ +mxRubberband.prototype.currentY = 0; + +/** + * Variable: fadeOut + * + * Optional fade out effect. Default is false. + */ +mxRubberband.prototype.fadeOut = false; + +/** + * Function: isEnabled + * + * Returns true if events are handled. This implementation returns + * . + */ +mxRubberband.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setEnabled + * + * Enables or disables event handling. This implementation updates + * . + */ +mxRubberband.prototype.setEnabled = function(enabled) +{ + this.enabled = enabled; +}; + +/** + * Function: isForceRubberbandEvent + * + * Returns true if the given should start rubberband selection. + * This implementation returns true if the alt key is pressed. + */ +mxRubberband.prototype.isForceRubberbandEvent = function(me) +{ + return mxEvent.isAltDown(me.getEvent()); +}; + +/** + * Function: mouseDown + * + * Handles the event by initiating a rubberband selection. By consuming the + * event all subsequent events of the gesture are redirected to this + * handler. + */ +mxRubberband.prototype.mouseDown = function(sender, me) +{ + if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() && + me.getState() == null && !mxEvent.isMultiTouchEvent(me.getEvent())) + { + var offset = mxUtils.getOffset(this.graph.container); + var origin = mxUtils.getScrollOrigin(this.graph.container); + origin.x -= offset.x; + origin.y -= offset.y; + this.start(me.getX() + origin.x, me.getY() + origin.y); + + // Does not prevent the default for this event so that the + // event processing chain is still executed even if we start + // rubberbanding. This is required eg. in ExtJs to hide the + // current context menu. In mouseMove we'll make sure we're + // not selecting anything while we're rubberbanding. + me.consume(false); + } +}; + +/** + * Function: start + * + * Sets the start point for the rubberband selection. + */ +mxRubberband.prototype.start = function(x, y) +{ + this.first = new mxPoint(x, y); + + var container = this.graph.container; + + function createMouseEvent(evt) + { + var me = new mxMouseEvent(evt); + var pt = mxUtils.convertPoint(container, me.getX(), me.getY()); + + me.graphX = pt.x; + me.graphY = pt.y; + + return me; + }; + + this.dragHandler = mxUtils.bind(this, function(evt) + { + this.mouseMove(this.graph, createMouseEvent(evt)); + }); + + this.dropHandler = mxUtils.bind(this, function(evt) + { + this.mouseUp(this.graph, createMouseEvent(evt)); + }); + + // Workaround for rubberband stopping if the mouse leaves the container in Firefox + if (mxClient.IS_FF) + { + mxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler); + } +}; + +/** + * Function: mouseMove + * + * Handles the event by updating therubberband selection. + */ +mxRubberband.prototype.mouseMove = function(sender, me) +{ + if (!me.isConsumed() && this.first != null) + { + var origin = mxUtils.getScrollOrigin(this.graph.container); + var offset = mxUtils.getOffset(this.graph.container); + origin.x -= offset.x; + origin.y -= offset.y; + var x = me.getX() + origin.x; + var y = me.getY() + origin.y; + var dx = this.first.x - x; + var dy = this.first.y - y; + var tol = this.graph.tolerance; + + if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol) + { + if (this.div == null) + { + this.div = this.createShape(); + } + + // Clears selection while rubberbanding. This is required because + // the event is not consumed in mouseDown. + mxUtils.clearSelection(); + + this.update(x, y); + me.consume(); + } + } +}; + +/** + * Function: createShape + * + * Creates the rubberband selection shape. + */ +mxRubberband.prototype.createShape = function() +{ + if (this.sharedDiv == null) + { + this.sharedDiv = document.createElement('div'); + this.sharedDiv.className = 'mxRubberband'; + mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity); + } + + this.graph.container.appendChild(this.sharedDiv); + var result = this.sharedDiv; + + if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut) + { + this.sharedDiv = null; + } + + return result; +}; + +/** + * Function: isActive + * + * Returns true if this handler is active. + */ +mxRubberband.prototype.isActive = function(sender, me) +{ + return this.div != null && this.div.style.display != 'none'; +}; + +/** + * Function: mouseUp + * + * Handles the event by selecting the region of the rubberband using + * . + */ +mxRubberband.prototype.mouseUp = function(sender, me) +{ + var active = this.isActive(); + this.reset(); + + if (active) + { + this.execute(me.getEvent()); + me.consume(); + } +}; + +/** + * Function: execute + * + * Resets the state of this handler and selects the current region + * for the given event. + */ +mxRubberband.prototype.execute = function(evt) +{ + var rect = new mxRectangle(this.x, this.y, this.width, this.height); + this.graph.selectRegion(rect, evt); +}; + +/** + * Function: reset + * + * Resets the state of the rubberband selection. + */ +mxRubberband.prototype.reset = function() +{ + if (this.div != null) + { + if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut) + { + var temp = this.div; + mxUtils.setPrefixedStyle(temp.style, 'transition', 'all 0.2s linear'); + temp.style.pointerEvents = 'none'; + temp.style.opacity = 0; + + window.setTimeout(function() + { + temp.parentNode.removeChild(temp); + }, 200); + } + else + { + this.div.parentNode.removeChild(this.div); + } + } + + mxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler); + this.dragHandler = null; + this.dropHandler = null; + + this.currentX = 0; + this.currentY = 0; + this.first = null; + this.div = null; +}; + +/** + * Function: update + * + * Sets and and calls . + */ +mxRubberband.prototype.update = function(x, y) +{ + this.currentX = x; + this.currentY = y; + + this.repaint(); +}; + +/** + * Function: repaint + * + * Computes the bounding box and updates the style of the
. + */ +mxRubberband.prototype.repaint = function() +{ + if (this.div != null) + { + var x = this.currentX - this.graph.panDx; + var y = this.currentY - this.graph.panDy; + + this.x = Math.min(this.first.x, x); + this.y = Math.min(this.first.y, y); + this.width = Math.max(this.first.x, x) - this.x; + this.height = Math.max(this.first.y, y) - this.y; + + var dx = (mxClient.IS_VML) ? this.graph.panDx : 0; + var dy = (mxClient.IS_VML) ? this.graph.panDy : 0; + + this.div.style.left = (this.x + dx) + 'px'; + this.div.style.top = (this.y + dy) + 'px'; + this.div.style.width = Math.max(1, this.width) + 'px'; + this.div.style.height = Math.max(1, this.height) + 'px'; + } +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. This does + * normally not need to be called, it is called automatically when the + * window unloads. + */ +mxRubberband.prototype.destroy = function() +{ + if (!this.destroyed) + { + this.destroyed = true; + this.graph.removeMouseListener(this); + this.graph.removeListener(this.forceRubberbandHandler); + this.graph.removeListener(this.panHandler); + this.reset(); + + if (this.sharedDiv != null) + { + this.sharedDiv = null; + } + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxSelectionCellsHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxSelectionCellsHandler.js new file mode 100644 index 0000000..460d7a2 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxSelectionCellsHandler.js @@ -0,0 +1,297 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxSelectionCellsHandler + * + * An event handler that manages cell handlers and invokes their mouse event + * processing functions. + * + * Group: Events + * + * Event: mxEvent.ADD + * + * Fires if a cell has been added to the selection. The state + * property contains the that has been added. + * + * Event: mxEvent.REMOVE + * + * Fires if a cell has been remove from the selection. The state + * property contains the that has been removed. + * + * Parameters: + * + * graph - Reference to the enclosing . + */ +function mxSelectionCellsHandler(graph) +{ + mxEventSource.call(this); + + this.graph = graph; + this.handlers = new mxDictionary(); + this.graph.addMouseListener(this); + + this.refreshHandler = mxUtils.bind(this, function(sender, evt) + { + if (this.isEnabled()) + { + this.refresh(); + } + }); + + this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler); + this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler); + this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler); + this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler); + this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler); + this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler); + this.graph.getView().addListener(mxEvent.UP, this.refreshHandler); +}; + +/** + * Extends mxEventSource. + */ +mxUtils.extend(mxSelectionCellsHandler, mxEventSource); + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxSelectionCellsHandler.prototype.graph = null; + +/** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ +mxSelectionCellsHandler.prototype.enabled = true; + +/** + * Variable: refreshHandler + * + * Keeps a reference to an event listener for later removal. + */ +mxSelectionCellsHandler.prototype.refreshHandler = null; + +/** + * Variable: maxHandlers + * + * Defines the maximum number of handlers to paint individually. Default is 100. + */ +mxSelectionCellsHandler.prototype.maxHandlers = 100; + +/** + * Variable: handlers + * + * that maps from cells to handlers. + */ +mxSelectionCellsHandler.prototype.handlers = null; + +/** + * Function: isEnabled + * + * Returns . + */ +mxSelectionCellsHandler.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setEnabled + * + * Sets . + */ +mxSelectionCellsHandler.prototype.setEnabled = function(value) +{ + this.enabled = value; +}; + +/** + * Function: getHandler + * + * Returns the handler for the given cell. + */ +mxSelectionCellsHandler.prototype.getHandler = function(cell) +{ + return this.handlers.get(cell); +}; + +/** + * Function: reset + * + * Resets all handlers. + */ +mxSelectionCellsHandler.prototype.reset = function() +{ + this.handlers.visit(function(key, handler) + { + handler.reset.apply(handler); + }); +}; + +/** + * Function: refresh + * + * Reloads or updates all handlers. + */ +mxSelectionCellsHandler.prototype.refresh = function() +{ + // Removes all existing handlers + var oldHandlers = this.handlers; + this.handlers = new mxDictionary(); + + // Creates handles for all selection cells + var tmp = this.graph.getSelectionCells(); + + for (var i = 0; i < tmp.length; i++) + { + var state = this.graph.view.getState(tmp[i]); + + if (state != null) + { + var handler = oldHandlers.remove(tmp[i]); + + if (handler != null) + { + if (handler.state != state) + { + handler.destroy(); + handler = null; + } + else if (!this.isHandlerActive(handler)) + { + if (handler.refresh != null) + { + handler.refresh(); + } + + handler.redraw(); + } + } + + if (handler == null) + { + handler = this.graph.createHandler(state); + this.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state)); + } + + if (handler != null) + { + this.handlers.put(tmp[i], handler); + } + } + } + + // Destroys all unused handlers + oldHandlers.visit(mxUtils.bind(this, function(key, handler) + { + this.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state)); + handler.destroy(); + })); +}; + +/** + * Function: isHandlerActive + * + * Returns true if the given handler is active and should not be redrawn. + */ +mxSelectionCellsHandler.prototype.isHandlerActive = function(handler) +{ + return handler.index != null; +}; + +/** + * Function: updateHandler + * + * Updates the handler for the given shape if one exists. + */ +mxSelectionCellsHandler.prototype.updateHandler = function(state) +{ + var handler = this.handlers.remove(state.cell); + + if (handler != null) + { + handler.destroy(); + handler = this.graph.createHandler(state); + + if (handler != null) + { + this.handlers.put(state.cell, handler); + } + } +}; + +/** + * Function: mouseDown + * + * Redirects the given event to the handlers. + */ +mxSelectionCellsHandler.prototype.mouseDown = function(sender, me) +{ + if (this.graph.isEnabled() && this.isEnabled()) + { + var args = [sender, me]; + + this.handlers.visit(function(key, handler) + { + handler.mouseDown.apply(handler, args); + }); + } +}; + +/** + * Function: mouseMove + * + * Redirects the given event to the handlers. + */ +mxSelectionCellsHandler.prototype.mouseMove = function(sender, me) +{ + if (this.graph.isEnabled() && this.isEnabled()) + { + var args = [sender, me]; + + this.handlers.visit(function(key, handler) + { + handler.mouseMove.apply(handler, args); + }); + } +}; + +/** + * Function: mouseUp + * + * Redirects the given event to the handlers. + */ +mxSelectionCellsHandler.prototype.mouseUp = function(sender, me) +{ + if (this.graph.isEnabled() && this.isEnabled()) + { + var args = [sender, me]; + + this.handlers.visit(function(key, handler) + { + handler.mouseUp.apply(handler, args); + }); + } +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ +mxSelectionCellsHandler.prototype.destroy = function() +{ + this.graph.removeMouseListener(this); + + if (this.refreshHandler != null) + { + this.graph.getSelectionModel().removeListener(this.refreshHandler); + this.graph.getModel().removeListener(this.refreshHandler); + this.graph.getView().removeListener(this.refreshHandler); + this.refreshHandler = null; + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxTooltipHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxTooltipHandler.js new file mode 100644 index 0000000..3ef42b7 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxTooltipHandler.js @@ -0,0 +1,348 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxTooltipHandler + * + * Graph event handler that displays tooltips. is used to + * get the tooltip for a cell or handle. This handler is built-into + * and enabled using . + * + * Example: + * + * (code> + * new mxTooltipHandler(graph); + * (end) + * + * Constructor: mxTooltipHandler + * + * Constructs an event handler that displays tooltips with the specified + * delay (in milliseconds). If no delay is specified then a default delay + * of 500 ms (0.5 sec) is used. + * + * Parameters: + * + * graph - Reference to the enclosing . + * delay - Optional delay in milliseconds. + */ +function mxTooltipHandler(graph, delay) +{ + if (graph != null) + { + this.graph = graph; + this.delay = delay || 500; + this.graph.addMouseListener(this); + } +}; + +/** + * Variable: zIndex + * + * Specifies the zIndex for the tooltip and its shadow. Default is 10005. + */ +mxTooltipHandler.prototype.zIndex = 10005; + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxTooltipHandler.prototype.graph = null; + +/** + * Variable: delay + * + * Delay to show the tooltip in milliseconds. Default is 500. + */ +mxTooltipHandler.prototype.delay = null; + +/** + * Variable: ignoreTouchEvents + * + * Specifies if touch and pen events should be ignored. Default is true. + */ +mxTooltipHandler.prototype.ignoreTouchEvents = true; + +/** + * Variable: hideOnHover + * + * Specifies if the tooltip should be hidden if the mouse is moved over the + * current cell. Default is false. + */ +mxTooltipHandler.prototype.hideOnHover = false; + +/** + * Variable: destroyed + * + * True if this handler was destroyed using . + */ +mxTooltipHandler.prototype.destroyed = false; + +/** + * Variable: enabled + * + * Specifies if events are handled. Default is true. + */ +mxTooltipHandler.prototype.enabled = true; + +/** + * Function: isEnabled + * + * Returns true if events are handled. This implementation + * returns . + */ +mxTooltipHandler.prototype.isEnabled = function() +{ + return this.enabled; +}; + +/** + * Function: setEnabled + * + * Enables or disables event handling. This implementation + * updates . + */ +mxTooltipHandler.prototype.setEnabled = function(enabled) +{ + this.enabled = enabled; +}; + +/** + * Function: isHideOnHover + * + * Returns . + */ +mxTooltipHandler.prototype.isHideOnHover = function() +{ + return this.hideOnHover; +}; + +/** + * Function: setHideOnHover + * + * Sets . + */ +mxTooltipHandler.prototype.setHideOnHover = function(value) +{ + this.hideOnHover = value; +}; + +/** + * Function: init + * + * Initializes the DOM nodes required for this tooltip handler. + */ +mxTooltipHandler.prototype.init = function() +{ + if (document.body != null) + { + this.div = document.createElement('div'); + this.div.className = 'mxTooltip'; + this.div.style.visibility = 'hidden'; + + document.body.appendChild(this.div); + + mxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt) + { + this.hideTooltip(); + })); + } +}; + +/** + * Function: getStateForEvent + * + * Returns the to be used for showing a tooltip for this event. + */ +mxTooltipHandler.prototype.getStateForEvent = function(me) +{ + return me.getState(); +}; + +/** + * Function: mouseDown + * + * Handles the event by initiating a rubberband selection. By consuming the + * event all subsequent events of the gesture are redirected to this + * handler. + */ +mxTooltipHandler.prototype.mouseDown = function(sender, me) +{ + this.reset(me, false); + this.hideTooltip(); +}; + +/** + * Function: mouseMove + * + * Handles the event by updating the rubberband selection. + */ +mxTooltipHandler.prototype.mouseMove = function(sender, me) +{ + if (me.getX() != this.lastX || me.getY() != this.lastY) + { + this.reset(me, true); + var state = this.getStateForEvent(me); + + if (this.isHideOnHover() || state != this.state || (me.getSource() != this.node && + (!this.stateSource || (state != null && this.stateSource == + (me.isSource(state.shape) || !me.isSource(state.text)))))) + { + this.hideTooltip(); + } + } + + this.lastX = me.getX(); + this.lastY = me.getY(); +}; + +/** + * Function: mouseUp + * + * Handles the event by resetting the tooltip timer or hiding the existing + * tooltip. + */ +mxTooltipHandler.prototype.mouseUp = function(sender, me) +{ + this.reset(me, true); + this.hideTooltip(); +}; + + +/** + * Function: resetTimer + * + * Resets the timer. + */ +mxTooltipHandler.prototype.resetTimer = function() +{ + if (this.thread != null) + { + window.clearTimeout(this.thread); + this.thread = null; + } +}; + +/** + * Function: reset + * + * Resets and/or restarts the timer to trigger the display of the tooltip. + */ +mxTooltipHandler.prototype.reset = function(me, restart, state) +{ + if (!this.ignoreTouchEvents || mxEvent.isMouseEvent(me.getEvent())) + { + this.resetTimer(); + state = (state != null) ? state : this.getStateForEvent(me); + + if (restart && this.isEnabled() && state != null && (this.div == null || + this.div.style.visibility == 'hidden')) + { + var node = me.getSource(); + var x = me.getX(); + var y = me.getY(); + var stateSource = me.isSource(state.shape) || me.isSource(state.text); + + this.thread = window.setTimeout(mxUtils.bind(this, function() + { + if (!this.graph.isEditing() && !this.graph.popupMenuHandler.isMenuShowing() && !this.graph.isMouseDown) + { + // Uses information from inside event cause using the event at + // this (delayed) point in time is not possible in IE as it no + // longer contains the required information (member not found) + var tip = this.graph.getTooltip(state, node, x, y); + this.show(tip, x, y); + this.state = state; + this.node = node; + this.stateSource = stateSource; + } + }), this.delay); + } + } +}; + +/** + * Function: hide + * + * Hides the tooltip and resets the timer. + */ +mxTooltipHandler.prototype.hide = function() +{ + this.resetTimer(); + this.hideTooltip(); +}; + +/** + * Function: hideTooltip + * + * Hides the tooltip. + */ +mxTooltipHandler.prototype.hideTooltip = function() +{ + if (this.div != null) + { + this.div.style.visibility = 'hidden'; + this.div.innerHTML = ''; + } +}; + +/** + * Function: show + * + * Shows the tooltip for the specified cell and optional index at the + * specified location (with a vertical offset of 10 pixels). + */ +mxTooltipHandler.prototype.show = function(tip, x, y) +{ + if (!this.destroyed && tip != null && tip.length > 0) + { + // Initializes the DOM nodes if required + if (this.div == null) + { + this.init(); + } + + var origin = mxUtils.getScrollOrigin(); + + this.div.style.zIndex = this.zIndex; + this.div.style.left = (x + origin.x) + 'px'; + this.div.style.top = (y + mxConstants.TOOLTIP_VERTICAL_OFFSET + + origin.y) + 'px'; + + if (!mxUtils.isNode(tip)) + { + this.div.innerHTML = tip.replace(/\n/g, '
'); + } + else + { + this.div.innerHTML = ''; + this.div.appendChild(tip); + } + + this.div.style.visibility = ''; + mxUtils.fit(this.div); + } +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ +mxTooltipHandler.prototype.destroy = function() +{ + if (!this.destroyed) + { + this.graph.removeMouseListener(this); + mxEvent.release(this.div); + + if (this.div != null && this.div.parentNode != null) + { + this.div.parentNode.removeChild(this.div); + } + + this.destroyed = true; + this.div = null; + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/handler/mxVertexHandler.js b/oaweb/public/cherry/drawio/src/js/handler/mxVertexHandler.js new file mode 100644 index 0000000..aa2331b --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/handler/mxVertexHandler.js @@ -0,0 +1,1966 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxVertexHandler + * + * Event handler for resizing cells. This handler is automatically created in + * . + * + * Constructor: mxVertexHandler + * + * Constructs an event handler that allows to resize vertices + * and groups. + * + * Parameters: + * + * state - of the cell to be resized. + */ +function mxVertexHandler(state) +{ + if (state != null) + { + this.state = state; + this.init(); + + // Handles escape keystrokes + this.escapeHandler = mxUtils.bind(this, function(sender, evt) + { + if (this.livePreview && this.index != null) + { + // Redraws the live preview + this.state.view.graph.cellRenderer.redraw(this.state, true); + + // Redraws connected edges + this.state.view.invalidate(this.state.cell); + this.state.invalid = false; + this.state.view.validate(); + } + + this.reset(); + }); + + this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler); + } +}; + +/** + * Variable: graph + * + * Reference to the enclosing . + */ +mxVertexHandler.prototype.graph = null; + +/** + * Variable: state + * + * Reference to the being modified. + */ +mxVertexHandler.prototype.state = null; + +/** + * Variable: singleSizer + * + * Specifies if only one sizer handle at the bottom, right corner should be + * used. Default is false. + */ +mxVertexHandler.prototype.singleSizer = false; + +/** + * Variable: index + * + * Holds the index of the current handle. + */ +mxVertexHandler.prototype.index = null; + +/** + * Variable: allowHandleBoundsCheck + * + * Specifies if the bounds of handles should be used for hit-detection in IE or + * if > 0. Default is true. + */ +mxVertexHandler.prototype.allowHandleBoundsCheck = true; + +/** + * Variable: handleImage + * + * Optional to be used as handles. Default is null. + */ +mxVertexHandler.prototype.handleImage = null; + +/** + * Variable: tolerance + * + * Optional tolerance for hit-detection in . Default is 0. + */ +mxVertexHandler.prototype.tolerance = 0; + +/** + * Variable: rotationEnabled + * + * Specifies if a rotation handle should be visible. Default is false. + */ +mxVertexHandler.prototype.rotationEnabled = false; + +/** + * Variable: parentHighlightEnabled + * + * Specifies if the parent should be highlighted if a child cell is selected. + * Default is false. + */ +mxVertexHandler.prototype.parentHighlightEnabled = false; + +/** + * Variable: rotationRaster + * + * Specifies if rotation steps should be "rasterized" depening on the distance + * to the handle. Default is true. + */ +mxVertexHandler.prototype.rotationRaster = true; + +/** + * Variable: rotationCursor + * + * Specifies the cursor for the rotation handle. Default is 'crosshair'. + */ +mxVertexHandler.prototype.rotationCursor = 'crosshair'; + +/** + * Variable: livePreview + * + * Specifies if resize should change the cell in-place. This is an experimental + * feature for non-touch devices. Default is false. + */ +mxVertexHandler.prototype.livePreview = false; + +/** + * Variable: manageSizers + * + * Specifies if sizers should be hidden and spaced if the vertex is small. + * Default is false. + */ +mxVertexHandler.prototype.manageSizers = false; + +/** + * Variable: constrainGroupByChildren + * + * Specifies if the size of groups should be constrained by the children. + * Default is false. + */ +mxVertexHandler.prototype.constrainGroupByChildren = false; + +/** + * Variable: rotationHandleVSpacing + * + * Vertical spacing for rotation icon. Default is -16. + */ +mxVertexHandler.prototype.rotationHandleVSpacing = -16; + +/** + * Variable: horizontalOffset + * + * The horizontal offset for the handles. This is updated in + * if is true and the sizers are offset horizontally. + */ +mxVertexHandler.prototype.horizontalOffset = 0; + +/** + * Variable: verticalOffset + * + * The horizontal offset for the handles. This is updated in + * if is true and the sizers are offset vertically. + */ +mxVertexHandler.prototype.verticalOffset = 0; + +/** + * Function: init + * + * Initializes the shapes required for this vertex handler. + */ +mxVertexHandler.prototype.init = function() +{ + this.graph = this.state.view.graph; + this.selectionBounds = this.getSelectionBounds(this.state); + this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height); + this.selectionBorder = this.createSelectionShape(this.bounds); + // VML dialect required here for event transparency in IE + this.selectionBorder.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + this.selectionBorder.pointerEvents = false; + this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0'); + this.selectionBorder.init(this.graph.getView().getOverlayPane()); + mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state); + + if (this.graph.isCellMovable(this.state.cell)) + { + this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX); + } + + // Adds the sizer handles + if (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) + { + var resizable = this.graph.isCellResizable(this.state.cell); + this.sizers = []; + + if (resizable || (this.graph.isLabelMovable(this.state.cell) && + this.state.width >= 2 && this.state.height >= 2)) + { + var i = 0; + + if (resizable) + { + if (!this.singleSizer) + { + this.sizers.push(this.createSizer('nw-resize', i++)); + this.sizers.push(this.createSizer('n-resize', i++)); + this.sizers.push(this.createSizer('ne-resize', i++)); + this.sizers.push(this.createSizer('w-resize', i++)); + this.sizers.push(this.createSizer('e-resize', i++)); + this.sizers.push(this.createSizer('sw-resize', i++)); + this.sizers.push(this.createSizer('s-resize', i++)); + } + + this.sizers.push(this.createSizer('se-resize', i++)); + } + + var geo = this.graph.model.getGeometry(this.state.cell); + + if (geo != null && !geo.relative && !this.graph.isSwimlane(this.state.cell) && + this.graph.isLabelMovable(this.state.cell)) + { + // Marks this as the label handle for getHandleForEvent + this.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE, mxEvent.LABEL_HANDLE, mxConstants.LABEL_HANDLE_SIZE, mxConstants.LABEL_HANDLE_FILLCOLOR); + this.sizers.push(this.labelShape); + } + } + else if (this.graph.isCellMovable(this.state.cell) && !this.graph.isCellResizable(this.state.cell) && + this.state.width < 2 && this.state.height < 2) + { + this.labelShape = this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX, + mxEvent.LABEL_HANDLE, null, mxConstants.LABEL_HANDLE_FILLCOLOR); + this.sizers.push(this.labelShape); + } + } + + // Adds the rotation handler + if (this.isRotationHandleVisible()) + { + this.rotationShape = this.createSizer(this.rotationCursor, mxEvent.ROTATION_HANDLE, + mxConstants.HANDLE_SIZE + 3, mxConstants.HANDLE_FILLCOLOR); + this.sizers.push(this.rotationShape); + } + + this.customHandles = this.createCustomHandles(); + this.redraw(); + + if (this.constrainGroupByChildren) + { + this.updateMinBounds(); + } +}; + +/** + * Function: isRotationHandleVisible + * + * Returns true if the rotation handle should be showing. + */ +mxVertexHandler.prototype.isRotationHandleVisible = function() +{ + return this.graph.isEnabled() && this.rotationEnabled && this.graph.isCellRotatable(this.state.cell) && + (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) && + this.state.width >= 2 && this.state.height >= 2; +}; + +/** + * Function: isConstrainedEvent + * + * Returns true if the aspect ratio if the cell should be maintained. + */ +mxVertexHandler.prototype.isConstrainedEvent = function(me) +{ + return mxEvent.isShiftDown(me.getEvent()) || this.state.style[mxConstants.STYLE_ASPECT] == 'fixed'; +}; + +/** + * Function: isCenteredEvent + * + * Returns true if the center of the vertex should be maintained during the resize. + */ +mxVertexHandler.prototype.isCenteredEvent = function(state, me) +{ + return false; +}; + +/** + * Function: createCustomHandles + * + * Returns an array of custom handles. This implementation returns null. + */ +mxVertexHandler.prototype.createCustomHandles = function() +{ + return null; +}; + +/** + * Function: updateMinBounds + * + * Initializes the shapes required for this vertex handler. + */ +mxVertexHandler.prototype.updateMinBounds = function() +{ + var children = this.graph.getChildCells(this.state.cell); + + if (children.length > 0) + { + this.minBounds = this.graph.view.getBounds(children); + + if (this.minBounds != null) + { + var s = this.state.view.scale; + var t = this.state.view.translate; + + this.minBounds.x -= this.state.x; + this.minBounds.y -= this.state.y; + this.minBounds.x /= s; + this.minBounds.y /= s; + this.minBounds.width /= s; + this.minBounds.height /= s; + this.x0 = this.state.x / s - t.x; + this.y0 = this.state.y / s - t.y; + } + } +}; + +/** + * Function: getSelectionBounds + * + * Returns the mxRectangle that defines the bounds of the selection + * border. + */ +mxVertexHandler.prototype.getSelectionBounds = function(state) +{ + return new mxRectangle(Math.round(state.x), Math.round(state.y), Math.round(state.width), Math.round(state.height)); +}; + +/** + * Function: createParentHighlightShape + * + * Creates the shape used to draw the selection border. + */ +mxVertexHandler.prototype.createParentHighlightShape = function(bounds) +{ + return this.createSelectionShape(bounds); +}; + +/** + * Function: createSelectionShape + * + * Creates the shape used to draw the selection border. + */ +mxVertexHandler.prototype.createSelectionShape = function(bounds) +{ + var shape = new mxRectangleShape(bounds, null, this.getSelectionColor()); + shape.strokewidth = this.getSelectionStrokeWidth(); + shape.isDashed = this.isSelectionDashed(); + + return shape; +}; + +/** + * Function: getSelectionColor + * + * Returns . + */ +mxVertexHandler.prototype.getSelectionColor = function() +{ + return mxConstants.VERTEX_SELECTION_COLOR; +}; + +/** + * Function: getSelectionStrokeWidth + * + * Returns . + */ +mxVertexHandler.prototype.getSelectionStrokeWidth = function() +{ + return mxConstants.VERTEX_SELECTION_STROKEWIDTH; +}; + +/** + * Function: isSelectionDashed + * + * Returns . + */ +mxVertexHandler.prototype.isSelectionDashed = function() +{ + return mxConstants.VERTEX_SELECTION_DASHED; +}; + +/** + * Function: createSizer + * + * Creates a sizer handle for the specified cursor and index and returns + * the new that represents the handle. + */ +mxVertexHandler.prototype.createSizer = function(cursor, index, size, fillColor) +{ + size = size || mxConstants.HANDLE_SIZE; + + var bounds = new mxRectangle(0, 0, size, size); + var sizer = this.createSizerShape(bounds, index, fillColor); + + if (sizer.isHtmlAllowed() && this.state.text != null && this.state.text.node.parentNode == this.graph.container) + { + sizer.bounds.height -= 1; + sizer.bounds.width -= 1; + sizer.dialect = mxConstants.DIALECT_STRICTHTML; + sizer.init(this.graph.container); + } + else + { + sizer.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG; + sizer.init(this.graph.getView().getOverlayPane()); + } + + mxEvent.redirectMouseEvents(sizer.node, this.graph, this.state); + + if (this.graph.isEnabled()) + { + sizer.setCursor(cursor); + } + + if (!this.isSizerVisible(index)) + { + sizer.visible = false; + } + + return sizer; +}; + +/** + * Function: isSizerVisible + * + * Returns true if the sizer for the given index is visible. + * This returns true for all given indices. + */ +mxVertexHandler.prototype.isSizerVisible = function(index) +{ + return true; +}; + +/** + * Function: createSizerShape + * + * Creates the shape used for the sizer handle for the specified bounds an + * index. Only images and rectangles should be returned if support for HTML + * labels with not foreign objects is required. + */ +mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor) +{ + if (this.handleImage != null) + { + bounds = new mxRectangle(bounds.x, bounds.y, this.handleImage.width, this.handleImage.height); + var shape = new mxImageShape(bounds, this.handleImage.src); + + // Allows HTML rendering of the images + shape.preserveImageAspect = false; + + return shape; + } + else if (index == mxEvent.ROTATION_HANDLE) + { + return new mxEllipse(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); + } + else + { + return new mxRectangleShape(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR); + } +}; + +/** + * Function: createBounds + * + * Helper method to create an around the given centerpoint + * with a width and height of 2*s or 6, if no s is given. + */ +mxVertexHandler.prototype.moveSizerTo = function(shape, x, y) +{ + if (shape != null) + { + shape.bounds.x = Math.floor(x - shape.bounds.width / 2); + shape.bounds.y = Math.floor(y - shape.bounds.height / 2); + + // Fixes visible inactive handles in VML + if (shape.node != null && shape.node.style.display != 'none') + { + shape.redraw(); + } + } +}; + +/** + * Function: getHandleForEvent + * + * Returns the index of the handle for the given event. This returns the index + * of the sizer from where the event originated or . + */ +mxVertexHandler.prototype.getHandleForEvent = function(me) +{ + // Connection highlight may consume events before they reach sizer handle + var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1; + var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ? + new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null; + + function checkShape(shape) + { + return shape != null && (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit) && + shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden')); + } + + if (this.customHandles != null && this.isCustomHandleEvent(me)) + { + // Inverse loop order to match display order + for (var i = this.customHandles.length - 1; i >= 0; i--) + { + if (checkShape(this.customHandles[i].shape)) + { + // LATER: Return reference to active shape + return mxEvent.CUSTOM_HANDLE - i; + } + } + } + + if (checkShape(this.rotationShape)) + { + return mxEvent.ROTATION_HANDLE; + } + else if (checkShape(this.labelShape)) + { + return mxEvent.LABEL_HANDLE; + } + + if (this.sizers != null) + { + for (var i = 0; i < this.sizers.length; i++) + { + if (checkShape(this.sizers[i])) + { + return i; + } + } + } + + return null; +}; + +/** + * Function: isCustomHandleEvent + * + * Returns true if the given event allows custom handles to be changed. This + * implementation returns true. + */ +mxVertexHandler.prototype.isCustomHandleEvent = function(me) +{ + return true; +}; + +/** + * Function: mouseDown + * + * Handles the event if a handle has been clicked. By consuming the + * event all subsequent events of the gesture are redirected to this + * handler. + */ +mxVertexHandler.prototype.mouseDown = function(sender, me) +{ + var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 0; + + if (!me.isConsumed() && this.graph.isEnabled() && (tol > 0 || me.getState() == this.state)) + { + var handle = this.getHandleForEvent(me); + + if (handle != null) + { + this.start(me.getGraphX(), me.getGraphY(), handle); + me.consume(); + } + } +}; + +/** + * Function: isLivePreviewBorder + * + * Called if is enabled to check if a border should be painted. + * This implementation returns true if the shape is transparent. + */ +mxVertexHandler.prototype.isLivePreviewBorder = function() +{ + return this.state.shape != null && this.state.shape.fill == null && this.state.shape.stroke == null; +}; + +/** + * Function: start + * + * Starts the handling of the mouse gesture. + */ +mxVertexHandler.prototype.start = function(x, y, index) +{ + this.inTolerance = true; + this.childOffsetX = 0; + this.childOffsetY = 0; + this.index = index; + this.startX = x; + this.startY = y; + + // Saves reference to parent state + var model = this.state.view.graph.model; + var parent = model.getParent(this.state.cell); + + if (this.state.view.currentRoot != parent && (model.isVertex(parent) || model.isEdge(parent))) + { + this.parentState = this.state.view.graph.view.getState(parent); + } + + // Creates a preview that can be on top of any HTML label + this.selectionBorder.node.style.display = (index == mxEvent.ROTATION_HANDLE) ? 'inline' : 'none'; + + // Creates the border that represents the new bounds + if (!this.livePreview || this.isLivePreviewBorder()) + { + this.preview = this.createSelectionShape(this.bounds); + + if (!(mxClient.IS_SVG && Number(this.state.style[mxConstants.STYLE_ROTATION] || '0') != 0) && + this.state.text != null && this.state.text.node.parentNode == this.graph.container) + { + this.preview.dialect = mxConstants.DIALECT_STRICTHTML; + this.preview.init(this.graph.container); + } + else + { + this.preview.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? + mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + this.preview.init(this.graph.view.getOverlayPane()); + } + } + + // Prepares the handles for live preview + if (this.livePreview) + { + this.hideSizers(); + + if (index == mxEvent.ROTATION_HANDLE) + { + this.rotationShape.node.style.display = ''; + } + else if (index == mxEvent.LABEL_HANDLE) + { + this.labelShape.node.style.display = ''; + } + else if (this.sizers != null && this.sizers[index] != null) + { + this.sizers[index].node.style.display = ''; + } + else if (index <= mxEvent.CUSTOM_HANDLE && this.customHandles != null) + { + this.customHandles[mxEvent.CUSTOM_HANDLE - index].setVisible(true); + } + + // Gets the array of connected edge handlers for redrawing + var edges = this.graph.getEdges(this.state.cell); + this.edgeHandlers = []; + + for (var i = 0; i < edges.length; i++) + { + var handler = this.graph.selectionCellsHandler.getHandler(edges[i]); + + if (handler != null) + { + this.edgeHandlers.push(handler); + } + } + } +}; + +/** + * Function: hideHandles + * + * Shortcut to . + */ +mxVertexHandler.prototype.setHandlesVisible = function(visible) +{ + if (this.sizers != null) + { + for (var i = 0; i < this.sizers.length; i++) + { + this.sizers[i].node.style.display = (visible) ? '' : 'none'; + } + } + + if (this.customHandles != null) + { + for (var i = 0; i < this.customHandles.length; i++) + { + this.customHandles[i].setVisible(visible); + } + } +}; + +/** + * Function: hideSizers + * + * Hides all sizers except. + * + * Starts the handling of the mouse gesture. + */ +mxVertexHandler.prototype.hideSizers = function() +{ + this.setHandlesVisible(false); +}; + +/** + * Function: checkTolerance + * + * Checks if the coordinates for the given event are within the + * . If the event is a mouse event then the tolerance is + * ignored. + */ +mxVertexHandler.prototype.checkTolerance = function(me) +{ + if (this.inTolerance && this.startX != null && this.startY != null) + { + if (mxEvent.isMouseEvent(me.getEvent()) || + Math.abs(me.getGraphX() - this.startX) > this.graph.tolerance || + Math.abs(me.getGraphY() - this.startY) > this.graph.tolerance) + { + this.inTolerance = false; + } + } +}; + +/** + * Function: updateHint + * + * Hook for subclassers do show details while the handler is active. + */ +mxVertexHandler.prototype.updateHint = function(me) { }; + +/** + * Function: removeHint + * + * Hooks for subclassers to hide details when the handler gets inactive. + */ +mxVertexHandler.prototype.removeHint = function() { }; + +/** + * Function: roundAngle + * + * Hook for rounding the angle. This uses Math.round. + */ +mxVertexHandler.prototype.roundAngle = function(angle) +{ + return Math.round(angle * 10) / 10; +}; + +/** + * Function: roundLength + * + * Hook for rounding the unscaled width or height. This uses Math.round. + */ +mxVertexHandler.prototype.roundLength = function(length) +{ + return Math.round(length); +}; + +/** + * Function: mouseMove + * + * Handles the event by updating the preview. + */ +mxVertexHandler.prototype.mouseMove = function(sender, me) +{ + if (!me.isConsumed() && this.index != null) + { + // Checks tolerance for ignoring single clicks + this.checkTolerance(me); + + if (!this.inTolerance) + { + if (this.index <= mxEvent.CUSTOM_HANDLE) + { + if (this.customHandles != null) + { + this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me); + this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = true; + } + } + else if (this.index == mxEvent.LABEL_HANDLE) + { + this.moveLabel(me); + } + else if (this.index == mxEvent.ROTATION_HANDLE) + { + this.rotateVertex(me); + } + else + { + this.resizeVertex(me); + } + + this.updateHint(me); + } + + me.consume(); + } + // Workaround for disabling the connect highlight when over handle + else if (!this.graph.isMouseDown && this.getHandleForEvent(me) != null) + { + me.consume(false); + } +}; + +/** + * Function: rotateVertex + * + * Rotates the vertex. + */ +mxVertexHandler.prototype.moveLabel = function(me) +{ + var point = new mxPoint(me.getGraphX(), me.getGraphY()); + var tr = this.graph.view.translate; + var scale = this.graph.view.scale; + + if (this.graph.isGridEnabledEvent(me.getEvent())) + { + point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale; + point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale; + } + + var index = (this.rotationShape != null) ? this.sizers.length - 2 : this.sizers.length - 1; + this.moveSizerTo(this.sizers[index], point.x, point.y); +}; + +/** + * Function: rotateVertex + * + * Rotates the vertex. + */ +mxVertexHandler.prototype.rotateVertex = function(me) +{ + var point = new mxPoint(me.getGraphX(), me.getGraphY()); + var dx = this.state.x + this.state.width / 2 - point.x; + var dy = this.state.y + this.state.height / 2 - point.y; + this.currentAlpha = (dx != 0) ? Math.atan(dy / dx) * 180 / Math.PI + 90 : ((dy < 0) ? 180 : 0); + + if (dx > 0) + { + this.currentAlpha -= 180; + } + + // Rotation raster + if (this.rotationRaster && this.graph.isGridEnabledEvent(me.getEvent())) + { + var dx = point.x - this.state.getCenterX(); + var dy = point.y - this.state.getCenterY(); + var dist = Math.abs(Math.sqrt(dx * dx + dy * dy) - 20) * 3; + var raster = Math.max(1, 5 * Math.min(3, Math.max(0, Math.round(80 / Math.abs(dist))))); + + this.currentAlpha = Math.round(this.currentAlpha / raster) * raster; + } + else + { + this.currentAlpha = this.roundAngle(this.currentAlpha); + } + + this.selectionBorder.rotation = this.currentAlpha; + this.selectionBorder.redraw(); + + if (this.livePreview) + { + this.redrawHandles(); + } +}; + +/** + * Function: rotateVertex + * + * Rotates the vertex. + */ +mxVertexHandler.prototype.resizeVertex = function(me) +{ + var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY()); + var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0'); + var point = new mxPoint(me.getGraphX(), me.getGraphY()); + var tr = this.graph.view.translate; + var scale = this.graph.view.scale; + var cos = Math.cos(-alpha); + var sin = Math.sin(-alpha); + + var dx = point.x - this.startX; + var dy = point.y - this.startY; + + // Rotates vector for mouse gesture + var tx = cos * dx - sin * dy; + var ty = sin * dx + cos * dy; + + dx = tx; + dy = ty; + + var geo = this.graph.getCellGeometry(this.state.cell); + this.unscaledBounds = this.union(geo, dx / scale, dy / scale, this.index, + this.graph.isGridEnabledEvent(me.getEvent()), 1, + new mxPoint(0, 0), this.isConstrainedEvent(me), + this.isCenteredEvent(this.state, me)); + + // Keeps vertex within maximum graph or parent bounds + if (!geo.relative) + { + var max = this.graph.getMaximumGraphBounds(); + + // Handles child cells + if (max != null && this.parentState != null) + { + max = mxRectangle.fromRectangle(max); + + max.x -= (this.parentState.x - tr.x * scale) / scale; + max.y -= (this.parentState.y - tr.y * scale) / scale; + } + + if (this.graph.isConstrainChild(this.state.cell)) + { + var tmp = this.graph.getCellContainmentArea(this.state.cell); + + if (tmp != null) + { + var overlap = this.graph.getOverlap(this.state.cell); + + if (overlap > 0) + { + tmp = mxRectangle.fromRectangle(tmp); + + tmp.x -= tmp.width * overlap; + tmp.y -= tmp.height * overlap; + tmp.width += 2 * tmp.width * overlap; + tmp.height += 2 * tmp.height * overlap; + } + + if (max == null) + { + max = tmp; + } + else + { + max = mxRectangle.fromRectangle(max); + max.intersect(tmp); + } + } + } + + if (max != null) + { + if (this.unscaledBounds.x < max.x) + { + this.unscaledBounds.width -= max.x - this.unscaledBounds.x; + this.unscaledBounds.x = max.x; + } + + if (this.unscaledBounds.y < max.y) + { + this.unscaledBounds.height -= max.y - this.unscaledBounds.y; + this.unscaledBounds.y = max.y; + } + + if (this.unscaledBounds.x + this.unscaledBounds.width > max.x + max.width) + { + this.unscaledBounds.width -= this.unscaledBounds.x + + this.unscaledBounds.width - max.x - max.width; + } + + if (this.unscaledBounds.y + this.unscaledBounds.height > max.y + max.height) + { + this.unscaledBounds.height -= this.unscaledBounds.y + + this.unscaledBounds.height - max.y - max.height; + } + } + } + + this.bounds = new mxRectangle(((this.parentState != null) ? this.parentState.x : tr.x * scale) + + (this.unscaledBounds.x) * scale, ((this.parentState != null) ? this.parentState.y : tr.y * scale) + + (this.unscaledBounds.y) * scale, this.unscaledBounds.width * scale, this.unscaledBounds.height * scale); + + if (geo.relative && this.parentState != null) + { + this.bounds.x += this.state.x - this.parentState.x; + this.bounds.y += this.state.y - this.parentState.y; + } + + cos = Math.cos(alpha); + sin = Math.sin(alpha); + + var c2 = new mxPoint(this.bounds.getCenterX(), this.bounds.getCenterY()); + + var dx = c2.x - ct.x; + var dy = c2.y - ct.y; + + var dx2 = cos * dx - sin * dy; + var dy2 = sin * dx + cos * dy; + + var dx3 = dx2 - dx; + var dy3 = dy2 - dy; + + var dx4 = this.bounds.x - this.state.x; + var dy4 = this.bounds.y - this.state.y; + + var dx5 = cos * dx4 - sin * dy4; + var dy5 = sin * dx4 + cos * dy4; + + this.bounds.x += dx3; + this.bounds.y += dy3; + + // Rounds unscaled bounds to int + this.unscaledBounds.x = this.roundLength(this.unscaledBounds.x + dx3 / scale); + this.unscaledBounds.y = this.roundLength(this.unscaledBounds.y + dy3 / scale); + this.unscaledBounds.width = this.roundLength(this.unscaledBounds.width); + this.unscaledBounds.height = this.roundLength(this.unscaledBounds.height); + + // Shifts the children according to parent offset + if (!this.graph.isCellCollapsed(this.state.cell) && (dx3 != 0 || dy3 != 0)) + { + this.childOffsetX = this.state.x - this.bounds.x + dx5; + this.childOffsetY = this.state.y - this.bounds.y + dy5; + } + else + { + this.childOffsetX = 0; + this.childOffsetY = 0; + } + + if (this.livePreview) + { + this.updateLivePreview(me); + } + + if (this.preview != null) + { + this.drawPreview(); + } +}; + +/** + * Function: updateLivePreview + * + * Repaints the live preview. + */ +mxVertexHandler.prototype.updateLivePreview = function(me) +{ + // TODO: Apply child offset to children in live preview + var scale = this.graph.view.scale; + var tr = this.graph.view.translate; + + // Saves current state + var tempState = this.state.clone(); + + // Temporarily changes size and origin + this.state.x = this.bounds.x; + this.state.y = this.bounds.y; + this.state.origin = new mxPoint(this.state.x / scale - tr.x, this.state.y / scale - tr.y); + this.state.width = this.bounds.width; + this.state.height = this.bounds.height; + + // Needed to force update of text bounds + this.state.unscaledWidth = null; + + // Redraws cell and handles + var off = this.state.absoluteOffset; + off = new mxPoint(off.x, off.y); + + // Required to store and reset absolute offset for updating label position + this.state.absoluteOffset.x = 0; + this.state.absoluteOffset.y = 0; + var geo = this.graph.getCellGeometry(this.state.cell); + + if (geo != null) + { + var offset = geo.offset || this.EMPTY_POINT; + + if (offset != null && !geo.relative) + { + this.state.absoluteOffset.x = this.state.view.scale * offset.x; + this.state.absoluteOffset.y = this.state.view.scale * offset.y; + } + + this.state.view.updateVertexLabelOffset(this.state); + } + + // Draws the live preview + this.state.view.graph.cellRenderer.redraw(this.state, true); + + // Redraws connected edges TODO: Include child edges + this.state.view.invalidate(this.state.cell); + this.state.invalid = false; + this.state.view.validate(); + this.redrawHandles(); + + // Restores current state + this.state.setState(tempState); +}; + +/** + * Function: mouseUp + * + * Handles the event by applying the changes to the geometry. + */ +mxVertexHandler.prototype.mouseUp = function(sender, me) +{ + if (this.index != null && this.state != null) + { + var point = new mxPoint(me.getGraphX(), me.getGraphY()); + + this.graph.getModel().beginUpdate(); + try + { + if (this.index <= mxEvent.CUSTOM_HANDLE) + { + if (this.customHandles != null) + { + this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = false; + this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute(); + } + } + else if (this.index == mxEvent.ROTATION_HANDLE) + { + if (this.currentAlpha != null) + { + var delta = this.currentAlpha - (this.state.style[mxConstants.STYLE_ROTATION] || 0); + + if (delta != 0) + { + this.rotateCell(this.state.cell, delta); + } + } + else + { + this.rotateClick(); + } + } + else + { + var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent()); + var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0'); + var cos = Math.cos(-alpha); + var sin = Math.sin(-alpha); + + var dx = point.x - this.startX; + var dy = point.y - this.startY; + + // Rotates vector for mouse gesture + var tx = cos * dx - sin * dy; + var ty = sin * dx + cos * dy; + + dx = tx; + dy = ty; + + var s = this.graph.view.scale; + var recurse = this.isRecursiveResize(this.state, me); + this.resizeCell(this.state.cell, this.roundLength(dx / s), this.roundLength(dy / s), + this.index, gridEnabled, this.isConstrainedEvent(me), recurse); + } + } + finally + { + this.graph.getModel().endUpdate(); + } + + me.consume(); + this.reset(); + } +}; + +/** + * Function: rotateCell + * + * Rotates the given cell to the given rotation. + */ +mxVertexHandler.prototype.isRecursiveResize = function(state, me) +{ + return this.graph.isRecursiveResize(this.state); +}; + +/** + * Function: rotateClick + * + * Hook for subclassers to implement a single click on the rotation handle. + * This code is executed as part of the model transaction. This implementation + * is empty. + */ +mxVertexHandler.prototype.rotateClick = function() { }; + +/** + * Function: rotateCell + * + * Rotates the given cell and its children by the given angle in degrees. + * + * Parameters: + * + * cell - to be rotated. + * angle - Angle in degrees. + */ +mxVertexHandler.prototype.rotateCell = function(cell, angle, parent) +{ + if (angle != 0) + { + var model = this.graph.getModel(); + + if (model.isVertex(cell) || model.isEdge(cell)) + { + if (!model.isEdge(cell)) + { + var state = this.graph.view.getState(cell); + var style = (state != null) ? state.style : this.graph.getCellStyle(cell); + + if (style != null) + { + var total = (style[mxConstants.STYLE_ROTATION] || 0) + angle; + this.graph.setCellStyles(mxConstants.STYLE_ROTATION, total, [cell]); + } + } + + var geo = this.graph.getCellGeometry(cell); + + if (geo != null) + { + var pgeo = this.graph.getCellGeometry(parent); + + if (pgeo != null && !model.isEdge(parent)) + { + geo = geo.clone(); + geo.rotate(angle, new mxPoint(pgeo.width / 2, pgeo.height / 2)); + model.setGeometry(cell, geo); + } + + if ((model.isVertex(cell) && !geo.relative) || model.isEdge(cell)) + { + // Recursive rotation + var childCount = model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + this.rotateCell(model.getChildAt(cell, i), angle, cell); + } + } + } + } + } +}; + +/** + * Function: reset + * + * Resets the state of this handler. + */ +mxVertexHandler.prototype.reset = function() +{ + if (this.sizers != null && this.index != null && this.sizers[this.index] != null && + this.sizers[this.index].node.style.display == 'none') + { + this.sizers[this.index].node.style.display = ''; + } + + this.currentAlpha = null; + this.inTolerance = null; + this.index = null; + + // TODO: Reset and redraw cell states for live preview + if (this.preview != null) + { + this.preview.destroy(); + this.preview = null; + } + + if (this.livePreview && this.sizers != null) + { + for (var i = 0; i < this.sizers.length; i++) + { + if (this.sizers[i] != null) + { + this.sizers[i].node.style.display = ''; + } + } + } + + if (this.customHandles != null) + { + for (var i = 0; i < this.customHandles.length; i++) + { + if (this.customHandles[i].active) + { + this.customHandles[i].active = false; + this.customHandles[i].reset(); + } + else + { + this.customHandles[i].setVisible(true); + } + } + } + + // Checks if handler has been destroyed + if (this.selectionBorder != null) + { + this.selectionBorder.node.style.display = 'inline'; + this.selectionBounds = this.getSelectionBounds(this.state); + this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, + this.selectionBounds.width, this.selectionBounds.height); + this.drawPreview(); + } + + this.removeHint(); + this.redrawHandles(); + this.edgeHandlers = null; + this.unscaledBounds = null; +}; + +/** + * Function: resizeCell + * + * Uses the given vector to change the bounds of the given cell + * in the graph using . + */ +mxVertexHandler.prototype.resizeCell = function(cell, dx, dy, index, gridEnabled, constrained, recurse) +{ + var geo = this.graph.model.getGeometry(cell); + + if (geo != null) + { + if (index == mxEvent.LABEL_HANDLE) + { + var scale = this.graph.view.scale; + dx = Math.round((this.labelShape.bounds.getCenterX() - this.startX) / scale); + dy = Math.round((this.labelShape.bounds.getCenterY() - this.startY) / scale); + + geo = geo.clone(); + + if (geo.offset == null) + { + geo.offset = new mxPoint(dx, dy); + } + else + { + geo.offset.x += dx; + geo.offset.y += dy; + } + + this.graph.model.setGeometry(cell, geo); + } + else if (this.unscaledBounds != null) + { + var scale = this.graph.view.scale; + + if (this.childOffsetX != 0 || this.childOffsetY != 0) + { + this.moveChildren(cell, Math.round(this.childOffsetX / scale), Math.round(this.childOffsetY / scale)); + } + + this.graph.resizeCell(cell, this.unscaledBounds, recurse); + } + } +}; + +/** + * Function: moveChildren + * + * Moves the children of the given cell by the given vector. + */ +mxVertexHandler.prototype.moveChildren = function(cell, dx, dy) +{ + var model = this.graph.getModel(); + var childCount = model.getChildCount(cell); + + for (var i = 0; i < childCount; i++) + { + var child = model.getChildAt(cell, i); + var geo = this.graph.getCellGeometry(child); + + if (geo != null) + { + geo = geo.clone(); + geo.translate(dx, dy); + model.setGeometry(child, geo); + } + } +}; +/** + * Function: union + * + * Returns the union of the given bounds and location for the specified + * handle index. + * + * To override this to limit the size of vertex via a minWidth/-Height style, + * the following code can be used. + * + * (code) + * var vertexHandlerUnion = mxVertexHandler.prototype.union; + * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained) + * { + * var result = vertexHandlerUnion.apply(this, arguments); + * + * result.width = Math.max(result.width, mxUtils.getNumber(this.state.style, 'minWidth', 0)); + * result.height = Math.max(result.height, mxUtils.getNumber(this.state.style, 'minHeight', 0)); + * + * return result; + * }; + * (end) + * + * The minWidth/-Height style can then be used as follows: + * + * (code) + * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'minWidth=100;minHeight=100;'); + * (end) + * + * To override this to update the height for a wrapped text if the width of a vertex is + * changed, the following can be used. + * + * (code) + * var mxVertexHandlerUnion = mxVertexHandler.prototype.union; + * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained) + * { + * var result = mxVertexHandlerUnion.apply(this, arguments); + * var s = this.state; + * + * if (this.graph.isHtmlLabel(s.cell) && (index == 3 || index == 4) && + * s.text != null && s.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') + * { + * var label = this.graph.getLabel(s.cell); + * var fontSize = mxUtils.getNumber(s.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE); + * var ww = result.width / s.view.scale - s.text.spacingRight - s.text.spacingLeft + * + * result.height = mxUtils.getSizeForString(label, fontSize, s.style[mxConstants.STYLE_FONTFAMILY], ww).height; + * } + * + * return result; + * }; + * (end) + */ +mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained, centered) +{ + if (this.singleSizer) + { + var x = bounds.x + bounds.width + dx; + var y = bounds.y + bounds.height + dy; + + if (gridEnabled) + { + x = this.graph.snap(x / scale) * scale; + y = this.graph.snap(y / scale) * scale; + } + + var rect = new mxRectangle(bounds.x, bounds.y, 0, 0); + rect.add(new mxRectangle(x, y, 0, 0)); + + return rect; + } + else + { + var w0 = bounds.width; + var h0 = bounds.height; + var left = bounds.x - tr.x * scale; + var right = left + w0; + var top = bounds.y - tr.y * scale; + var bottom = top + h0; + + var cx = left + w0 / 2; + var cy = top + h0 / 2; + + if (index > 4 /* Bottom Row */) + { + bottom = bottom + dy; + + if (gridEnabled) + { + bottom = this.graph.snap(bottom / scale) * scale; + } + } + else if (index < 3 /* Top Row */) + { + top = top + dy; + + if (gridEnabled) + { + top = this.graph.snap(top / scale) * scale; + } + } + + if (index == 0 || index == 3 || index == 5 /* Left */) + { + left += dx; + + if (gridEnabled) + { + left = this.graph.snap(left / scale) * scale; + } + } + else if (index == 2 || index == 4 || index == 7 /* Right */) + { + right += dx; + + if (gridEnabled) + { + right = this.graph.snap(right / scale) * scale; + } + } + + var width = right - left; + var height = bottom - top; + + if (constrained) + { + var geo = this.graph.getCellGeometry(this.state.cell); + + if (geo != null) + { + var aspect = geo.width / geo.height; + + if (index== 1 || index== 2 || index == 7 || index == 6) + { + width = height * aspect; + } + else + { + height = width / aspect; + } + + if (index == 0) + { + left = right - width; + top = bottom - height; + } + } + } + + if (centered) + { + width += (width - w0); + height += (height - h0); + + var cdx = cx - (left + width / 2); + var cdy = cy - (top + height / 2); + + left += cdx; + top += cdy; + right += cdx; + bottom += cdy; + } + + // Flips over left side + if (width < 0) + { + left += width; + width = Math.abs(width); + } + + // Flips over top side + if (height < 0) + { + top += height; + height = Math.abs(height); + } + + var result = new mxRectangle(left + tr.x * scale, top + tr.y * scale, width, height); + + if (this.minBounds != null) + { + result.width = Math.max(result.width, this.minBounds.x * scale + this.minBounds.width * scale + + Math.max(0, this.x0 * scale - result.x)); + result.height = Math.max(result.height, this.minBounds.y * scale + this.minBounds.height * scale + + Math.max(0, this.y0 * scale - result.y)); + } + + return result; + } +}; + +/** + * Function: redraw + * + * Redraws the handles and the preview. + */ +mxVertexHandler.prototype.redraw = function() +{ + this.selectionBounds = this.getSelectionBounds(this.state); + this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height); + + this.redrawHandles(); + this.drawPreview(); +}; + +/** + * Returns the padding to be used for drawing handles for the current . + */ +mxVertexHandler.prototype.getHandlePadding = function() +{ + // KNOWN: Tolerance depends on event type (eg. 0 for mouse events) + var result = new mxPoint(0, 0); + var tol = this.tolerance; + + if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null && + (this.bounds.width < 2 * this.sizers[0].bounds.width + 2 * tol || + this.bounds.height < 2 * this.sizers[0].bounds.height + 2 * tol)) + { + tol /= 2; + + result.x = this.sizers[0].bounds.width + tol; + result.y = this.sizers[0].bounds.height + tol; + } + + return result; +}; + +/** + * Function: redrawHandles + * + * Redraws the handles. To hide certain handles the following code can be used. + * + * (code) + * mxVertexHandler.prototype.redrawHandles = function() + * { + * mxVertexHandlerRedrawHandles.apply(this, arguments); + * + * if (this.sizers != null && this.sizers.length > 7) + * { + * this.sizers[1].node.style.display = 'none'; + * this.sizers[6].node.style.display = 'none'; + * } + * }; + * (end) + */ +mxVertexHandler.prototype.redrawHandles = function() +{ + var tol = this.tolerance; + this.horizontalOffset = 0; + this.verticalOffset = 0; + var s = this.bounds; + + if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null) + { + if (this.index == null && this.manageSizers && this.sizers.length >= 8) + { + // KNOWN: Tolerance depends on event type (eg. 0 for mouse events) + var padding = this.getHandlePadding(); + this.horizontalOffset = padding.x; + this.verticalOffset = padding.y; + + if (this.horizontalOffset != 0 || this.verticalOffset != 0) + { + s = new mxRectangle(s.x, s.y, s.width, s.height); + + s.x -= this.horizontalOffset / 2; + s.width += this.horizontalOffset; + s.y -= this.verticalOffset / 2; + s.height += this.verticalOffset; + } + + if (this.sizers.length >= 8) + { + if ((s.width < 2 * this.sizers[0].bounds.width + 2 * tol) || + (s.height < 2 * this.sizers[0].bounds.height + 2 * tol)) + { + this.sizers[0].node.style.display = 'none'; + this.sizers[2].node.style.display = 'none'; + this.sizers[5].node.style.display = 'none'; + this.sizers[7].node.style.display = 'none'; + } + else + { + this.sizers[0].node.style.display = ''; + this.sizers[2].node.style.display = ''; + this.sizers[5].node.style.display = ''; + this.sizers[7].node.style.display = ''; + } + } + } + + var r = s.x + s.width; + var b = s.y + s.height; + + if (this.singleSizer) + { + this.moveSizerTo(this.sizers[0], r, b); + } + else + { + var cx = s.x + s.width / 2; + var cy = s.y + s.height / 2; + + if (this.sizers.length >= 8) + { + var crs = ['nw-resize', 'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize']; + + var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0'); + var cos = Math.cos(alpha); + var sin = Math.sin(alpha); + + var da = Math.round(alpha * 4 / Math.PI); + + var ct = new mxPoint(s.getCenterX(), s.getCenterY()); + var pt = mxUtils.getRotatedPoint(new mxPoint(s.x, s.y), cos, sin, ct); + + this.moveSizerTo(this.sizers[0], pt.x, pt.y); + this.sizers[0].setCursor(crs[mxUtils.mod(0 + da, crs.length)]); + + pt.x = cx; + pt.y = s.y; + pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); + + this.moveSizerTo(this.sizers[1], pt.x, pt.y); + this.sizers[1].setCursor(crs[mxUtils.mod(1 + da, crs.length)]); + + pt.x = r; + pt.y = s.y; + pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); + + this.moveSizerTo(this.sizers[2], pt.x, pt.y); + this.sizers[2].setCursor(crs[mxUtils.mod(2 + da, crs.length)]); + + pt.x = s.x; + pt.y = cy; + pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); + + this.moveSizerTo(this.sizers[3], pt.x, pt.y); + this.sizers[3].setCursor(crs[mxUtils.mod(7 + da, crs.length)]); + + pt.x = r; + pt.y = cy; + pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); + + this.moveSizerTo(this.sizers[4], pt.x, pt.y); + this.sizers[4].setCursor(crs[mxUtils.mod(3 + da, crs.length)]); + + pt.x = s.x; + pt.y = b; + pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); + + this.moveSizerTo(this.sizers[5], pt.x, pt.y); + this.sizers[5].setCursor(crs[mxUtils.mod(6 + da, crs.length)]); + + pt.x = cx; + pt.y = b; + pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); + + this.moveSizerTo(this.sizers[6], pt.x, pt.y); + this.sizers[6].setCursor(crs[mxUtils.mod(5 + da, crs.length)]); + + pt.x = r; + pt.y = b; + pt = mxUtils.getRotatedPoint(pt, cos, sin, ct); + + this.moveSizerTo(this.sizers[7], pt.x, pt.y); + this.sizers[7].setCursor(crs[mxUtils.mod(4 + da, crs.length)]); + + this.moveSizerTo(this.sizers[8], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y); + } + else if (this.state.width >= 2 && this.state.height >= 2) + { + this.moveSizerTo(this.sizers[0], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y); + } + else + { + this.moveSizerTo(this.sizers[0], this.state.x, this.state.y); + } + } + } + + if (this.rotationShape != null) + { + var alpha = mxUtils.toRadians((this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0'); + var cos = Math.cos(alpha); + var sin = Math.sin(alpha); + + var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY()); + var pt = mxUtils.getRotatedPoint(this.getRotationHandlePosition(), cos, sin, ct); + + if (this.rotationShape.node != null) + { + this.moveSizerTo(this.rotationShape, pt.x, pt.y); + + // Hides rotation handle during text editing + this.rotationShape.node.style.visibility = (this.state.view.graph.isEditing()) ? 'hidden' : ''; + } + } + + if (this.selectionBorder != null) + { + this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0'); + } + + if (this.edgeHandlers != null) + { + for (var i = 0; i < this.edgeHandlers.length; i++) + { + this.edgeHandlers[i].redraw(); + } + } + + if (this.customHandles != null) + { + for (var i = 0; i < this.customHandles.length; i++) + { + var temp = this.customHandles[i].shape.node.style.display; + this.customHandles[i].redraw(); + this.customHandles[i].shape.node.style.display = temp; + + // Hides custom handles during text editing + this.customHandles[i].shape.node.style.visibility = (this.graph.isEditing()) ? 'hidden' : ''; + } + } + + this.updateParentHighlight(); +}; + +/** + * Function: getRotationHandlePosition + * + * Returns an that defines the rotation handle position. + */ +mxVertexHandler.prototype.getRotationHandlePosition = function() +{ + return new mxPoint(this.bounds.x + this.bounds.width / 2, this.bounds.y + this.rotationHandleVSpacing) +}; + +/** + * Function: updateParentHighlight + * + * Updates the highlight of the parent if is true. + */ +mxVertexHandler.prototype.updateParentHighlight = function() +{ + // If not destroyed + if (this.selectionBorder != null) + { + if (this.parentHighlight != null) + { + var parent = this.graph.model.getParent(this.state.cell); + + if (this.graph.model.isVertex(parent)) + { + var pstate = this.graph.view.getState(parent); + var b = this.parentHighlight.bounds; + + if (pstate != null && (b.x != pstate.x || b.y != pstate.y || + b.width != pstate.width || b.height != pstate.height)) + { + this.parentHighlight.bounds = pstate; + this.parentHighlight.redraw(); + } + } + else + { + this.parentHighlight.destroy(); + this.parentHighlight = null; + } + } + else if (this.parentHighlightEnabled) + { + var parent = this.graph.model.getParent(this.state.cell); + + if (this.graph.model.isVertex(parent)) + { + var pstate = this.graph.view.getState(parent); + + if (pstate != null) + { + this.parentHighlight = this.createParentHighlightShape(pstate); + // VML dialect required here for event transparency in IE + this.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; + this.parentHighlight.pointerEvents = false; + this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0'); + this.parentHighlight.init(this.graph.getView().getOverlayPane()); + } + } + } + } +}; + +/** + * Function: drawPreview + * + * Redraws the preview. + */ +mxVertexHandler.prototype.drawPreview = function() +{ + if (this.preview != null) + { + this.preview.bounds = this.bounds; + + if (this.preview.node.parentNode == this.graph.container) + { + this.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1); + this.preview.bounds.height = Math.max(0, this.preview.bounds.height - 1); + } + + this.preview.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0'); + this.preview.redraw(); + } + + this.selectionBorder.bounds = this.bounds; + this.selectionBorder.redraw(); + + if (this.parentHighlight != null) + { + this.parentHighlight.redraw(); + } +}; + +/** + * Function: destroy + * + * Destroys the handler and all its resources and DOM nodes. + */ +mxVertexHandler.prototype.destroy = function() +{ + if (this.escapeHandler != null) + { + this.state.view.graph.removeListener(this.escapeHandler); + this.escapeHandler = null; + } + + if (this.preview != null) + { + this.preview.destroy(); + this.preview = null; + } + + if (this.parentHighlight != null) + { + this.parentHighlight.destroy(); + this.parentHighlight = null; + } + + if (this.selectionBorder != null) + { + this.selectionBorder.destroy(); + this.selectionBorder = null; + } + + this.labelShape = null; + this.removeHint(); + + if (this.sizers != null) + { + for (var i = 0; i < this.sizers.length; i++) + { + this.sizers[i].destroy(); + } + + this.sizers = null; + } + + if (this.customHandles != null) + { + for (var i = 0; i < this.customHandles.length; i++) + { + this.customHandles[i].destroy(); + } + + this.customHandles = null; + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/index.txt b/oaweb/public/cherry/drawio/src/js/index.txt new file mode 100644 index 0000000..f3631d6 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/index.txt @@ -0,0 +1,316 @@ +Document: API Specification + +Overview: + + This JavaScript library is divided into 8 packages. The top-level + class includes (or dynamically imports) everything else. The current version + is stored in . + + The *editor* package provides the classes required to implement a diagram + editor. The main class in this package is . + + The *view* and *model* packages implement the graph component, represented + by . It refers to a which contains s and + caches the state of the cells in a . The cells are painted + using a based on the appearance defined in . + Undo history is implemented in . To display an icon on the + graph, may be used. Validation rules are defined with + . + + The *handler*, *layout* and *shape* packages contain event listeners, + layout algorithms and shapes, respectively. The graph event listeners + include for rubberband selection, + for tooltips and for basic cell modifications. + implements a tree layout algorithm, and the + shape package provides various shapes, which are subclasses of + . + + The *util* package provides utility classes including for + copy-paste, for drag-and-drop, for keys and + values of stylesheets, and for cross-browser + event-handling and general purpose functions, for + internationalization and for console output. + + The *io* package implements a generic for turning + JavaScript objects into XML. The main class is . + is the global registry for custom codecs. + +Events: + + There are three different types of events, namely native DOM events, + which are fired in an , and + which are fired in . + + Some helper methods for handling native events are provided in . It + also takes care of resolving cycles between DOM nodes and JavaScript event + handlers, which can lead to memory leaks in IE6. + + Most custom events in mxGraph are implemented using . Its + listeners are functions that take a sender and . Additionally, + the class fires special which are handled using + mouse listeners, which are objects that provide a mousedown, mousemove and + mouseup method. + + Events in are fired using . + Listeners are added and removed using and + . in are fired using + . Listeners are added and removed using + and , respectively. + +Key bindings: + + The following key bindings are defined for mouse events in the client across + all browsers and platforms: + + - Control-Drag: Duplicates (clones) selected cells + - Shift-Rightlick: Shows the context menu + - Alt-Click: Forces rubberband (aka. marquee) + - Control-Select: Toggles the selection state + - Shift-Drag: Constrains the offset to one direction + - Shift-Control-Drag: Panning (also Shift-Rightdrag) + +Configuration: + + The following global variables may be defined before the client is loaded to + specify its language or base path, respectively. + + - mxBasePath: Specifies the path in . + - mxImageBasePath: Specifies the path in . + - mxLanguage: Specifies the language for resources in . + - mxDefaultLanguage: Specifies the default language in . + - mxLoadResources: Specifies if any resources should be loaded. Default is true. + - mxLoadStylesheets: Specifies if any stylesheets should be loaded. Default is true. + +Reserved Words: + + The mx prefix is used for all classes and objects in mxGraph. The mx prefix + can be seen as the global namespace for all JavaScript code in mxGraph. The + following fieldnames should not be used in objects. + + - *mxObjectId*: If the object is used with mxObjectIdentity + - *as*: If the object is a field of another object + - *id*: If the object is an idref in a codec + - *mxListenerList*: Added to DOM nodes when used with + - *window._mxDynamicCode*: Temporarily used to load code in Safari and Chrome + (see ). + - *_mxJavaScriptExpression*: Global variable that is temporarily used to + evaluate code in Safari, Opera, Firefox 3 and IE (see ). + +Files: + + The library contains these relative filenames. All filenames are relative + to . + +Built-in Images: + + All images are loaded from the , + which you can change to reflect your environment. The image variables can + also be changed individually. + + - mxGraph.prototype.collapsedImage + - mxGraph.prototype.expandedImage + - mxGraph.prototype.warningImage + - mxWindow.prototype.closeImage + - mxWindow.prototype.minimizeImage + - mxWindow.prototype.normalizeImage + - mxWindow.prototype.maximizeImage + - mxWindow.prototype.resizeImage + - mxPopupMenu.prototype.submenuImage + - mxUtils.errorImage + - mxConstraintHandler.prototype.pointImage + + The basename of the warning image (images/warning without extension) used in + is defined in . + +Resources: + + The and classes add the following resources to + at class loading time: + + - resources/editor*.properties + - resources/graph*.properties + + By default, the library ships with English and German resource files. + +Images: + + Recommendations for using images. Use GIF images (256 color palette) in HTML + elements (such as the toolbar and context menu), and PNG images (24 bit) for + all images which appear inside the graph component. + + - For PNG images inside HTML elements, Internet Explorer will ignore any + transparency information. + - For GIF images inside the graph, Firefox on the Mac will display strange + colors. Furthermore, only the first image for animated GIFs is displayed + on the Mac. + + For faster image rendering during application runtime, images can be + prefetched using the following code: + + (code) + var image = new Image(); + image.src = url_to_image; + (end) + +Deployment: + + The client is added to the page using the following script tag inside the + head of a document: + + (code) + + (end) + + The deployment version of the mxClient.js file contains all required code + in a single file. For deployment, the complete javascript/src directory is + required. + +Source Code: + + If you are a source code customer and you wish to develop using the + full source code, the commented source code is shipped in the + javascript/devel/source.zip file. It contains one file for each class + in mxGraph. To use the source code the source.zip file must be + uncompressed and the mxClient.js URL in the HTML page must be changed + to reference the uncompressed mxClient.js from the source.zip file. + +Compression: + + When using Apache2 with mod_deflate, you can use the following directive + in src/js/.htaccess to speedup the loading of the JavaScript sources: + + (code) + SetOutputFilter DEFLATE + (end) + +Classes: + + There are two types of "classes" in mxGraph: classes and singletons (where + only one instance exists). Singletons are mapped to global objects where the + variable name equals the classname. For example mxConstants is an object with + all the constants defined as object fields. Normal classes are mapped to a + constructor function and a prototype which defines the instance fields and + methods. For example, is a function and mxEditor.prototype is the + prototype for the object that the mxEditor function creates. The mx prefix is + a convention that is used for all classes in the mxGraph package to avoid + conflicts with other objects in the global namespace. + +Subclassing: + + For subclassing, the superclass must provide a constructor that is either + parameterless or handles an invocation with no arguments. Furthermore, the + special constructor field must be redefined after extending the prototype. + For example, the superclass of mxEditor is . This is + represented in JavaScript by first "inheriting" all fields and methods from + the superclass by assigning the prototype to an instance of the superclass, + eg. mxEditor.prototype = new mxEventSource() and redefining the constructor + field using mxEditor.prototype.constructor = mxEditor. The latter rule is + applied so that the type of an object can be retrieved via the name of its + constructor using mxUtils.getFunctionName(obj.constructor). + +Constructor: + + For subclassing in mxGraph, the same scheme should be applied. For example, + for subclassing the class, first a constructor must be defined for + the new class. The constructor calls the super constructor with any arguments + that it may have using the call function on the mxGraph function object, + passing along explitely each argument: + + (code) + function MyGraph(container) + { + mxGraph.call(this, container); + } + (end) + + The prototype of MyGraph inherits from mxGraph as follows. As usual, the + constructor is redefined after extending the superclass: + + (code) + MyGraph.prototype = new mxGraph(); + MyGraph.prototype.constructor = MyGraph; + (end) + + You may want to define the codec associated for the class after the above + code. This code will be executed at class loading time and makes sure the + same codec is used to encode instances of mxGraph and MyGraph. + + (code) + var codec = mxCodecRegistry.getCodec(mxGraph); + codec.template = new MyGraph(); + mxCodecRegistry.register(codec); + (end) + +Functions: + + In the prototype for MyGraph, functions of mxGraph can then be extended as + follows. + + (code) + MyGraph.prototype.isCellSelectable = function(cell) + { + var selectable = mxGraph.prototype.isSelectable.apply(this, arguments); + + var geo = this.model.getGeometry(cell); + return selectable && (geo == null || !geo.relative); + } + (end) + + The supercall in the first line is optional. It is done using the apply + function on the isSelectable function object of the mxGraph prototype, using + the special this and arguments variables as parameters. Calls to the + superclass function are only possible if the function is not replaced in the + superclass as follows, which is another way of subclassing in JavaScript. + + (code) + mxGraph.prototype.isCellSelectable = function(cell) + { + var geo = this.model.getGeometry(cell); + return selectable && + (geo == null || + !geo.relative); + } + (end) + + The above scheme is useful if a function definition needs to be replaced + completely. + + In order to add new functions and fields to the subclass, the following code + is used. The example below adds a new function to return the XML + representation of the graph model: + + (code) + MyGraph.prototype.getXml = function() + { + var enc = new mxCodec(); + return enc.encode(this.getModel()); + } + (end) + +Variables: + + Likewise, a new field is declared and defined as follows. + + (code) + MyGraph.prototype.myField = 'Hello, World!'; + (end) + + Note that the value assigned to myField is created only once, that is, all + instances of MyGraph share the same value. If you require instance-specific + values, then the field must be defined in the constructor instead. + + (code) + function MyGraph(container) + { + mxGraph.call(this, container); + + this.myField = new Array(); + } + (end) + + Finally, a new instance of MyGraph is created using the following code, where + container is a DOM node that acts as a container for the graph view: + + (code) + var graph = new MyGraph(container); + (end) diff --git a/oaweb/public/cherry/drawio/src/js/io/mxCellCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxCellCodec.js new file mode 100644 index 0000000..253c96f --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxCellCodec.js @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +mxCodecRegistry.register(function() +{ + /** + * Class: mxCellCodec + * + * Codec for s. This class is created and registered + * dynamically at load time and used implicitely via + * and the . + * + * Transient Fields: + * + * - children + * - edges + * - overlays + * - mxTransient + * + * Reference Fields: + * + * - parent + * - source + * - target + * + * Transient fields can be added using the following code: + * + * mxCodecRegistry.getCodec(mxCell).exclude.push('name_of_field'); + * + * To subclass , replace the template and add an alias as + * follows. + * + * (code) + * function CustomCell(value, geometry, style) + * { + * mxCell.apply(this, arguments); + * } + * + * mxUtils.extend(CustomCell, mxCell); + * + * mxCodecRegistry.getCodec(mxCell).template = new CustomCell(); + * mxCodecRegistry.addAlias('CustomCell', 'mxCell'); + * (end) + */ + var codec = new mxObjectCodec(new mxCell(), + ['children', 'edges', 'overlays', 'mxTransient'], + ['parent', 'source', 'target']); + + /** + * Function: isCellCodec + * + * Returns true since this is a cell codec. + */ + codec.isCellCodec = function() + { + return true; + }; + + /** + * Overidden to disable conversion of value to number. + */ + codec.isNumericAttribute = function(dec, attr, obj) + { + return attr.nodeName !== 'value' && mxObjectCodec.prototype.isNumericAttribute.apply(this, arguments); + }; + + /** + * Function: isExcluded + * + * Excludes user objects that are XML nodes. + */ + codec.isExcluded = function(obj, attr, value, isWrite) + { + return mxObjectCodec.prototype.isExcluded.apply(this, arguments) || + (isWrite && attr == 'value' && + value.nodeType == mxConstants.NODETYPE_ELEMENT); + }; + + /** + * Function: afterEncode + * + * Encodes an and wraps the XML up inside the + * XML of the user object (inversion). + */ + codec.afterEncode = function(enc, obj, node) + { + if (obj.value != null && obj.value.nodeType == mxConstants.NODETYPE_ELEMENT) + { + // Wraps the graphical annotation up in the user object (inversion) + // by putting the result of the default encoding into a clone of the + // user object (node type 1) and returning this cloned user object. + var tmp = node; + node = mxUtils.importNode(enc.document, obj.value, true); + node.appendChild(tmp); + + // Moves the id attribute to the outermost XML node, namely the + // node which denotes the object boundaries in the file. + var id = tmp.getAttribute('id'); + node.setAttribute('id', id); + tmp.removeAttribute('id'); + } + + return node; + }; + + /** + * Function: beforeDecode + * + * Decodes an and uses the enclosing XML node as + * the user object for the cell (inversion). + */ + codec.beforeDecode = function(dec, node, obj) + { + var inner = node.cloneNode(true); + var classname = this.getName(); + + if (node.nodeName != classname) + { + // Passes the inner graphical annotation node to the + // object codec for further processing of the cell. + var tmp = node.getElementsByTagName(classname)[0]; + + if (tmp != null && tmp.parentNode == node) + { + mxUtils.removeWhitespace(tmp, true); + mxUtils.removeWhitespace(tmp, false); + tmp.parentNode.removeChild(tmp); + inner = tmp; + } + else + { + inner = null; + } + + // Creates the user object out of the XML node + obj.value = node.cloneNode(true); + var id = obj.value.getAttribute('id'); + + if (id != null) + { + obj.setId(id); + obj.value.removeAttribute('id'); + } + } + else + { + // Uses ID from XML file as ID for cell in model + obj.setId(node.getAttribute('id')); + } + + // Preprocesses and removes all Id-references in order to use the + // correct encoder (this) for the known references to cells (all). + if (inner != null) + { + for (var i = 0; i < this.idrefs.length; i++) + { + var attr = this.idrefs[i]; + var ref = inner.getAttribute(attr); + + if (ref != null) + { + inner.removeAttribute(attr); + var object = dec.objects[ref] || dec.lookup(ref); + + if (object == null) + { + // Needs to decode forward reference + var element = dec.getElementById(ref); + + if (element != null) + { + var decoder = mxCodecRegistry.codecs[element.nodeName] || this; + object = decoder.decode(dec, element); + } + } + + obj[attr] = object; + } + } + } + + return inner; + }; + + // Returns the codec into the registry + return codec; + +}()); diff --git a/oaweb/public/cherry/drawio/src/js/io/mxChildChangeCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxChildChangeCodec.js new file mode 100644 index 0000000..49f2d4d --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxChildChangeCodec.js @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +mxCodecRegistry.register(function() +{ + /** + * Class: mxChildChangeCodec + * + * Codec for s. This class is created and registered + * dynamically at load time and used implicitely via and + * the . + * + * Transient Fields: + * + * - model + * - previous + * - previousIndex + * - child + * + * Reference Fields: + * + * - parent + */ + var codec = new mxObjectCodec(new mxChildChange(), + ['model', 'child', 'previousIndex'], + ['parent', 'previous']); + + /** + * Function: isReference + * + * Returns true for the child attribute if the child + * cell had a previous parent or if we're reading the + * child as an attribute rather than a child node, in + * which case it's always a reference. + */ + codec.isReference = function(obj, attr, value, isWrite) + { + if (attr == 'child' && (obj.previous != null || !isWrite)) + { + return true; + } + + return mxUtils.indexOf(this.idrefs, attr) >= 0; + }; + + /** + * Function: afterEncode + * + * Encodes the child recusively and adds the result + * to the given node. + */ + codec.afterEncode = function(enc, obj, node) + { + if (this.isReference(obj, 'child', obj.child, true)) + { + // Encodes as reference (id) + node.setAttribute('child', enc.getId(obj.child)); + } + else + { + // At this point, the encoder is no longer able to know which cells + // are new, so we have to encode the complete cell hierarchy and + // ignore the ones that are already there at decoding time. Note: + // This can only be resolved by moving the notify event into the + // execute of the edit. + enc.encodeCell(obj.child, node); + } + + return node; + }; + + /** + * Function: beforeDecode + * + * Decodes the any child nodes as using the respective + * codec from the registry. + */ + codec.beforeDecode = function(dec, node, obj) + { + if (node.firstChild != null && + node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT) + { + // Makes sure the original node isn't modified + node = node.cloneNode(true); + + var tmp = node.firstChild; + obj.child = dec.decodeCell(tmp, false); + + var tmp2 = tmp.nextSibling; + tmp.parentNode.removeChild(tmp); + tmp = tmp2; + + while (tmp != null) + { + tmp2 = tmp.nextSibling; + + if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT) + { + // Ignores all existing cells because those do not need to + // be re-inserted into the model. Since the encoded version + // of these cells contains the new parent, this would leave + // to an inconsistent state on the model (ie. a parent + // change without a call to parentForCellChanged). + var id = tmp.getAttribute('id'); + + if (dec.lookup(id) == null) + { + dec.decodeCell(tmp); + } + } + + tmp.parentNode.removeChild(tmp); + tmp = tmp2; + } + } + else + { + var childRef = node.getAttribute('child'); + obj.child = dec.getObject(childRef); + } + + return node; + }; + + /** + * Function: afterDecode + * + * Restores object state in the child change. + */ + codec.afterDecode = function(dec, node, obj) + { + // Cells are encoded here after a complete transaction so the previous + // parent must be restored on the cell for the case where the cell was + // added. This is needed for the local model to identify the cell as a + // new cell and register the ID. + if (obj.child != null) + { + if (obj.child.parent != null && obj.previous != null && + obj.child.parent != obj.previous) + { + + obj.previous = obj.child.parent; + } + + obj.child.parent = obj.previous; + obj.previous = obj.parent; + obj.previousIndex = obj.index; + } + + return obj; + }; + + // Returns the codec into the registry + return codec; + +}()); diff --git a/oaweb/public/cherry/drawio/src/js/io/mxCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxCodec.js new file mode 100644 index 0000000..bd25d1c --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxCodec.js @@ -0,0 +1,592 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxCodec + * + * XML codec for JavaScript object graphs. See for a + * description of the general encoding/decoding scheme. This class uses the + * codecs registered in for encoding/decoding each object. + * + * References: + * + * In order to resolve references, especially forward references, the mxCodec + * constructor must be given the document that contains the referenced + * elements. + * + * Examples: + * + * The following code is used to encode a graph model. + * + * (code) + * var encoder = new mxCodec(); + * var result = encoder.encode(graph.getModel()); + * var xml = mxUtils.getXml(result); + * (end) + * + * Example: + * + * Using the code below, an XML document is decoded into an existing model. The + * document may be obtained using one of the functions in mxUtils for loading + * an XML file, eg. , or using for parsing an + * XML string. + * + * (code) + * var doc = mxUtils.parseXml(xmlString); + * var codec = new mxCodec(doc); + * codec.decode(doc.documentElement, graph.getModel()); + * (end) + * + * Example: + * + * This example demonstrates parsing a list of isolated cells into an existing + * graph model. Note that the cells do not have a parent reference so they can + * be added anywhere in the cell hierarchy after parsing. + * + * (code) + * var xml = ''; + * var doc = mxUtils.parseXml(xml); + * var codec = new mxCodec(doc); + * var elt = doc.documentElement.firstChild; + * var cells = []; + * + * while (elt != null) + * { + * cells.push(codec.decode(elt)); + * elt = elt.nextSibling; + * } + * + * graph.addCells(cells); + * (end) + * + * Example: + * + * Using the following code, the selection cells of a graph are encoded and the + * output is displayed in a dialog box. + * + * (code) + * var enc = new mxCodec(); + * var cells = graph.getSelectionCells(); + * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells))); + * (end) + * + * Newlines in the XML can be converted to
, in which case a '
' argument + * must be passed to as the second argument. + * + * Debugging: + * + * For debugging I/O you can use the following code to get the sequence of + * encoded objects: + * + * (code) + * var oldEncode = mxCodec.prototype.encode; + * mxCodec.prototype.encode = function(obj) + * { + * mxLog.show(); + * mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor)); + * + * return oldEncode.apply(this, arguments); + * }; + * (end) + * + * Note that the I/O system adds object codecs for new object automatically. For + * decoding those objects, the constructor should be written as follows: + * + * (code) + * var MyObj = function(name) + * { + * // ... + * }; + * (end) + * + * Constructor: mxCodec + * + * Constructs an XML encoder/decoder for the specified + * owner document. + * + * Parameters: + * + * document - Optional XML document that contains the data. + * If no document is specified then a new document is created + * using . + */ +function mxCodec(document) +{ + this.document = document || mxUtils.createXmlDocument(); + this.objects = []; +}; + +/** + * Variable: document + * + * The owner document of the codec. + */ +mxCodec.prototype.document = null; + +/** + * Variable: objects + * + * Maps from IDs to objects. + */ +mxCodec.prototype.objects = null; + +/** + * Variable: elements + * + * Lookup table for resolving IDs to elements. + */ +mxCodec.prototype.elements = null; + +/** + * Variable: encodeDefaults + * + * Specifies if default values should be encoded. Default is false. + */ +mxCodec.prototype.encodeDefaults = false; + + +/** + * Function: putObject + * + * Assoiates the given object with the given ID and returns the given object. + * + * Parameters + * + * id - ID for the object to be associated with. + * obj - Object to be associated with the ID. + */ +mxCodec.prototype.putObject = function(id, obj) +{ + this.objects[id] = obj; + + return obj; +}; + +/** + * Function: getObject + * + * Returns the decoded object for the element with the specified ID in + * . If the object is not known then is used to find an + * object. If no object is found, then the element with the respective ID + * from the document is parsed using . + */ +mxCodec.prototype.getObject = function(id) +{ + var obj = null; + + if (id != null) + { + obj = this.objects[id]; + + if (obj == null) + { + obj = this.lookup(id); + + if (obj == null) + { + var node = this.getElementById(id); + + if (node != null) + { + obj = this.decode(node); + } + } + } + } + + return obj; +}; + +/** + * Function: lookup + * + * Hook for subclassers to implement a custom lookup mechanism for cell IDs. + * This implementation always returns null. + * + * Example: + * + * (code) + * var codec = new mxCodec(); + * codec.lookup = function(id) + * { + * return model.getCell(id); + * }; + * (end) + * + * Parameters: + * + * id - ID of the object to be returned. + */ +mxCodec.prototype.lookup = function(id) +{ + return null; +}; + +/** + * Function: getElementById + * + * Returns the element with the given ID from . + * + * Parameters: + * + * id - String that contains the ID. + */ +mxCodec.prototype.getElementById = function(id) +{ + if (this.elements == null) + { + this.elements = new Object(); + + if (this.document.documentElement != null) + { + this.addElement(this.document.documentElement); + } + } + + return this.elements[id]; +}; + +/** + * Function: addElement + * + * Adds the given element to if it has an ID. + */ +mxCodec.prototype.addElement = function(node) +{ + if (node.nodeType == mxConstants.NODETYPE_ELEMENT) + { + var id = node.getAttribute('id'); + + if (id != null && this.elements[id] == null) + { + this.elements[id] = node; + } + } + + node = node.firstChild; + + while (node != null) + { + this.addElement(node); + node = node.nextSibling; + } +}; + +/** + * Function: getId + * + * Returns the ID of the specified object. This implementation + * calls first and if that returns null handles + * the object as an by returning their IDs using + * . If no ID exists for the given cell, then + * an on-the-fly ID is generated using . + * + * Parameters: + * + * obj - Object to return the ID for. + */ +mxCodec.prototype.getId = function(obj) +{ + var id = null; + + if (obj != null) + { + id = this.reference(obj); + + if (id == null && obj instanceof mxCell) + { + id = obj.getId(); + + if (id == null) + { + // Uses an on-the-fly Id + id = mxCellPath.create(obj); + + if (id.length == 0) + { + id = 'root'; + } + } + } + } + + return id; +}; + +/** + * Function: reference + * + * Hook for subclassers to implement a custom method + * for retrieving IDs from objects. This implementation + * always returns null. + * + * Example: + * + * (code) + * var codec = new mxCodec(); + * codec.reference = function(obj) + * { + * return obj.getCustomId(); + * }; + * (end) + * + * Parameters: + * + * obj - Object whose ID should be returned. + */ +mxCodec.prototype.reference = function(obj) +{ + return null; +}; + +/** + * Function: encode + * + * Encodes the specified object and returns the resulting + * XML node. + * + * Parameters: + * + * obj - Object to be encoded. + */ +mxCodec.prototype.encode = function(obj) +{ + var node = null; + + if (obj != null && obj.constructor != null) + { + var enc = mxCodecRegistry.getCodec(obj.constructor); + + if (enc != null) + { + node = enc.encode(this, obj); + } + else + { + if (mxUtils.isNode(obj)) + { + node = mxUtils.importNode(this.document, obj, true); + } + else + { + mxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor)); + } + } + } + + return node; +}; + +/** + * Function: decode + * + * Decodes the given XML node. The optional "into" + * argument specifies an existing object to be + * used. If no object is given, then a new instance + * is created using the constructor from the codec. + * + * The function returns the passed in object or + * the new instance if no object was given. + * + * Parameters: + * + * node - XML node to be decoded. + * into - Optional object to be decodec into. + */ +mxCodec.prototype.decode = function(node, into) +{ + var obj = null; + + if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT) + { + var ctor = null; + + try + { + ctor = window[node.nodeName]; + } + catch (err) + { + // ignore + } + + var dec = mxCodecRegistry.getCodec(ctor); + + if (dec != null) + { + obj = dec.decode(this, node, into); + } + else + { + obj = node.cloneNode(true); + obj.removeAttribute('as'); + } + } + + return obj; +}; + +/** + * Function: encodeCell + * + * Encoding of cell hierarchies is built-into the core, but + * is a higher-level function that needs to be explicitely + * used by the respective object encoders (eg. , + * and ). This + * implementation writes the given cell and its children as a + * (flat) sequence into the given node. The children are not + * encoded if the optional includeChildren is false. The + * function is in charge of adding the result into the + * given node and has no return value. + * + * Parameters: + * + * cell - to be encoded. + * node - Parent XML node to add the encoded cell into. + * includeChildren - Optional boolean indicating if the + * function should include all descendents. Default is true. + */ +mxCodec.prototype.encodeCell = function(cell, node, includeChildren) +{ + node.appendChild(this.encode(cell)); + + if (includeChildren == null || includeChildren) + { + var childCount = cell.getChildCount(); + + for (var i = 0; i < childCount; i++) + { + this.encodeCell(cell.getChildAt(i), node); + } + } +}; + +/** + * Function: isCellCodec + * + * Returns true if the given codec is a cell codec. This uses + * to check if the codec is of the + * given type. + */ +mxCodec.prototype.isCellCodec = function(codec) +{ + if (codec != null && typeof(codec.isCellCodec) == 'function') + { + return codec.isCellCodec(); + } + + return false; +}; + +/** + * Function: decodeCell + * + * Decodes cells that have been encoded using inversion, ie. + * where the user object is the enclosing node in the XML, + * and restores the group and graph structure in the cells. + * Returns a new instance that represents the + * given node. + * + * Parameters: + * + * node - XML node that contains the cell data. + * restoreStructures - Optional boolean indicating whether + * the graph structure should be restored by calling insert + * and insertEdge on the parent and terminals, respectively. + * Default is true. + */ +mxCodec.prototype.decodeCell = function(node, restoreStructures) +{ + restoreStructures = (restoreStructures != null) ? restoreStructures : true; + var cell = null; + + if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT) + { + // Tries to find a codec for the given node name. If that does + // not return a codec then the node is the user object (an XML node + // that contains the mxCell, aka inversion). + var decoder = mxCodecRegistry.getCodec(node.nodeName); + + // Tries to find the codec for the cell inside the user object. + // This assumes all node names inside the user object are either + // not registered or they correspond to a class for cells. + if (!this.isCellCodec(decoder)) + { + var child = node.firstChild; + + while (child != null && !this.isCellCodec(decoder)) + { + decoder = mxCodecRegistry.getCodec(child.nodeName); + child = child.nextSibling; + } + } + + if (!this.isCellCodec(decoder)) + { + decoder = mxCodecRegistry.getCodec(mxCell); + } + + cell = decoder.decode(this, node); + + if (restoreStructures) + { + this.insertIntoGraph(cell); + } + } + + return cell; +}; + +/** + * Function: insertIntoGraph + * + * Inserts the given cell into its parent and terminal cells. + */ +mxCodec.prototype.insertIntoGraph = function(cell) +{ + var parent = cell.parent; + var source = cell.getTerminal(true); + var target = cell.getTerminal(false); + + // Fixes possible inconsistencies during insert into graph + cell.setTerminal(null, false); + cell.setTerminal(null, true); + cell.parent = null; + + if (parent != null) + { + parent.insert(cell); + } + + if (source != null) + { + source.insertEdge(cell, true); + } + + if (target != null) + { + target.insertEdge(cell, false); + } +}; + +/** + * Function: setAttribute + * + * Sets the attribute on the specified node to value. This is a + * helper method that makes sure the attribute and value arguments + * are not null. + * + * Parameters: + * + * node - XML node to set the attribute for. + * attributes - Attributename to be set. + * value - New value of the attribute. + */ +mxCodec.prototype.setAttribute = function(node, attribute, value) +{ + if (attribute != null && value != null) + { + node.setAttribute(attribute, value); + } +}; diff --git a/oaweb/public/cherry/drawio/src/js/io/mxCodecRegistry.js b/oaweb/public/cherry/drawio/src/js/io/mxCodecRegistry.js new file mode 100644 index 0000000..42ebcd7 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxCodecRegistry.js @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +var mxCodecRegistry = +{ + /** + * Class: mxCodecRegistry + * + * Singleton class that acts as a global registry for codecs. + * + * Adding an : + * + * 1. Define a default codec with a new instance of the + * object to be handled. + * + * (code) + * var codec = new mxObjectCodec(new mxGraphModel()); + * (end) + * + * 2. Define the functions required for encoding and decoding + * objects. + * + * (code) + * codec.encode = function(enc, obj) { ... } + * codec.decode = function(dec, node, into) { ... } + * (end) + * + * 3. Register the codec in the . + * + * (code) + * mxCodecRegistry.register(codec); + * (end) + * + * may be used to either create a new + * instance of an object or to configure an existing instance, + * in which case the into argument points to the existing + * object. In this case, we say the codec "configures" the + * object. + * + * Variable: codecs + * + * Maps from constructor names to codecs. + */ + codecs: [], + + /** + * Variable: aliases + * + * Maps from classnames to codecnames. + */ + aliases: [], + + /** + * Function: register + * + * Registers a new codec and associates the name of the template + * constructor in the codec with the codec object. + * + * Parameters: + * + * codec - to be registered. + */ + register: function(codec) + { + if (codec != null) + { + var name = codec.getName(); + mxCodecRegistry.codecs[name] = codec; + + var classname = mxUtils.getFunctionName(codec.template.constructor); + + if (classname != name) + { + mxCodecRegistry.addAlias(classname, name); + } + } + + return codec; + }, + + /** + * Function: addAlias + * + * Adds an alias for mapping a classname to a codecname. + */ + addAlias: function(classname, codecname) + { + mxCodecRegistry.aliases[classname] = codecname; + }, + + /** + * Function: getCodec + * + * Returns a codec that handles objects that are constructed + * using the given constructor. + * + * Parameters: + * + * ctor - JavaScript constructor function. + */ + getCodec: function(ctor) + { + var codec = null; + + if (ctor != null) + { + var name = mxUtils.getFunctionName(ctor); + var tmp = mxCodecRegistry.aliases[name]; + + if (tmp != null) + { + name = tmp; + } + + codec = mxCodecRegistry.codecs[name]; + + // Registers a new default codec for the given constructor + // if no codec has been previously defined. + if (codec == null) + { + try + { + codec = new mxObjectCodec(new ctor()); + mxCodecRegistry.register(codec); + } + catch (e) + { + // ignore + } + } + } + + return codec; + } + +}; diff --git a/oaweb/public/cherry/drawio/src/js/io/mxDefaultKeyHandlerCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxDefaultKeyHandlerCodec.js new file mode 100644 index 0000000..9a18579 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxDefaultKeyHandlerCodec.js @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +mxCodecRegistry.register(function() +{ + /** + * Class: mxDefaultKeyHandlerCodec + * + * Custom codec for configuring s. This class is created + * and registered dynamically at load time and used implicitely via + * and the . This codec only reads configuration + * data for existing key handlers, it does not encode or create key handlers. + */ + var codec = new mxObjectCodec(new mxDefaultKeyHandler()); + + /** + * Function: encode + * + * Returns null. + */ + codec.encode = function(enc, obj) + { + return null; + }; + + /** + * Function: decode + * + * Reads a sequence of the following child nodes + * and attributes: + * + * Child Nodes: + * + * add - Binds a keystroke to an actionname. + * + * Attributes: + * + * as - Keycode. + * action - Actionname to execute in editor. + * control - Optional boolean indicating if + * the control key must be pressed. + * + * Example: + * + * (code) + * + * + * + * + * + * (end) + * + * The keycodes are for the x, c and v keys. + * + * See also: , + * http://www.js-examples.com/page/tutorials__key_codes.html + */ + codec.decode = function(dec, node, into) + { + if (into != null) + { + var editor = into.editor; + node = node.firstChild; + + while (node != null) + { + if (!this.processInclude(dec, node, into) && + node.nodeName == 'add') + { + var as = node.getAttribute('as'); + var action = node.getAttribute('action'); + var control = node.getAttribute('control'); + + into.bindAction(as, action, control); + } + + node = node.nextSibling; + } + } + + return into; + }; + + // Returns the codec into the registry + return codec; + +}()); diff --git a/oaweb/public/cherry/drawio/src/js/io/mxDefaultPopupMenuCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxDefaultPopupMenuCodec.js new file mode 100644 index 0000000..7a62ac2 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxDefaultPopupMenuCodec.js @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +mxCodecRegistry.register(function() +{ + /** + * Class: mxDefaultPopupMenuCodec + * + * Custom codec for configuring s. This class is created + * and registered dynamically at load time and used implicitely via + * and the . This codec only reads configuration + * data for existing popup menus, it does not encode or create menus. Note + * that this codec only passes the configuration node to the popup menu, + * which uses the config to dynamically create menus. See + * . + */ + var codec = new mxObjectCodec(new mxDefaultPopupMenu()); + + /** + * Function: encode + * + * Returns null. + */ + codec.encode = function(enc, obj) + { + return null; + }; + + /** + * Function: decode + * + * Uses the given node as the config for . + */ + codec.decode = function(dec, node, into) + { + var inc = node.getElementsByTagName('include')[0]; + + if (inc != null) + { + this.processInclude(dec, inc, into); + } + else if (into != null) + { + into.config = node; + } + + return into; + }; + + // Returns the codec into the registry + return codec; + +}()); diff --git a/oaweb/public/cherry/drawio/src/js/io/mxDefaultToolbarCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxDefaultToolbarCodec.js new file mode 100644 index 0000000..ce4d173 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxDefaultToolbarCodec.js @@ -0,0 +1,312 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxDefaultToolbarCodec + * + * Custom codec for configuring s. This class is created + * and registered dynamically at load time and used implicitely via + * and the . This codec only reads configuration + * data for existing toolbars handlers, it does not encode or create toolbars. + */ +var mxDefaultToolbarCodec = mxCodecRegistry.register(function() +{ + var codec = new mxObjectCodec(new mxDefaultToolbar()); + + /** + * Function: encode + * + * Returns null. + */ + codec.encode = function(enc, obj) + { + return null; + }; + + /** + * Function: decode + * + * Reads a sequence of the following child nodes + * and attributes: + * + * Child Nodes: + * + * add - Adds a new item to the toolbar. See below for attributes. + * separator - Adds a vertical separator. No attributes. + * hr - Adds a horizontal separator. No attributes. + * br - Adds a linefeed. No attributes. + * + * Attributes: + * + * as - Resource key for the label. + * action - Name of the action to execute in enclosing editor. + * mode - Modename (see below). + * template - Template name for cell insertion. + * style - Optional style to override the template style. + * icon - Icon (relative/absolute URL). + * pressedIcon - Optional icon for pressed state (relative/absolute URL). + * id - Optional ID to be used for the created DOM element. + * toggle - Optional 0 or 1 to disable toggling of the element. Default is + * 1 (true). + * + * The action, mode and template attributes are mutually exclusive. The + * style can only be used with the template attribute. The add node may + * contain another sequence of add nodes with as and action attributes + * to create a combo box in the toolbar. If the icon is specified then + * a list of the child node is expected to have its template attribute + * set and the action is ignored instead. + * + * Nodes with a specified template may define a function to be used for + * inserting the cloned template into the graph. Here is an example of such + * a node: + * + * (code) + * + * (end) + * + * In the above function, editor is the enclosing instance, cell + * is the clone of the template, evt is the mouse event that represents the + * drop and targetCell is the cell under the mousepointer where the drop + * occurred. The targetCell is retrieved using . + * + * Futhermore, nodes with the mode attribute may define a function to + * be executed upon selection of the respective toolbar icon. In the + * example below, the default edge style is set when this specific + * connect-mode is activated: + * + * (code) + * + * (end) + * + * Both functions require to be set to true. + * + * Modes: + * + * select - Left mouse button used for rubberband- & cell-selection. + * connect - Allows connecting vertices by inserting new edges. + * pan - Disables selection and switches to panning on the left button. + * + * Example: + * + * To add items to the toolbar: + * + * (code) + * + * + *

+ * + * + *
+ * (end) + */ + codec.decode = function(dec, node, into) + { + if (into != null) + { + var editor = into.editor; + node = node.firstChild; + + while (node != null) + { + if (node.nodeType == mxConstants.NODETYPE_ELEMENT) + { + if (!this.processInclude(dec, node, into)) + { + if (node.nodeName == 'separator') + { + into.addSeparator(); + } + else if (node.nodeName == 'br') + { + into.toolbar.addBreak(); + } + else if (node.nodeName == 'hr') + { + into.toolbar.addLine(); + } + else if (node.nodeName == 'add') + { + var as = node.getAttribute('as'); + as = mxResources.get(as) || as; + var icon = node.getAttribute('icon'); + var pressedIcon = node.getAttribute('pressedIcon'); + var action = node.getAttribute('action'); + var mode = node.getAttribute('mode'); + var template = node.getAttribute('template'); + var toggle = node.getAttribute('toggle') != '0'; + var text = mxUtils.getTextContent(node); + var elt = null; + + if (action != null) + { + elt = into.addItem(as, icon, action, pressedIcon); + } + else if (mode != null) + { + var funct = (mxDefaultToolbarCodec.allowEval) ? mxUtils.eval(text) : null; + elt = into.addMode(as, icon, mode, pressedIcon, funct); + } + else if (template != null || (text != null && text.length > 0)) + { + var cell = editor.templates[template]; + var style = node.getAttribute('style'); + + if (cell != null && style != null) + { + cell = editor.graph.cloneCell(cell); + cell.setStyle(style); + } + + var insertFunction = null; + + if (text != null && text.length > 0 && mxDefaultToolbarCodec.allowEval) + { + insertFunction = mxUtils.eval(text); + } + + elt = into.addPrototype(as, icon, cell, pressedIcon, insertFunction, toggle); + } + else + { + var children = mxUtils.getChildNodes(node); + + if (children.length > 0) + { + if (icon == null) + { + var combo = into.addActionCombo(as); + + for (var i=0; i 0) + { + elt.setAttribute('id', id); + } + } + } + } + } + + node = node.nextSibling; + } + } + + return into; + }; + + // Returns the codec into the registry + return codec; + +}()); + +/** + * Variable: allowEval + * + * Static global switch that specifies if the use of eval is allowed for + * evaluating text content. Default is true. Set this to false if stylesheets + * may contain user input + */ +mxDefaultToolbarCodec.allowEval = true; diff --git a/oaweb/public/cherry/drawio/src/js/io/mxEditorCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxEditorCodec.js new file mode 100644 index 0000000..47ce585 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxEditorCodec.js @@ -0,0 +1,245 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +mxCodecRegistry.register(function() +{ + /** + * Class: mxEditorCodec + * + * Codec for s. This class is created and registered + * dynamically at load time and used implicitely via + * and the . + * + * Transient Fields: + * + * - modified + * - lastSnapshot + * - ignoredChanges + * - undoManager + * - graphContainer + * - toolbarContainer + */ + var codec = new mxObjectCodec(new mxEditor(), + ['modified', 'lastSnapshot', 'ignoredChanges', + 'undoManager', 'graphContainer', 'toolbarContainer']); + + /** + * Function: beforeDecode + * + * Decodes the ui-part of the configuration node by reading + * a sequence of the following child nodes and attributes + * and passes the control to the default decoding mechanism: + * + * Child Nodes: + * + * stylesheet - Adds a CSS stylesheet to the document. + * resource - Adds the basename of a resource bundle. + * add - Creates or configures a known UI element. + * + * These elements may appear in any order given that the + * graph UI element is added before the toolbar element + * (see Known Keys). + * + * Attributes: + * + * as - Key for the UI element (see below). + * element - ID for the element in the document. + * style - CSS style to be used for the element or window. + * x - X coordinate for the new window. + * y - Y coordinate for the new window. + * width - Width for the new window. + * height - Optional height for the new window. + * name - Name of the stylesheet (absolute/relative URL). + * basename - Basename of the resource bundle (see ). + * + * The x, y, width and height attributes are used to create a new + * if the element attribute is not specified in an add + * node. The name and basename are only used in the stylesheet and + * resource nodes, respectively. + * + * Known Keys: + * + * graph - Main graph element (see ). + * title - Title element (see ). + * toolbar - Toolbar element (see ). + * status - Status bar element (see ). + * + * Example: + * + * (code) + * + * + * + * + * + * + * + * (end) + */ + codec.afterDecode = function(dec, node, obj) + { + // Assigns the specified templates for edges + var defaultEdge = node.getAttribute('defaultEdge'); + + if (defaultEdge != null) + { + node.removeAttribute('defaultEdge'); + obj.defaultEdge = obj.templates[defaultEdge]; + } + + // Assigns the specified templates for groups + var defaultGroup = node.getAttribute('defaultGroup'); + + if (defaultGroup != null) + { + node.removeAttribute('defaultGroup'); + obj.defaultGroup = obj.templates[defaultGroup]; + } + + return obj; + }; + + /** + * Function: decodeChild + * + * Overrides decode child to handle special child nodes. + */ + codec.decodeChild = function(dec, child, obj) + { + if (child.nodeName == 'Array') + { + var role = child.getAttribute('as'); + + if (role == 'templates') + { + this.decodeTemplates(dec, child, obj); + return; + } + } + else if (child.nodeName == 'ui') + { + this.decodeUi(dec, child, obj); + return; + } + + mxObjectCodec.prototype.decodeChild.apply(this, arguments); + }; + + /** + * Function: decodeTemplates + * + * Decodes the cells from the given node as templates. + */ + codec.decodeUi = function(dec, node, editor) + { + var tmp = node.firstChild; + while (tmp != null) + { + if (tmp.nodeName == 'add') + { + var as = tmp.getAttribute('as'); + var elt = tmp.getAttribute('element'); + var style = tmp.getAttribute('style'); + var element = null; + + if (elt != null) + { + element = document.getElementById(elt); + + if (element != null && style != null) + { + element.style.cssText += ';' + style; + } + } + else + { + var x = parseInt(tmp.getAttribute('x')); + var y = parseInt(tmp.getAttribute('y')); + var width = tmp.getAttribute('width'); + var height = tmp.getAttribute('height'); + + // Creates a new window around the element + element = document.createElement('div'); + element.style.cssText = style; + + var wnd = new mxWindow(mxResources.get(as) || as, + element, x, y, width, height, false, true); + wnd.setVisible(true); + } + + // TODO: Make more generic + if (as == 'graph') + { + editor.setGraphContainer(element); + } + else if (as == 'toolbar') + { + editor.setToolbarContainer(element); + } + else if (as == 'title') + { + editor.setTitleContainer(element); + } + else if (as == 'status') + { + editor.setStatusContainer(element); + } + else if (as == 'map') + { + editor.setMapContainer(element); + } + } + else if (tmp.nodeName == 'resource') + { + mxResources.add(tmp.getAttribute('basename')); + } + else if (tmp.nodeName == 'stylesheet') + { + mxClient.link('stylesheet', tmp.getAttribute('name')); + } + + tmp = tmp.nextSibling; + } + }; + + /** + * Function: decodeTemplates + * + * Decodes the cells from the given node as templates. + */ + codec.decodeTemplates = function(dec, node, editor) + { + if (editor.templates == null) + { + editor.templates = []; + } + + var children = mxUtils.getChildNodes(node); + for (var j=0; js, s, s, + * s and s. This class is created + * and registered dynamically at load time and used implicitely + * via and the . + * + * Transient Fields: + * + * - model + * - previous + * + * Reference Fields: + * + * - cell + * + * Constructor: mxGenericChangeCodec + * + * Factory function that creates a for + * the specified change and fieldname. + * + * Parameters: + * + * obj - An instance of the change object. + * variable - The fieldname for the change data. + */ +var mxGenericChangeCodec = function(obj, variable) +{ + var codec = new mxObjectCodec(obj, ['model', 'previous'], ['cell']); + + /** + * Function: afterDecode + * + * Restores the state by assigning the previous value. + */ + codec.afterDecode = function(dec, node, obj) + { + // Allows forward references in sessions. This is a workaround + // for the sequence of edits in mxGraph.moveCells and cellsAdded. + if (mxUtils.isNode(obj.cell)) + { + obj.cell = dec.decodeCell(obj.cell, false); + } + + obj.previous = obj[variable]; + + return obj; + }; + + return codec; +}; + +// Registers the codecs +mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value')); +mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange(), 'style')); +mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange(), 'geometry')); +mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange(), 'collapsed')); +mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange(), 'visible')); +mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange(), 'value')); diff --git a/oaweb/public/cherry/drawio/src/js/io/mxGraphCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxGraphCodec.js new file mode 100644 index 0000000..f3e7a56 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxGraphCodec.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +mxCodecRegistry.register(function() +{ + /** + * Class: mxGraphCodec + * + * Codec for s. This class is created and registered + * dynamically at load time and used implicitely via + * and the . + * + * Transient Fields: + * + * - graphListeners + * - eventListeners + * - view + * - container + * - cellRenderer + * - editor + * - selection + */ + return new mxObjectCodec(new mxGraph(), + ['graphListeners', 'eventListeners', 'view', 'container', + 'cellRenderer', 'editor', 'selection']); + +}()); diff --git a/oaweb/public/cherry/drawio/src/js/io/mxGraphViewCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxGraphViewCodec.js new file mode 100644 index 0000000..c3023de --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxGraphViewCodec.js @@ -0,0 +1,197 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +mxCodecRegistry.register(function() +{ + /** + * Class: mxGraphViewCodec + * + * Custom encoder for s. This class is created + * and registered dynamically at load time and used implicitely via + * and the . This codec only writes views + * into a XML format that can be used to create an image for + * the graph, that is, it contains absolute coordinates with + * computed perimeters, edge styles and cell styles. + */ + var codec = new mxObjectCodec(new mxGraphView()); + + /** + * Function: encode + * + * Encodes the given using + * starting at the model's root. This returns the + * top-level graph node of the recursive encoding. + */ + codec.encode = function(enc, view) + { + return this.encodeCell(enc, view, + view.graph.getModel().getRoot()); + }; + + /** + * Function: encodeCell + * + * Recursively encodes the specifed cell. Uses layer + * as the default nodename. If the cell's parent is + * null, then graph is used for the nodename. If + * returns true for the cell, + * then edge is used for the nodename, else if + * returns true for the cell, + * then vertex is used for the nodename. + * + * is used to create the label + * attribute for the cell. For graph nodes and vertices + * the bounds are encoded into x, y, width and height. + * For edges the points are encoded into a points + * attribute as a space-separated list of comma-separated + * coordinate pairs (eg. x0,y0 x1,y1 ... xn,yn). All + * values from the cell style are added as attribute + * values to the node. + */ + codec.encodeCell = function(enc, view, cell) + { + var model = view.graph.getModel(); + var state = view.getState(cell); + var parent = model.getParent(cell); + + if (parent == null || state != null) + { + var childCount = model.getChildCount(cell); + var geo = view.graph.getCellGeometry(cell); + var name = null; + + if (parent == model.getRoot()) + { + name = 'layer'; + } + else if (parent == null) + { + name = 'graph'; + } + else if (model.isEdge(cell)) + { + name = 'edge'; + } + else if (childCount > 0 && geo != null) + { + name = 'group'; + } + else if (model.isVertex(cell)) + { + name = 'vertex'; + } + + if (name != null) + { + var node = enc.document.createElement(name); + var lab = view.graph.getLabel(cell); + + if (lab != null) + { + node.setAttribute('label', view.graph.getLabel(cell)); + + if (view.graph.isHtmlLabel(cell)) + { + node.setAttribute('html', true); + } + } + + if (parent == null) + { + var bounds = view.getGraphBounds(); + + if (bounds != null) + { + node.setAttribute('x', Math.round(bounds.x)); + node.setAttribute('y', Math.round(bounds.y)); + node.setAttribute('width', Math.round(bounds.width)); + node.setAttribute('height', Math.round(bounds.height)); + } + + node.setAttribute('scale', view.scale); + } + else if (state != null && geo != null) + { + // Writes each key, value in the style pair to an attribute + for (var i in state.style) + { + var value = state.style[i]; + + // Tries to turn objects and functions into strings + if (typeof(value) == 'function' && + typeof(value) == 'object') + { + value = mxStyleRegistry.getName(value); + } + + if (value != null && + typeof(value) != 'function' && + typeof(value) != 'object') + { + node.setAttribute(i, value); + } + } + + var abs = state.absolutePoints; + + // Writes the list of points into one attribute + if (abs != null && abs.length > 0) + { + var pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y); + + for (var i=1; is. This class is created and registered + * dynamically at load time and used implicitely via + * and the . + */ + var codec = new mxObjectCodec(new mxGraphModel()); + + /** + * Function: encodeObject + * + * Encodes the given by writing a (flat) XML sequence of + * cell nodes as produced by the . The sequence is + * wrapped-up in a node with the name root. + */ + codec.encodeObject = function(enc, obj, node) + { + var rootNode = enc.document.createElement('root'); + enc.encodeCell(obj.getRoot(), rootNode); + node.appendChild(rootNode); + }; + + /** + * Function: decodeChild + * + * Overrides decode child to handle special child nodes. + */ + codec.decodeChild = function(dec, child, obj) + { + if (child.nodeName == 'root') + { + this.decodeRoot(dec, child, obj); + } + else + { + mxObjectCodec.prototype.decodeChild.apply(this, arguments); + } + }; + + /** + * Function: decodeRoot + * + * Reads the cells into the graph model. All cells + * are children of the root element in the node. + */ + codec.decodeRoot = function(dec, root, model) + { + var rootCell = null; + var tmp = root.firstChild; + + while (tmp != null) + { + var cell = dec.decodeCell(tmp); + + if (cell != null && cell.getParent() == null) + { + rootCell = cell; + } + + tmp = tmp.nextSibling; + } + + // Sets the root on the model if one has been decoded + if (rootCell != null) + { + model.setRoot(rootCell); + } + }; + + // Returns the codec into the registry + return codec; + +}()); diff --git a/oaweb/public/cherry/drawio/src/js/io/mxObjectCodec.js b/oaweb/public/cherry/drawio/src/js/io/mxObjectCodec.js new file mode 100644 index 0000000..380afa1 --- /dev/null +++ b/oaweb/public/cherry/drawio/src/js/io/mxObjectCodec.js @@ -0,0 +1,1011 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxObjectCodec + * + * Generic codec for JavaScript objects that implements a mapping between + * JavaScript objects and XML nodes that maps each field or element to an + * attribute or child node, and vice versa. + * + * Atomic Values: + * + * Consider the following example. + * + * (code) + * var obj = new Object(); + * obj.foo = "Foo"; + * obj.bar = "Bar"; + * (end) + * + * This object is encoded into an XML node using the following. + * + * (code) + * var enc = new mxCodec(); + * var node = enc.encode(obj); + * (end) + * + * The output of the encoding may be viewed using as follows. + * + * (code) + * mxLog.show(); + * mxLog.debug(mxUtils.getPrettyXml(node)); + * (end) + * + * Finally, the result of the encoding looks as follows. + * + * (code) + * + * (end) + * + * In the above output, the foo and bar fields have been mapped to attributes + * with the same names, and the name of the constructor was used for the + * nodename. + * + * Booleans: + * + * Since booleans are numbers in JavaScript, all boolean values are encoded + * into 1 for true and 0 for false. The decoder also accepts the string true + * and false for boolean values. + * + * Objects: + * + * The above scheme is applied to all atomic fields, that is, to all non-object + * fields of an object. For object fields, a child node is created with a + * special attribute that contains the fieldname. This special attribute is + * called "as" and hence, as is a reserved word that should not be used for a + * fieldname. + * + * Consider the following example where foo is an object and bar is an atomic + * property of foo. + * + * (code) + * var obj = {foo: {bar: "Bar"}}; + * (end) + * + * This will be mapped to the following XML structure by mxObjectCodec. + * + * (code) + * + * + * + * (end) + * + * In the above output, the inner Object node contains the as-attribute that + * specifies the fieldname in the enclosing object. That is, the field foo was + * mapped to a child node with an as-attribute that has the value foo. + * + * Arrays: + * + * Arrays are special objects that are either associative, in which case each + * key, value pair is treated like a field where the key is the fieldname, or + * they are a sequence of atomic values and objects, which is mapped to a + * sequence of child nodes. For object elements, the above scheme is applied + * without the use of the special as-attribute for creating each child. For + * atomic elements, a special add-node is created with the value stored in the + * value-attribute. + * + * For example, the following array contains one atomic value and one object + * with a field called bar. Furthermore it contains two associative entries + * called bar with an atomic value, and foo with an object value. + * + * (code) + * var obj = ["Bar", {bar: "Bar"}]; + * obj["bar"] = "Bar"; + * obj["foo"] = {bar: "Bar"}; + * (end) + * + * This array is represented by the following XML nodes. + * + * (code) + * + * + * + * + * + * (end) + * + * The Array node name is the name of the constructor. The additional + * as-attribute in the last child contains the key of the associative entry, + * whereas the second last child is part of the array sequence and does not + * have an as-attribute. + * + * References: + * + * Objects may be represented as child nodes or attributes with ID values, + * which are used to lookup the object in a table within . The + * function is in charge of deciding if a specific field should + * be encoded as a reference or not. Its default implementation returns true if + * the fieldname is in , an array of strings that is used to configure + * the . + * + * Using this approach, the mapping does not guarantee that the referenced + * object itself exists in the document. The fields that are encoded as + * references must be carefully chosen to make sure all referenced objects + * exist in the document, or may be resolved by some other means if necessary. + * + * For example, in the case of the graph model all cells are stored in a tree + * whose root is referenced by the model's root field. A tree is a structure + * that is well suited for an XML representation, however, the additional edges + * in the graph model have a reference to a source and target cell, which are + * also contained in the tree. To handle this case, the source and target cell + * of an edge are treated as references, whereas the children are treated as + * objects. Since all cells are contained in the tree and no edge references a + * source or target outside the tree, this setup makes sure all referenced + * objects are contained in the document. + * + * In the case of a tree structure we must further avoid infinite recursion by + * ignoring the parent reference of each child. This is done by returning true + * in , whose default implementation uses the array of excluded + * fieldnames passed to the mxObjectCodec constructor. + * + * References are only used for cells in mxGraph. For defining other + * referencable object types, the codec must be able to work out the ID of an + * object. This is done by implementing . For decoding a + * reference, the XML node with the respective id-attribute is fetched from the + * document, decoded, and stored in a lookup table for later reference. For + * looking up external objects, may be implemented. + * + * Expressions: + * + * For decoding JavaScript expressions, the add-node may be used with a text + * content that contains the JavaScript expression. For example, the following + * creates a field called foo in the enclosing object and assigns it the value + * of . + * + * (code) + * + * mxConstants.ALIGN_LEFT + * + * (end) + * + * The resulting object has a field called foo with the value "left". Its XML + * representation looks as follows. + * + * (code) + * + * (end) + * + * This means the expression is evaluated at decoding time and the result of + * the evaluation is stored in the respective field. Valid expressions are all + * JavaScript expressions, including function definitions, which are mapped to + * functions on the resulting object. + * + * Expressions are only evaluated if is true. + * + * Constructor: mxObjectCodec + * + * Constructs a new codec for the specified template object. + * The variables in the optional exclude array are ignored by + * the codec. Variables in the optional idrefs array are + * turned into references in the XML. The optional mapping + * may be used to map from variable names to XML attributes. + * The argument is created as follows: + * + * (code) + * var mapping = new Object(); + * mapping['variableName'] = 'attribute-name'; + * (end) + * + * Parameters: + * + * template - Prototypical instance of the object to be + * encoded/decoded. + * exclude - Optional array of fieldnames to be ignored. + * idrefs - Optional array of fieldnames to be converted to/from + * references. + * mapping - Optional mapping from field- to attributenames. + */ +function mxObjectCodec(template, exclude, idrefs, mapping) { + this.template = template; + + this.exclude = (exclude != null) ? exclude : []; + this.idrefs = (idrefs != null) ? idrefs : []; + this.mapping = (mapping != null) ? mapping : []; + + this.reverse = new Object(); + + for (var i in this.mapping) { + this.reverse[this.mapping[i]] = i; + } +}; + +/** + * Variable: allowEval + * + * Static global switch that specifies if expressions in arrays are allowed. + * Default is false. NOTE: Enabling this carries a possible security risk. + */ +mxObjectCodec.allowEval = false; + +/** + * Variable: template + * + * Holds the template object associated with this codec. + */ +mxObjectCodec.prototype.template = null; + +/** + * Variable: exclude + * + * Array containing the variable names that should be + * ignored by the codec. + */ +mxObjectCodec.prototype.exclude = null; + +/** + * Variable: idrefs + * + * Array containing the variable names that should be + * turned into or converted from references. See + * and . + */ +mxObjectCodec.prototype.idrefs = null; + +/** + * Variable: mapping + * + * Maps from from fieldnames to XML attribute names. + */ +mxObjectCodec.prototype.mapping = null; + +/** + * Variable: reverse + * + * Maps from from XML attribute names to fieldnames. + */ +mxObjectCodec.prototype.reverse = null; + +/** + * Function: getName + * + * Returns the name used for the nodenames and lookup of the codec when + * classes are encoded and nodes are decoded. For classes to work with + * this the codec registry automatically adds an alias for the classname + * if that is different than what this returns. The default implementation + * returns the classname of the template class. + */ +mxObjectCodec.prototype.getName = function () { + return mxUtils.getFunctionName(this.template.constructor); +}; + +/** + * Function: cloneTemplate + * + * Returns a new instance of the template for this codec. + */ +mxObjectCodec.prototype.cloneTemplate = function () { + return new this.template.constructor(); +}; + +/** + * Function: getFieldName + * + * Returns the fieldname for the given attributename. + * Looks up the value in the mapping or returns + * the input if there is no reverse mapping for the + * given name. + */ +mxObjectCodec.prototype.getFieldName = function (attributename) { + if (attributename != null) { + var mapped = this.reverse[attributename]; + + if (mapped != null) { + attributename = mapped; + } + } + + return attributename; +}; + +/** + * Function: getAttributeName + * + * Returns the attributename for the given fieldname. + * Looks up the value in the or returns + * the input if there is no mapping for the + * given name. + */ +mxObjectCodec.prototype.getAttributeName = function (fieldname) { + if (fieldname != null) { + var mapped = this.mapping[fieldname]; + + if (mapped != null) { + fieldname = mapped; + } + } + + return fieldname; +}; + +/** + * Function: isExcluded + * + * Returns true if the given attribute is to be ignored by the codec. This + * implementation returns true if the given fieldname is in or + * if the fieldname equals . + * + * Parameters: + * + * obj - Object instance that contains the field. + * attr - Fieldname of the field. + * value - Value of the field. + * write - Boolean indicating if the field is being encoded or decoded. + * Write is true if the field is being encoded, else it is being decoded. + */ +mxObjectCodec.prototype.isExcluded = function (obj, attr, value, write) { + return attr == mxObjectIdentity.FIELD_NAME || + mxUtils.indexOf(this.exclude, attr) >= 0; +}; + +/** + * Function: isReference + * + * Returns true if the given fieldname is to be treated + * as a textual reference (ID). This implementation returns + * true if the given fieldname is in . + * + * Parameters: + * + * obj - Object instance that contains the field. + * attr - Fieldname of the field. + * value - Value of the field. + * write - Boolean indicating if the field is being encoded or decoded. + * Write is true if the field is being encoded, else it is being decoded. + */ +mxObjectCodec.prototype.isReference = function (obj, attr, value, write) { + return mxUtils.indexOf(this.idrefs, attr) >= 0; +}; + +/** + * Function: encode + * + * Encodes the specified object and returns a node + * representing then given object. Calls + * after creating the node and with the + * resulting node after processing. + * + * Enc is a reference to the calling encoder. It is used + * to encode complex objects and create references. + * + * This implementation encodes all variables of an + * object according to the following rules: + * + * - If the variable name is in then it is ignored. + * - If the variable name is in then + * is used to replace the object with its ID. + * - The variable name is mapped using . + * - If obj is an array and the variable name is numeric + * (ie. an index) then it is not encoded. + * - If the value is an object, then the codec is used to + * create a child node with the variable name encoded into + * the "as" attribute. + * - Else, if is true or the value differs + * from the template value, then ... + * - ... if obj is not an array, then the value is mapped to + * an attribute. + * - ... else if obj is an array, the value is mapped to an + * add child with a value attribute or a text child node, + * if the value is a function. + * + * If no ID exists for a variable in or if an object + * cannot be encoded, a warning is issued using . + * + * Returns the resulting XML node that represents the given + * object. + * + * Parameters: + * + * enc - that controls the encoding process. + * obj - Object to be encoded. + */ +mxObjectCodec.prototype.encode = function (enc, obj) { + var node = enc.document.createElement(this.getName()); + + obj = this.beforeEncode(enc, obj, node); + this.encodeObject(enc, obj, node); + + return this.afterEncode(enc, obj, node); +}; + +/** + * Function: encodeObject + * + * Encodes the value of each member in then given obj into the given node using + * . + * + * Parameters: + * + * enc - that controls the encoding process. + * obj - Object to be encoded. + * node - XML node that contains the encoded object. + */ +mxObjectCodec.prototype.encodeObject = function (enc, obj, node) { + enc.setAttribute(node, 'id', enc.getId(obj)); + + for (var i in obj) { + var name = i; + var value = obj[name]; + + if (value != null && !this.isExcluded(obj, name, value, true)) { + if (mxUtils.isInteger(name)) { + name = null; + } + + this.encodeValue(enc, obj, name, value, node); + } + } +}; + +/** + * Function: encodeValue + * + * Converts the given value according to the mappings + * and id-refs in this codec and uses + * to write the attribute into the given node. + * + * Parameters: + * + * enc - that controls the encoding process. + * obj - Object whose property is going to be encoded. + * name - XML node that contains the encoded object. + * value - Value of the property to be encoded. + * node - XML node that contains the encoded object. + */ +mxObjectCodec.prototype.encodeValue = function (enc, obj, name, value, node) { + if (value != null) { + if (this.isReference(obj, name, value, true)) { + var tmp = enc.getId(value); + + if (tmp == null) { + mxLog.warn('mxObjectCodec.encode: No ID for ' + + this.getName() + '.' + name + '=' + value); + return; // exit + } + + value = tmp; + } + + var defaultValue = this.template[name]; + + // Checks if the value is a default value and + // the name is correct + if (name == null || enc.encodeDefaults || defaultValue != value) { + name = this.getAttributeName(name); + this.writeAttribute(enc, obj, name, value, node); + } + } +}; + +/** + * Function: writeAttribute + * + * Writes the given value into node using + * or depending on the type of the value. + */ +mxObjectCodec.prototype.writeAttribute = function (enc, obj, name, value, node) { + if (typeof (value) != 'object' /* primitive type */) { + this.writePrimitiveAttribute(enc, obj, name, value, node); + } + else /* complex type */ { + this.writeComplexAttribute(enc, obj, name, value, node); + } +}; + +/** + * Function: writePrimitiveAttribute + * + * Writes the given value as an attribute of the given node. + */ +mxObjectCodec.prototype.writePrimitiveAttribute = function (enc, obj, name, value, node) { + value = this.convertAttributeToXml(enc, obj, name, value, node); + + if (name == null) { + var child = enc.document.createElement('add'); + + if (typeof (value) == 'function') { + child.appendChild(enc.document.createTextNode(value)); + } + else { + enc.setAttribute(child, 'value', value); + } + + node.appendChild(child); + } + else if (typeof (value) != 'function') { + enc.setAttribute(node, name, value); + } +}; + +/** + * Function: writeComplexAttribute + * + * Writes the given value as a child node of the given node. + */ +mxObjectCodec.prototype.writeComplexAttribute = function (enc, obj, name, value, node) { + var child = enc.encode(value); + + if (child != null) { + if (name != null) { + child.setAttribute('as', name); + } + + node.appendChild(child); + } + else { + mxLog.warn('mxObjectCodec.encode: No node for ' + this.getName() + '.' + name + ': ' + value); + } +}; + +/** + * Function: convertAttributeToXml + * + * Converts true to "1" and false to "0" is returns true. + * All other values are not converted. + * + * Parameters: + * + * enc - that controls the encoding process. + * obj - Objec to convert the attribute for. + * name - Name of the attribute to be converted. + * value - Value to be converted. + */ +mxObjectCodec.prototype.convertAttributeToXml = function (enc, obj, name, value) { + // Makes sure to encode boolean values as numeric values + if (this.isBooleanAttribute(enc, obj, name, value)) { + // Checks if the value is true (do not use the value as is, because + // this would check if the value is not null, so 0 would be true) + value = (value == true) ? '1' : '0'; + } + + return value; +}; + +/** + * Function: isBooleanAttribute + * + * Returns true if the given object attribute is a boolean value. + * + * Parameters: + * + * enc - that controls the encoding process. + * obj - Objec to convert the attribute for. + * name - Name of the attribute to be converted. + * value - Value of the attribute to be converted. + */ +mxObjectCodec.prototype.isBooleanAttribute = function (enc, obj, name, value) { + return (typeof (value.length) == 'undefined' && (value == true || value == false)); +}; + +/** + * Function: convertAttributeFromXml + * + * Converts booleans and numeric values to the respective types. Values are + * numeric if returns true. + * + * Parameters: + * + * dec - that controls the decoding process. + * attr - XML attribute to be converted. + * obj - Objec to convert the attribute for. + */ +mxObjectCodec.prototype.convertAttributeFromXml = function (dec, attr, obj) { + var value = attr.value; + + if (this.isNumericAttribute(dec, attr, obj)) { + value = parseFloat(value); + + if (isNaN(value)) { + value = 0; + } + } + + return value; +}; + +/** + * Function: isNumericAttribute + * + * Returns true if the given XML attribute is or should be a numeric value. + * + * Parameters: + * + * dec - that controls the decoding process. + * attr - XML attribute to be converted. + * obj - Objec to convert the attribute for. + */ +mxObjectCodec.prototype.isNumericAttribute = function (dec, attr, obj) { + // Handles known numeric attributes for generic objects + var result = (obj.constructor == mxGeometry && + (attr.name == 'x' || attr.name == 'y' || + attr.name == 'width' || attr.name == 'height')) || + (obj.constructor == mxPoint && + (attr.name == 'x' || attr.name == 'y')) || + mxUtils.isNumeric(attr.value); + + return result; +}; + +/** + * Function: beforeEncode + * + * Hook for subclassers to pre-process the object before + * encoding. This returns the input object. The return + * value of this function is used in to perform + * the default encoding into the given node. + * + * Parameters: + * + * enc - that controls the encoding process. + * obj - Object to be encoded. + * node - XML node to encode the object into. + */ +mxObjectCodec.prototype.beforeEncode = function (enc, obj, node) { + return obj; +}; + +/** + * Function: afterEncode + * + * Hook for subclassers to post-process the node + * for the given object after encoding and return the + * post-processed node. This implementation returns + * the input node. The return value of this method + * is returned to the encoder from . + * + * Parameters: + * + * enc - that controls the encoding process. + * obj - Object to be encoded. + * node - XML node that represents the default encoding. + */ +mxObjectCodec.prototype.afterEncode = function (enc, obj, node) { + return node; +}; + +/** + * Function: decode + * + * Parses the given node into the object or returns a new object + * representing the given node. + * + * Dec is a reference to the calling decoder. It is used to decode + * complex objects and resolve references. + * + * If a node has an id attribute then the object cache is checked for the + * object. If the object is not yet in the cache then it is constructed + * using the constructor of