2 * $Id: Editor.js,v 1.14 2013/03/06 17:57:10 boris Exp $
3 * Copyright (c) 2006-2012, JGraph Ltd
5 // Specifies if local storage should be used (eg. on the iPad which has no filesystem)
6 var useLocalStorage = (mxClient.IS_TOUCH || urlParams['storage'] == 'local') && typeof(localStorage) != 'undefined';
7 var fileSupport = window.File != null && window.FileReader != null && window.FileList != null;
9 // Specifies if connector should be shown on selected cells
10 var touchStyle = mxClient.IS_TOUCH || urlParams['touch'] == '1';
12 // Counts open editor tabs (must be global for cross-window access)
15 // Cross-domain window access is not allowed in FF, so if we
16 // were opened from another domain then this will fail.
20 while (op.opener != null && !isNaN(op.opener.counter)) {
24 // Increments the counter in the first opener in the chain
34 * Editor constructor executed on page load.
37 mxEventSource.call(this);
39 this.initStencilRegistry();
40 this.graph = new Graph();
41 this.outline = new mxOutline(this.graph);
42 this.outline.updateOnPan = true;
43 this.undoManager = this.createUndoManager();
46 // Contains the name which was used for the last save. Default value is null.
49 this.getOrCreateFilename = function() {
50 return this.filename || mxResources.get('drawing', [counter]) + '.xml';
53 this.getFilename = function() {
57 // Sets the status and fires a statusChanged event
58 this.setStatus = function(value) {
60 this.fireEvent(new mxEventObject('statusChanged'));
63 // Returns the current status
64 this.getStatus = function() {
68 // Contains the current modified state of the diagram. This is false for
69 // new diagrams and after the diagram was saved.
70 this.modified = false;
72 // Updates modified state if graph changes
73 this.graphChangeListener = function() {
76 this.graph.getModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function() {
77 this.graphChangeListener.apply(this, arguments);
80 // Installs dialog if browser window is closed without saving
81 // This must be disabled during save and image export
82 window.onbeforeunload = mxUtils.bind(this, function() {
84 return mxResources.get('allChangesLost');
88 // Sets persistent graph state defaults
89 this.graph.resetViewOnRootChange = false;
90 this.graph.scrollbars = true;
91 this.graph.background = null;
94 // Editor inherits from mxEventSource
95 mxUtils.extend(Editor, mxEventSource);
98 * Specifies the image URL to be used for the grid.
100 Editor.prototype.gridImage = IMAGE_PATH + '/grid.gif';
103 * Specifies the image URL to be used for the transparent background.
105 Editor.prototype.transparentImage = IMAGE_PATH + '/transparent.gif';
108 * Sets the XML node for the current diagram.
110 Editor.prototype.setGraphXml = function(node) {
111 var dec = new mxCodec(node.ownerDocument);
113 if (node.nodeName == 'mxGraphModel') {
114 this.graph.view.scale = 1;
115 this.graph.gridEnabled = node.getAttribute('grid') != '0';
116 this.graph.graphHandler.guidesEnabled = node.getAttribute('guides') != '0';
117 this.graph.setTooltips(node.getAttribute('tooltips') != '0');
118 this.graph.setConnectable(node.getAttribute('connect') != '0');
119 this.graph.foldingEnabled = node.getAttribute('fold') != '0';
120 this.graph.scrollbars = node.getAttribute('scrollbars') != '0';
122 if (!this.graph.scrollbars) {
123 this.graph.container.scrollLeft = 0;
124 this.graph.container.scrollTop = 0;
125 this.graph.view.translate.x = Number(node.getAttribute('dx') || 0);
126 this.graph.view.translate.y = Number(node.getAttribute('dy') || 0);
129 this.graph.pageVisible = node.getAttribute('page') == '1';
130 this.graph.pageBreaksVisible = this.graph.pageVisible;
131 this.graph.preferPageSize = this.graph.pageBreaksVisible;
133 // Loads the persistent state settings
134 var ps = node.getAttribute('pageScale');
137 this.graph.pageScale = ps;
139 this.graph.pageScale = 1.5;
142 var pw = node.getAttribute('pageWidth');
143 var ph = node.getAttribute('pageHeight');
145 if (pw != null && ph != null) {
146 this.graph.pageFormat = new mxRectangle(0, 0, parseFloat(pw), parseFloat(ph));
147 this.outline.outline.pageFormat = this.graph.pageFormat;
150 // Loads the persistent state settings
151 var bg = node.getAttribute('background');
153 if (bg != null && bg.length > 0) {
154 this.graph.background = bg;
157 dec.decode(node, this.graph.getModel());
158 this.updateGraphComponents();
163 * Returns the XML node that represents the current diagram.
165 Editor.prototype.getGraphXml = function() {
166 var enc = new mxCodec(mxUtils.createXmlDocument());
167 var node = enc.encode(this.graph.getModel());
169 if (this.graph.view.translate.x != 0 || this.graph.view.translate.y != 0) {
170 node.setAttribute('dx', Math.round(this.graph.view.translate.x * 100) / 100);
171 node.setAttribute('dy', Math.round(this.graph.view.translate.y * 100) / 100);
174 node.setAttribute('grid', (this.graph.isGridEnabled()) ? '1' : '0');
175 node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
176 node.setAttribute('guides', (this.graph.graphHandler.guidesEnabled) ? '1' : '0');
177 node.setAttribute('tooltips', (this.graph.tooltipHandler.isEnabled()) ? '1' : '0');
178 node.setAttribute('connect', (this.graph.connectionHandler.isEnabled()) ? '1' : '0');
179 node.setAttribute('fold', (this.graph.foldingEnabled) ? '1' : '0');
180 node.setAttribute('page', (this.graph.pageVisible) ? '1' : '0');
181 node.setAttribute('pageScale', this.graph.pageScale);
182 node.setAttribute('pageWidth', this.graph.pageFormat.width);
183 node.setAttribute('pageHeight', this.graph.pageFormat.height);
185 if (!this.graph.scrollbars) {
186 node.setAttribute('scrollbars', '0');
189 if (this.graph.background != null) {
190 node.setAttribute('background', this.graph.background);
197 * Keeps the graph container in sync with the persistent graph state
199 Editor.prototype.updateGraphComponents = function() {
200 var graph = this.graph;
201 var outline = this.outline;
203 if (graph.container != null && outline.outline.container != null) {
204 if (graph.background != null) {
205 if (graph.background == 'none') {
206 graph.container.style.backgroundColor = 'transparent';
208 if (graph.view.backgroundPageShape != null) {
209 graph.view.backgroundPageShape.fill = graph.background;
210 graph.view.backgroundPageShape.reconfigure();
213 graph.container.style.backgroundColor = graph.background;
216 graph.container.style.backgroundColor = '';
219 if (graph.pageVisible) {
220 graph.container.style.backgroundColor = '#ebebeb';
221 graph.container.style.borderStyle = 'solid';
222 graph.container.style.borderColor = '#e5e5e5';
223 graph.container.style.borderTopWidth = '1px';
224 graph.container.style.borderLeftWidth = '1px';
225 graph.container.style.borderRightWidth = '0px';
226 graph.container.style.borderBottomWidth = '0px';
228 graph.container.style.border = '';
231 outline.outline.container.style.backgroundColor = graph.container.style.backgroundColor;
233 if (outline.outline.pageVisible != graph.pageVisible ||
234 outline.outline.pageScale != graph.pageScale) {
235 outline.outline.pageScale = graph.pageScale;
236 outline.outline.pageVisible = graph.pageVisible;
237 outline.outline.view.validate();
240 if (graph.scrollbars && graph.container.style.overflow == 'hidden' && !touchStyle) {
241 graph.container.style.overflow = 'auto';
242 } else if (!graph.scrollbars || touchStyle) {
243 graph.container.style.overflow = 'hidden';
246 // Transparent.gif is a workaround for focus repaint problems in IE
247 var noBackground = (mxClient.IS_IE && document.documentMode >= 9) ? 'url(' + this.transparentImage + ')' : 'none';
248 graph.container.style.backgroundImage = (!graph.pageVisible && graph.isGridEnabled()) ? 'url(' + this.gridImage + ')' : noBackground;
250 if (graph.view.backgroundPageShape != null) {
251 graph.view.backgroundPageShape.node.style.backgroundImage = (this.graph.isGridEnabled()) ? 'url(' + this.gridImage + ')' : 'none';
257 * Initializes the environment.
259 Editor.prototype.init = function() {
260 // Adds stylesheet for IE6
261 if (mxClient.IS_IE6) {
262 mxClient.link('stylesheet', CSS_PATH + '/grapheditor-ie6.css');
265 // Adds required resources (disables loading of fallback properties, this can only
266 // be used if we know that all keys are defined in the language specific file)
267 mxResources.loadDefaultBundle = false;
268 mxResources.add(RESOURCE_BASE);
270 // Makes the connection hotspot smaller
271 mxConstants.DEFAULT_HOTSPOT = 0.3;
273 var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
274 mxConnectionHandler.prototype.createMarker = function() {
275 var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
277 // Overrides to ignore hotspot only for target terminal
278 marker.intersects = mxUtils.bind(this, function(state, evt) {
279 if (this.isConnecting()) {
283 return mxCellMarker.prototype.intersects.apply(marker, arguments);
289 // Makes the shadow brighter
290 mxConstants.SHADOWCOLOR = '#d0d0d0';
292 // Changes some default colors
293 mxConstants.HANDLE_FILLCOLOR = '#99ccff';
294 mxConstants.HANDLE_STROKECOLOR = '#0088cf';
295 mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
296 mxConstants.OUTLINE_COLOR = '#00a8ff';
297 mxConstants.OUTLINE_HANDLE_FILLCOLOR = '#99ccff';
298 mxConstants.OUTLINE_HANDLE_STROKECOLOR = '#00a8ff';
299 mxConstants.CONNECT_HANDLE_FILLCOLOR = '#cee7ff';
300 mxConstants.EDGE_SELECTION_COLOR = '#00a8ff';
301 mxConstants.DEFAULT_VALID_COLOR = '#00a8ff';
302 mxConstants.LABEL_HANDLE_FILLCOLOR = '#cee7ff';
303 mxConstants.GUIDE_COLOR = '#0088cf';
305 mxGraph.prototype.pageBreakColor = '#c0c0c0';
306 mxGraph.prototype.pageScale = 1;
308 // Increases default rubberband opacity (default is 20)
309 mxRubberband.prototype.defaultOpacity = 30;
311 // Changes border color of background page shape
312 mxGraphView.prototype.createBackgroundPageShape = function(bounds) {
313 return new mxRectangleShape(bounds, this.graph.background || 'white', '#cacaca');
316 // Fits the number of background pages to the graph
317 mxGraphView.prototype.getBackgroundPageBounds = function() {
318 var gb = this.getGraphBounds();
320 // Computes unscaled, untranslated graph bounds
321 var x = (gb.width > 0) ? gb.x / this.scale - this.translate.x : 0;
322 var y = (gb.height > 0) ? gb.y / this.scale - this.translate.y : 0;
323 var w = gb.width / this.scale;
324 var h = gb.height / this.scale;
326 var fmt = this.graph.pageFormat;
327 var ps = this.graph.pageScale;
329 var pw = fmt.width * ps;
330 var ph = fmt.height * ps;
332 var x0 = Math.floor(Math.min(0, x) / pw);
333 var y0 = Math.floor(Math.min(0, y) / ph);
334 var xe = Math.ceil(Math.max(1, x + w) / pw);
335 var ye = Math.ceil(Math.max(1, y + h) / ph);
340 var bounds = new mxRectangle(this.scale * (this.translate.x + x0 * pw), this.scale *
341 (this.translate.y + y0 * ph), this.scale * rows * pw, this.scale * cols * ph);
346 // Add panning for background page in VML
347 var graphPanGraph = mxGraph.prototype.panGraph;
348 mxGraph.prototype.panGraph = function(dx, dy) {
349 graphPanGraph.apply(this, arguments);
351 if ((this.dialect != mxConstants.DIALECT_SVG && this.view.backgroundPageShape != null) &&
352 (!this.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.container))) {
353 this.view.backgroundPageShape.node.style.marginLeft = dx + 'px';
354 this.view.backgroundPageShape.node.style.marginTop = dy + 'px';
360 // Uses HTML for background pages (to support grid background image)
361 mxGraphView.prototype.validateBackground = function() {
362 var bg = this.graph.getBackgroundImage();
365 if (this.backgroundImage == null || this.backgroundImage.image != bg.src) {
366 if (this.backgroundImage != null) {
367 this.backgroundImage.destroy();
370 var bounds = new mxRectangle(0, 0, 1, 1);
372 this.backgroundImage = new mxImageShape(bounds, bg.src);
373 this.backgroundImage.dialect = this.graph.dialect;
374 this.backgroundImage.init(this.backgroundPane);
375 this.backgroundImage.redraw();
378 this.redrawBackgroundImage(this.backgroundImage, bg);
379 } else if (this.backgroundImage != null) {
380 this.backgroundImage.destroy();
381 this.backgroundImage = null;
384 if (this.graph.pageVisible) {
385 var bounds = this.getBackgroundPageBounds();
387 if (this.backgroundPageShape == null) {
388 this.backgroundPageShape = this.createBackgroundPageShape(bounds);
389 this.backgroundPageShape.scale = 1;
390 this.backgroundPageShape.isShadow = true;
391 this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
392 this.backgroundPageShape.init(this.graph.container);
393 // Required for the browser to render the background page in correct order
394 this.graph.container.firstChild.style.position = 'absolute';
395 this.graph.container.insertBefore(this.backgroundPageShape.node, this.graph.container.firstChild);
396 this.backgroundPageShape.redraw();
398 this.backgroundPageShape.node.className = 'geBackgroundPage';
399 this.backgroundPageShape.node.style.backgroundPosition = '-1px -1px';
401 // Adds listener for double click handling on background
402 mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
403 mxUtils.bind(this, function(evt) {
404 this.graph.dblClick(evt);
408 // Adds basic listeners for graph event dispatching outside of the
409 // container and finishing the handling of a single gesture
410 mxEvent.addGestureListeners(this.backgroundPageShape.node,
411 mxUtils.bind(this, function(evt) {
412 this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
414 mxUtils.bind(this, function(evt) {
415 // Hides the tooltip if mouse is outside container
416 if (this.graph.tooltipHandler != null &&
417 this.graph.tooltipHandler.isHideOnHover()) {
418 this.graph.tooltipHandler.hide();
421 if (this.graph.isMouseDown &&
422 !mxEvent.isConsumed(evt)) {
423 this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
424 new mxMouseEvent(evt));
427 mxUtils.bind(this, function(evt) {
428 this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
429 new mxMouseEvent(evt));
432 this.backgroundPageShape.scale = 1;
433 this.backgroundPageShape.bounds = bounds;
434 this.backgroundPageShape.redraw();
437 this.backgroundPageShape.node.style.backgroundImage = (this.graph.isGridEnabled()) ?
438 'url(' + editor.gridImage + ')' : 'none';
439 } else if (this.backgroundPageShape != null) {
440 this.backgroundPageShape.destroy();
441 this.backgroundPageShape = null;
445 // Draws page breaks only within the page
446 mxGraph.prototype.updatePageBreaks = function(visible, width, height) {
447 var scale = this.view.scale;
448 var tr = this.view.translate;
449 var fmt = this.pageFormat;
450 var ps = scale * this.pageScale;
452 var bounds2 = this.view.getBackgroundPageBounds();
454 width = bounds2.width;
455 height = bounds2.height;
456 var bounds = new mxRectangle(scale * tr.x, scale * tr.y,
457 fmt.width * ps, fmt.height * ps);
459 // Does not show page breaks if the scale is too small
460 visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
462 var horizontalCount = (visible) ? Math.ceil(width / bounds.width) - 1 : 0;
463 var verticalCount = (visible) ? Math.ceil(height / bounds.height) - 1 : 0;
464 var right = bounds2.x + width;
465 var bottom = bounds2.y + height;
467 if (this.horizontalPageBreaks == null && horizontalCount > 0) {
468 this.horizontalPageBreaks = [];
471 if (this.horizontalPageBreaks != null) {
472 for (var i = 0; i <= horizontalCount; i++) {
473 var pts = [new mxPoint(bounds2.x + (i + 1) * bounds.width, bounds2.y),
474 new mxPoint(bounds2.x + (i + 1) * bounds.width, bottom)
477 if (this.horizontalPageBreaks[i] != null) {
478 this.horizontalPageBreaks[i].scale = 1;
479 this.horizontalPageBreaks[i].points = pts;
480 this.horizontalPageBreaks[i].redraw();
482 var pageBreak = new mxPolyline(pts, this.pageBreakColor, this.scale);
483 pageBreak.dialect = this.dialect;
484 pageBreak.isDashed = this.pageBreakDashed;
485 pageBreak.addPipe = false;
486 pageBreak.scale = scale;
487 pageBreak.init(this.view.backgroundPane);
490 this.horizontalPageBreaks[i] = pageBreak;
494 for (var i = horizontalCount; i < this.horizontalPageBreaks.length; i++) {
495 this.horizontalPageBreaks[i].destroy();
498 this.horizontalPageBreaks.splice(horizontalCount, this.horizontalPageBreaks.length - horizontalCount);
501 if (this.verticalPageBreaks == null && verticalCount > 0) {
502 this.verticalPageBreaks = [];
505 if (this.verticalPageBreaks != null) {
506 for (var i = 0; i <= verticalCount; i++) {
507 var pts = [new mxPoint(bounds2.x, bounds2.y + (i + 1) * bounds.height),
508 new mxPoint(right, bounds2.y + (i + 1) * bounds.height)
511 if (this.verticalPageBreaks[i] != null) {
512 this.verticalPageBreaks[i].scale = 1; //scale;
513 this.verticalPageBreaks[i].points = pts;
514 this.verticalPageBreaks[i].redraw();
516 var pageBreak = new mxPolyline(pts, this.pageBreakColor, scale);
517 pageBreak.dialect = this.dialect;
518 pageBreak.isDashed = this.pageBreakDashed;
519 pageBreak.addPipe = false;
520 pageBreak.scale = scale;
521 pageBreak.init(this.view.backgroundPane);
524 this.verticalPageBreaks[i] = pageBreak;
528 for (var i = verticalCount; i < this.verticalPageBreaks.length; i++) {
529 this.verticalPageBreaks[i].destroy();
532 this.verticalPageBreaks.splice(verticalCount, this.verticalPageBreaks.length - verticalCount);
536 // Enables snapping to off-grid terminals for edge waypoints
537 mxEdgeHandler.prototype.snapToTerminals = true;
540 mxGraphHandler.prototype.guidesEnabled = true;
542 // Disables removing relative children from parents
543 var mxGraphHandlerShouldRemoveCellsFromParent = mxGraphHandler.prototype.shouldRemoveCellsFromParent;
544 mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt) {
545 for (var i = 0; i < cells.length; i++) {
546 if (this.graph.getModel().isVertex(cells[i])) {
547 var geo = this.graph.getCellGeometry(cells[i]);
549 if (geo != null && geo.relative) {
555 return mxGraphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
558 // Alt-move disables guides
559 mxGuide.prototype.isEnabledForEvent = function(evt) {
560 return !mxEvent.isAltDown(evt);
563 // Consumes click events for disabled menu items
564 mxPopupMenuAddItem = mxPopupMenu.prototype.addItem;
565 mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled) {
566 var result = mxPopupMenuAddItem.apply(this, arguments);
568 if (enabled != null && !enabled) {
569 mxEvent.addListener(result, 'mousedown', function(evt) {
570 mxEvent.consume(evt);
577 // Selects descendants before children selection mode
578 var graphHandlerGetInitialCellForEvent = mxGraphHandler.prototype.getInitialCellForEvent;
579 mxGraphHandler.prototype.getInitialCellForEvent = function(me) {
580 var model = this.graph.getModel();
581 var psel = model.getParent(this.graph.getSelectionCell());
582 var cell = graphHandlerGetInitialCellForEvent.apply(this, arguments);
583 var parent = model.getParent(cell);
585 if (psel == null || (psel != cell && psel != parent)) {
586 while (!this.graph.isCellSelected(cell) && !this.graph.isCellSelected(parent) &&
587 model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
589 parent = this.graph.getModel().getParent(cell);
596 // Selection is delayed to mouseup if child selected
597 var graphHandlerIsDelayedSelection = mxGraphHandler.prototype.isDelayedSelection;
598 mxGraphHandler.prototype.isDelayedSelection = function(cell) {
599 var result = graphHandlerIsDelayedSelection.apply(this, arguments);
600 var model = this.graph.getModel();
601 var psel = model.getParent(this.graph.getSelectionCell());
602 var parent = model.getParent(cell);
604 if (psel == null || (psel != cell && psel != parent)) {
605 if (!this.graph.isCellSelected(cell) && model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
613 // Delayed selection of parent group
614 mxGraphHandler.prototype.selectDelayed = function(me) {
615 var cell = me.getCell();
621 var model = this.graph.getModel();
622 var parent = model.getParent(cell);
624 while (this.graph.isCellSelected(cell) && model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
626 parent = model.getParent(cell);
629 this.graph.selectCellForEvent(cell, me.getEvent());
632 // Returns last selected ancestor
633 mxPanningHandler.prototype.getCellForPopupEvent = function(me) {
634 var cell = me.getCell();
635 var model = this.graph.getModel();
636 var parent = model.getParent(cell);
638 while (model.isVertex(parent) && !this.graph.isValidRoot(parent)) {
639 if (this.graph.isCellSelected(parent)) {
643 parent = model.getParent(parent);
651 * Creates and returns a new undo manager.
653 Editor.prototype.createUndoManager = function() {
654 var graph = this.graph;
655 var undoMgr = new mxUndoManager();
657 // Installs the command history
658 var listener = function(sender, evt) {
659 undoMgr.undoableEditHappened(evt.getProperty('edit'));
662 graph.getModel().addListener(mxEvent.UNDO, listener);
663 graph.getView().addListener(mxEvent.UNDO, listener);
665 // Keeps the selection in sync with the history
666 var undoHandler = function(sender, evt) {
667 var cand = graph.getSelectionCellsForChanges(evt.getProperty('edit').changes);
670 for (var i = 1; i < cand.length; i++) {
671 if (graph.view.getState(cand[i]) != null) {
676 graph.setSelectionCells(cells);
679 undoMgr.addListener(mxEvent.UNDO, undoHandler);
680 undoMgr.addListener(mxEvent.REDO, undoHandler);
686 * Adds basic stencil set (no namespace).
688 Editor.prototype.initStencilRegistry = function() {
689 // Loads default stencils
690 mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/general.xml');
694 * Overrides stencil registry for dynamic loading of stencils.
698 * Maps from library names to an array of Javascript filenames,
699 * which are synchronously loaded. Currently only stencil files
700 * (.xml) and JS files (.js) are supported.
701 * IMPORTANT: For embedded diagrams to work entries must also
702 * be added in EmbedServlet.java.
704 mxStencilRegistry.libraries = {};
707 * Stores all package names that have been dynamically loaded.
708 * Each package is only loaded once.
710 mxStencilRegistry.packages = [];
712 // Extends the default stencil registry to add dynamic loading
713 mxStencilRegistry.getStencil = function(name) {
714 var result = mxStencilRegistry.stencils[name];
716 if (result == null) {
717 var basename = mxStencilRegistry.getBasenameForStencil(name);
719 // Loads stencil files and tries again
720 if (basename != null) {
721 var libs = mxStencilRegistry.libraries[basename];
724 if (mxStencilRegistry.packages[basename] == null) {
725 mxStencilRegistry.packages[basename] = 1;
727 for (var i = 0; i < libs.length; i++) {
730 if (fname.toLowerCase().substring(fname.length - 4, fname.length) == '.xml') {
731 mxStencilRegistry.loadStencilSet(fname, null);
732 } else if (fname.toLowerCase().substring(fname.length - 3, fname.length) == '.js') {
733 var req = mxUtils.load(fname);
736 eval.call(window, req.getText());
739 // FIXME: This does not yet work as the loading is triggered after
740 // the shape was used in the graph, at which point the keys have
741 // typically been translated in the calling method.
742 //mxResources.add(fname);
747 mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/' + basename + '.xml', null);
750 result = mxStencilRegistry.stencils[name];
757 // Returns the basename for the given stencil or null if no file must be
758 // loaded to render the given stencil.
759 mxStencilRegistry.getBasenameForStencil = function(name) {
760 var parts = name.split('.');
763 if (parts.length > 0 && parts[0] == 'mxgraph') {
766 for (var i = 2; i < parts.length - 1; i++) {
767 tmp += '/' + parts[i];
774 // Loads the given stencil set
775 mxStencilRegistry.loadStencilSet = function(stencilFile, postStencilLoad, force) {
776 force = (force != null) ? force : false;
778 // Uses additional cache for detecting previous load attempts
779 var xmlDoc = mxStencilRegistry.packages[stencilFile];
781 if (force || xmlDoc == null) {
784 if (xmlDoc == null) {
785 var req = mxUtils.load(stencilFile);
786 xmlDoc = req.getXml();
787 mxStencilRegistry.packages[stencilFile] = xmlDoc;
791 mxStencilRegistry.parseStencilSet(xmlDoc, postStencilLoad, install);
795 // Parses the given stencil set
796 mxStencilRegistry.parseStencilSet = function(xmlDocument, postStencilLoad, install) {
797 install = (install != null) ? install : true;
798 var root = xmlDocument.documentElement;
799 var shape = root.firstChild;
800 var packageName = '';
801 var name = root.getAttribute('name');
804 packageName = name + '.';
807 while (shape != null) {
808 if (shape.nodeType == mxConstants.NODETYPE_ELEMENT) {
809 name = shape.getAttribute('name');
812 packageName = packageName.toLowerCase();
813 var stencilName = name.replace(/ /g, "_");
816 mxStencilRegistry.addStencil(packageName + stencilName.toLowerCase(), new mxStencil(shape));
819 if (postStencilLoad != null) {
820 var w = shape.getAttribute('w');
821 var h = shape.getAttribute('h');
823 w = (w == null) ? 80 : parseInt(w, 10);
824 h = (h == null) ? 80 : parseInt(h, 10);
826 postStencilLoad(packageName, stencilName, name, w, h);
831 shape = shape.nextSibling;
837 * Class for asynchronously opening a new window and loading a file at the same
838 * time. This acts as a bridge between the open dialog and the new editor.
840 OpenFile = function(done) {
841 this.producer = null;
842 this.consumer = null;
847 * Registers the editor from the new window.
849 OpenFile.prototype.setConsumer = function(value) {
850 this.consumer = value;
855 * Sets the data from the loaded file.
857 OpenFile.prototype.setData = function(value, filename) {
859 this.filename = filename;
864 * Displays an error message.
866 OpenFile.prototype.error = function(msg) {
874 OpenFile.prototype.execute = function() {
875 if (this.consumer != null && this.data != null) {
876 this.consumer(this.data, this.filename);
882 * Cancels the operation.
884 OpenFile.prototype.cancel = function() {
885 if (this.done != null) {