UNPKG

devexpress-diagram

Version:

DevExpress Diagram Control

704 lines (640 loc) 32.7 kB
import { Size } from "@devexpress/utils/lib/geometry/size"; import { Point } from "@devexpress/utils/lib/geometry/point"; import { ModelManipulator } from "./Model/ModelManipulator"; import { CommandManager, DiagramCommand } from "./Commands/CommandManager"; import { EventManager } from "./Events/EventManager"; import { DiagramModel } from "./Model/Model"; import { Selection } from "./Selection/Selection"; import { History, IHistoryChangesListener } from "./History/History"; import { BarManager } from "./UI/BarManager"; import { RenderManager as RenderManager } from "./Render/RenderManager"; import { ShapeDescriptionManager } from "./Model/Shapes/Descriptions/ShapeDescriptionManager"; import { ItemKey, ConnectionPointSide, DiagramItem, ItemDataKey } from "./Model/DiagramItem"; import { IDataChangesListener, IEdgeDataImporter, INodeDataImporter, IDataImportParameters, IDataLayoutImportParameters } from "./Data/Interfaces"; import { DocumentDataSource } from "./Data/DocumentDataSource"; import { IToolboxDragListener, IToolboxClickListener } from "./Render/Toolbox/Toolbox"; import { DiagramSettings, IShapeSizeSettings } from "./Settings"; import { ModelOperationSettings, DiagramModelOperation, IModelOperationSettings } from "./ModelOperationSettings"; import { ViewController } from "./ViewController"; import { ICustomShape } from "./Interfaces"; import { ModelUtils } from "./Model/ModelUtils"; import { ToolboxManager } from "./Render/Toolbox/ToolboxManager"; import { EventDispatcher, GeometryUtils } from "./Utils"; import { IContextMenuVisibilityChangesListener } from "./Events/ContextMenu/ContextMenuHandler"; import { INativeActionListener } from "./Api/INativeActionListener"; import { ApiController } from "./Api/ApiController"; import { IContextToolboxVisibilityChangesListener } from "./Events/ContextToolboxHandler"; import { IScrollView } from "./Render/ScrollView"; import { IShapeIconToolboxOptions } from "./Render/Toolbox/IconToolbox"; import { ITextInputOperationListener } from "./Events/Event"; import { IBar } from "./UI/IBar"; import { IShapeDescriptionChangesListener, ShapeDescription } from "./Model/Shapes/Descriptions/ShapeDescription"; import { IImageCacheChangesListener, ImageCache } from "./Images/ImageCache"; import { ITextMeasurer } from "./Render/Measurer/ITextMeasurer"; import { IPermissionRequestListener, RequestOperationEventArgs, PermissionsProvider } from "./Model/Permissions/PermissionsProvider"; import { ConnectorRoutingModel } from "./Model/Connectors/Routing/ConnectorRoutingModel"; import { TextMeasurer } from "./Render/Measurer/TextMeasurer"; import { Connector, ConnectorPosition } from "./Model/Connectors/Connector"; import { Shape } from "./Model/Shapes/Shape"; import { DataLayoutParameters } from "./Data/DataLayoutParameters"; import { ReloadContentParameters } from "./ReloadContentParameters"; import { INativeItem } from "./Api/INativeItem"; import { RenderUtils } from "./Render/Utils"; import { ChangeConnectionEventArgs } from "./Model/Permissions/Entities/ChangeConnection"; import { Browser } from "."; export class DiagramControl implements IHistoryChangesListener, IDataChangesListener, IToolboxDragListener, ITextInputOperationListener, IContextMenuVisibilityChangesListener, IContextToolboxVisibilityChangesListener, IToolboxClickListener, IShapeDescriptionChangesListener, IImageCacheChangesListener, IPermissionRequestListener { model: DiagramModel; routingModel : ConnectorRoutingModel; modelManipulator: ModelManipulator; shapeDescriptionManager: ShapeDescriptionManager; render: RenderManager; history: History; commandManager: CommandManager; selection: Selection; eventManager: EventManager; barManager: BarManager; settings: DiagramSettings; view: ViewController; toolboxManager: ToolboxManager; permissionsProvider: PermissionsProvider; measurer: ITextMeasurer; private lockedReadOnly; private contextToolbox; private contextToolboxOnClick; apiController: ApiController; onNativeAction: EventDispatcher<INativeActionListener>; onChanged: () => void; onEdgeInserted: (data: any, callback?: (data: any) => void, errorCallback?: (error: any) => void) => void; onEdgeUpdated: (key: ItemKey, data: any, callback?: (key: ItemKey, data: any) => void, errorCallback?: (error: any) => void) => void; onEdgeRemoved: (key: ItemKey, data: any, callback?: (key: ItemKey, data: any) => void, errorCallback?: (error: any) => void) => void; onNodeInserted: (data: any, callback?: (data: any) => void, errorCallback?: (error: any) => void) => void; onNodeUpdated: (key: ItemKey, data: any, callback?: (key: ItemKey, data: any) => void, errorCallback?: (error: any) => void) => void; onNodeRemoved: (key: ItemKey, data: any, callback?: (key: ItemKey, data: any) => void, errorCallback?: (error: any) => void) => void; onToolboxDragStart: () => void; onToolboxDragEnd: () => void; onTextInputStart: () => void; onTextInputEnd: () => void; onToggleFullscreen: (value: boolean) => void; onShowContextMenu: (eventX: number, eventY: number, selection: { x: number, y: number, width: number, height: number}) => void; onHideContextMenu: () => void; onShowContextToolbox: (x: number, y: number, side: ConnectionPointSide, category: string, callback: (shapeType: string) => void) => void; onHideContextToolbox: () => void; onRequestOperation: (operation: DiagramModelOperation, args: RequestOperationEventArgs) => void; contextMenuPosition: Point | undefined; documentDataSource: DocumentDataSource; private updateLockCount: number = 0; private shouldUpdateItemsByModel = false; private reloadContentNeeded = false; private reloadContentParameters = new ReloadContentParameters(); private reloadContentByExternalChangesParameters = new ReloadContentParameters(); constructor() { this.settings = new DiagramSettings(); this.shapeDescriptionManager = new ShapeDescriptionManager(); this.shapeDescriptionManager.onShapeDecriptionChanged.add(this); this.model = new DiagramModel(); this.selection = new Selection(this.model); this.onNativeAction = new EventDispatcher(); this.apiController = new ApiController(this.onNativeAction, this.selection, this.model); this.permissionsProvider = new PermissionsProvider(this.apiController); this.permissionsProvider.onRequestOperation.add(this); this.routingModel = new ConnectorRoutingModel(); this.modelManipulator = new ModelManipulator(this.model, this.routingModel, this.permissionsProvider); this.modelManipulator.onModelChanged.add(this.permissionsProvider); this.history = new History(this.modelManipulator); this.barManager = new BarManager(this); this.view = new ViewController(this.settings, this.barManager); this.commandManager = new CommandManager(this); this.eventManager = new EventManager(this); this.settings.onReadOnlyChanged.add(this.eventManager.mouseHandler); this.settings.onReadOnlyChanged.add(this.eventManager.visualizersManager); this.selection.onChanged.add(this.barManager); this.selection.onChanged.add(this.apiController); this.selection.onChanged.add(this.permissionsProvider); this.modelManipulator.commitItemsCreateChanges(); this.history.onChanged.add(this); this.toolboxManager = new ToolboxManager(this.shapeDescriptionManager); this.settings.onConnectorRoutingModeChanged.add(this.routingModel); ImageCache.instance.onReadyStateChanged.add(this); } get operationSettings() :ModelOperationSettings { return this.permissionsProvider.operationSettings; } cleanMarkup(removeElement?: (element: HTMLElement) => void) { removeElement = removeElement || ((element: HTMLElement) => { RenderUtils.removeElement(element); }); this.toolboxManager.clean(removeElement); this.barManager.clean(); if(this.render) { this.settings.onZoomChanged.remove(this.render.view); this.settings.onViewChanged.remove(this.render.page); this.settings.onViewChanged.remove(this.render.view); this.settings.onReadOnlyChanged.remove(this.render); this.settings.onReadOnlyChanged.remove(this.render.selection); this.eventManager.cleanToolboxes(this.settings.onReadOnlyChanged); this.eventManager.onTextInputOperation.remove(this.render.input); this.eventManager.onTextInputOperation.remove(this.render.items); this.eventManager.onTextInputOperation.remove(this.render.selection); this.eventManager.onMouseOperation.remove(this.render.items); this.eventManager.onMouseOperation.remove(this.render.selection); this.eventManager.onMouseOperation.remove(this.render.view); this.eventManager.onMouseOperation.remove(this.render); this.eventManager.onVisualizersUpdate.remove(this.render.selection); this.modelManipulator.onModelSizeChanged.remove(this.render.view); this.modelManipulator.onModelSizeChanged.remove(this.render.page); this.modelManipulator.onModelChanged.remove(this.render.items); this.modelManipulator.onModelChanged.remove(this.render.page); this.modelManipulator.onModelChanged.remove(this.render.selection); this.selection.onChanged.remove(this.render.selection); this.selection.onChanged.remove(this.render.items); this.render.clean(removeElement); this.render = undefined; } } createDocument(parent: HTMLElement, scrollView?: IScrollView, focusElementsParent?: HTMLElement) { if(!this.measurer) this.initMeasurer(parent); if(this.render) this.render.replaceParent(parent, scrollView); else { this.render = new RenderManager(parent, this.eventManager, this.measurer, { pageColor: this.model.pageColor, modelSize: this.model.size, pageLandscape: this.model.pageLandscape, pageSize: this.model.pageSize, simpleView: this.settings.simpleView, readOnly: this.settings.readOnly, contextMenuEnabled: this.settings.contextMenuEnabled, gridSize: this.settings.gridSize, gridVisible: this.settings.showGrid, zoomLevel: this.settings.zoomLevel, autoZoom: this.settings.autoZoom, rectangle: this.model.getRectangle(true) }, scrollView, focusElementsParent); this.settings.onZoomChanged.add(this.render.view); this.settings.onViewChanged.add(this.render.page); this.settings.onViewChanged.add(this.render.view); this.settings.onReadOnlyChanged.add(this.render); this.settings.onReadOnlyChanged.add(this.render.selection); this.eventManager.onTextInputOperation.add(this.render.input); this.eventManager.onTextInputOperation.add(this.render.items); this.eventManager.onTextInputOperation.add(this.render.selection); this.eventManager.onTextInputOperation.add(this); this.eventManager.onMouseOperation.add(this.render.items); this.eventManager.onMouseOperation.add(this.render.selection); this.eventManager.onMouseOperation.add(this.render.view); this.eventManager.onMouseOperation.add(this.render); this.eventManager.onVisualizersUpdate.add(this.render.selection); this.modelManipulator.onModelSizeChanged.add(this.render.view); this.modelManipulator.onModelSizeChanged.add(this.render.page); this.modelManipulator.onModelChanged.add(this.render.items); this.modelManipulator.onModelChanged.add(this.render.page); this.modelManipulator.onModelChanged.add(this.render.selection); this.selection.onChanged.add(this.render.selection); this.selection.onChanged.add(this.render.items); this.render.update(false); this.render.onNewModel(this.model.items); this.modelManipulator.commitItemsCreateChanges(); this.view.initialize(this.render.view); if(this.settings.zoomLevelWasChanged) this.raiseCanvasViewActualZoomChanged(); this.selection.raiseSelectionChanged(); } } createToolbox(parent: HTMLElement, renderAsText: boolean, shapes: string | string[], options?: IShapeIconToolboxOptions) { const toolbox = this.toolboxManager.create(parent, this.settings.readOnly, true, renderAsText, shapes, this.getToolboxAllowedShapeTypes.bind(this), options); this.settings.onReadOnlyChanged.add(toolbox); toolbox.onDragOperation.add(this); toolbox.onDragOperation.add(this.apiController); this.eventManager.registerToolbox(toolbox); } createContextToolbox(parent: HTMLElement, renderAsText: boolean, shapes: string | string[], options?: IShapeIconToolboxOptions, onClick?: (shapeType: string) => void) { if(this.contextToolbox) this.cleanContextToolbox(parent); this.contextToolbox = this.toolboxManager.create(parent, this.settings.readOnly, false, renderAsText, shapes, this.getToolboxAllowedShapeTypes.bind(this), options); this.contextToolbox.onClickOperation.add(this); this.contextToolboxOnClick = onClick; } private getToolboxAllowedShapeTypes(shapeTypes: string[]): string[] { const allowedShapeTypes = []; this.permissionsProvider.beginUpdateUI(); shapeTypes.forEach(shapeType => { if(this.permissionsProvider.canAddShapeFromToolbox(shapeType)) allowedShapeTypes.push(shapeType); }); this.permissionsProvider.endUpdateUI(); return allowedShapeTypes; } cleanContextToolbox(parent: HTMLElement) { if(this.contextToolbox) { this.toolboxManager.clean(undefined, this.contextToolbox); this.contextToolbox = undefined; } } refreshToolbox(toolboxes?: number[]): void { this.permissionsProvider.clearCache(DiagramModelOperation.AddShapeFromToolbox); this.toolboxManager.refresh(toolboxes); } applyToolboxFilter(shapeSubstring: string, toolboxes?: number[]): number[] { return this.toolboxManager.applyFilter(shapeSubstring, toolboxes); } notifyToolboxClick(shapeType: string) { if(this.contextToolboxOnClick) this.contextToolboxOnClick(shapeType); } initMeasurer(parent: HTMLElement) { this.measurer = new TextMeasurer(parent); } onDimensionChanged() : void { if(!Browser.TouchUI) this.updateLayout(true); } updateLayout(resetScroll = false) { this.render && this.render.update(!resetScroll); } captureFocus() { this.render && this.render.input.captureFocus(); } isFocused() { return !this.render || this.render.input.isFocused(); } registerBar(bar: IBar) { this.barManager.registerBar(bar); } updateBarItemsState(bar: IBar, queryCommands?: DiagramCommand[]) { this.barManager.updateBarItemsState(bar, queryCommands); } getCommand(key: DiagramCommand) { return this.commandManager.getCommand(key); } getNativeItemByKey(key: ItemKey): INativeItem { const item = this.model.findItem(key); return item && this.apiController.createNativeItem(item); } getNativeItemByDataKey(dataKey: ItemDataKey): INativeItem { const item = this.model.findItemByDataKey(dataKey); return item && this.apiController.createNativeItem(item); } getNativeItems(): INativeItem[] { return this.model.items.map(item => this.apiController.createNativeItem(item)); } getNativeSelectedItems(): INativeItem[] { return this.selection.getKeys().map(key => this.apiController.createNativeItem(this.model.findItem(key))); } setSelectedItems(keys: ItemKey[]) { this.selection.set(keys); } scrollToItems(keys: ItemKey[]) { const rectangle = GeometryUtils.getCommonRectangle(keys.map(key => this.model.findItem(key).rectangle)); this.view.scrollIntoView(rectangle); } setInitialStyleProperties(style: any) { this.selection.inputPosition.setInitialStyleProperties(style); } setInitialTextStyleProperties(style: any) { this.selection.inputPosition.setInitialTextStyleProperties(style); } setInitialConnectorProperties(properties: any) { this.selection.inputPosition.setInitialConnectorProperties(properties); } addCustomShapes(shapes: ICustomShape[]) { shapes.forEach(shape => { shape.apiController = this.apiController; if(shape.defaultWidth) shape.defaultWidth = ModelUtils.getTwipsValue(this.model.units, shape.defaultWidth); if(shape.defaultHeight) shape.defaultHeight = ModelUtils.getTwipsValue(this.model.units, shape.defaultHeight); if(shape.minWidth) shape.minWidth = ModelUtils.getTwipsValue(this.model.units, shape.minWidth); if(shape.minHeight) shape.minHeight = ModelUtils.getTwipsValue(this.model.units, shape.minHeight); if(shape.maxWidth) shape.maxWidth = ModelUtils.getTwipsValue(this.model.units, shape.maxWidth); if(shape.maxHeight) shape.maxHeight = ModelUtils.getTwipsValue(this.model.units, shape.maxHeight); this.shapeDescriptionManager.registerCustomShape(shape); }); } removeCustomShapes(shapeTypes: string[]) { shapeTypes.forEach(shapeType => { this.shapeDescriptionManager.unregisterCustomShape(shapeType); }); } removeAllCustomShapes() { this.shapeDescriptionManager.unregisterAllCustomShapes(); } importModel(model: DiagramModel) { model.units = this.model.units; this.model = model; this.model.initializeKeyCounter(); this.apiController.model = model; this.onImportData(); } importItemsData() { this.model.iterateItems(item => { if(item instanceof Connector) item.invalidateRenderPoints(); }); this.onImportData(); } protected onImportData() { if(this.render) { this.render.clear(); this.render.onNewModel(this.model.items); } this.permissionsProvider.clearCache(); this.selection.initialize(this.model); this.modelManipulator.initialize(this.model, this.routingModel); this.history.clear(); this.eventManager.initialize(); this.modelManipulator.commitPageChanges(); this.modelManipulator.commitItemsCreateChanges(); this.notifyViewChanged(); this.notifyHistoryChanged(); } createDocumentDataSource(nodeDataSource: any[], edgeDataSource: any[], parameters?: IDataImportParameters, nodeDataImporter?: INodeDataImporter, edgeDataImporter?: IEdgeDataImporter): DocumentDataSource { this.documentDataSource = new DocumentDataSource(this, nodeDataSource, edgeDataSource, parameters, nodeDataImporter, edgeDataImporter); this.apiController.setDataSource(this.documentDataSource); return this.documentDataSource; } deleteDocumentDataSource() { this.apiController.setDataSource(null); delete this.documentDataSource; } applyShapeSizeSettings(settings: IShapeSizeSettings): void { this.settings.applyShapeSizeSettings(settings, this.model.units); } applyOperationSettings(settings: IModelOperationSettings): void { this.permissionsProvider.operationSettings.applySettings(settings); } beginUpdateCanvas(): void { if(this.render) { this.render.items.beginUpdate(); this.render.selection.beginUpdate(); } } endUpdateCanvas(): void { if(this.render) { this.render.items.endUpdate(); this.render.selection.endUpdate(); } } beginUpdate() { this.barManager.beginUpdate(); this.apiController.beginUpdate(); this.eventManager.beginUpdate(); } endUpdate() { this.barManager.endUpdate(); this.apiController.endUpdate(); this.eventManager.endUpdate(); } notifyEdgeInserted(data: any, callback?: (data: any) => void, errorCallback?: (error: any) => void) { if(this.onEdgeInserted) this.onEdgeInserted(data, callback, errorCallback); else callback(data); } notifyEdgeUpdated(key: ItemKey, data: any, callback?: (key: ItemKey, data: any) => void, errorCallback?: (error: any) => void) { if(this.onEdgeUpdated) this.onEdgeUpdated(key, data, callback, errorCallback); else callback(key, data); } notifyEdgeRemoved(key: ItemKey, data: any, callback?: (key: ItemKey, data: any) => void, errorCallback?: (error: any) => void) { if(this.onEdgeUpdated) this.onEdgeRemoved(key, data, callback, errorCallback); else callback(key, data); } notifyNodeInserted(data: any, callback?: (data: any) => void, errorCallback?: (error: any) => void) { if(this.onNodeInserted) this.onNodeInserted(data, callback, errorCallback); else callback(data); } notifyNodeUpdated(key: ItemKey, data: any, callback?: (key: ItemKey, data: any) => void, errorCallback?: (error: any) => void) { if(this.onNodeUpdated) this.onNodeUpdated(key, data, callback, errorCallback); else callback(key, data); } notifyNodeRemoved(key: ItemKey, data: any, callback?: (key: ItemKey, data: any) => void, errorCallback?: (error: any) => void) { if(this.onNodeRemoved) this.onNodeRemoved(key, data, callback, errorCallback); else callback(key, data); } reloadInsertedItem(dataKey: ItemDataKey) { if(this.settings.reloadInsertedItemRequired) this.reloadContent(dataKey); } reloadContent(dataKey?: ItemDataKey|ItemDataKey[], getData?: () => { nodeDataSource?: any[], edgeDataSource?: any[] }, layoutParameters?: IDataLayoutImportParameters, isExternalChanges?: boolean): void { if(!this.documentDataSource) return; if(this.isChangesLocked()) this.reloadContentNeeded = true; const addNewHistoryItem = isExternalChanges === true || (isExternalChanges === undefined && !this.reloadContentNeeded); const reloadContentParameters = addNewHistoryItem ? this.reloadContentByExternalChangesParameters : this.reloadContentParameters; reloadContentParameters.add(dataKey, getData, layoutParameters); if(!this.isChangesLocked()) { this.reloadContentCore(reloadContentParameters, addNewHistoryItem); this.barManager.updateItemsState(); } } private reloadContentCore(parameters: ReloadContentParameters, addNewHistoryItem?: boolean): void { const data = parameters.getData && parameters.getData(); const changes = this.documentDataSource.refetchData(data && data.nodeDataSource, data && data.edgeDataSource); this.beginUpdateCanvas(); this.permissionsProvider.lockPermissions(); this.documentDataSource.updateModelItems(this.history, this.model, this.shapeDescriptionManager, this.selection, new DataLayoutParameters(this.settings, parameters.layoutParameters), addNewHistoryItem, parameters.dataKeys, (item) => { this.modelManipulator.commitItemUpdateChanges(item); }, changes, this.settings.snapToGrid, this.settings.gridSize, this.measurer ); this.permissionsProvider.unlockPermissions(); this.endUpdateCanvas(); parameters.clear(); } notifyHistoryChanged() { if(this.documentDataSource) { this.shouldUpdateItemsByModel = true; if(!this.settings.readOnly) this.notifyDataChanges(); } else this.raiseOnChanged(); } notifyViewChanged() { this.settings.notifyViewChanged(); } notifyToolboxDragStart(evt: MouseEvent) { this.render.notifyToolboxDragStart(evt); if(this.onToolboxDragStart) this.onToolboxDragStart(); } notifyToolboxDragEnd(evt: MouseEvent) { this.render.notifyToolboxDragEnd(evt); if(this.onToolboxDragEnd) this.onToolboxDragEnd(); } notifyToolboxDraggingMouseMove(evt: MouseEvent) { if(this.render) this.render.notifyToolboxDraggingMouseMove(evt); } notifyTextInputStart(item: DiagramItem, text: string, position: Point, size?: Size): void { if(this.onTextInputStart) this.onTextInputStart(); } notifyTextInputEnd(item: DiagramItem, captureFocus?: boolean): void { if(this.onTextInputEnd) this.onTextInputEnd(); } notifyTextInputPermissionsCheck(item: DiagramItem, allowed: boolean): void {} notifyToggleFullscreen(value: boolean) { if(this.onToggleFullscreen) this.onToggleFullscreen(value); } notifyShowContextMenu(eventPoint: Point, modelPoint: Point) { if(this.onShowContextMenu && this.render) { let selection: any; const selectedItems = this.selection.getSelectedItems(true); if(selectedItems.length > 0) { const rect = ModelUtils.createRectangle(this.selection.getSelectedItems(true)); const pos = this.render.getEventPointByModelPoint(rect.createPosition()); const size = this.render.view.getAbsoluteSize(rect.createSize()); selection = { x: pos.x, y: pos.y, width: size.width, height: size.height }; } if(eventPoint) { this.contextMenuPosition = new Point(eventPoint.x, eventPoint.y); this.onShowContextMenu(eventPoint.x, eventPoint.y, selection); } else if(modelPoint) { const point = this.render.getEventPointByModelPoint(modelPoint); this.contextMenuPosition = point.clone(); this.onShowContextMenu(point.x, point.y, selection); } } } notifyHideContextMenu() { if(this.onHideContextMenu && this.render) this.onHideContextMenu(); } notifyShowContextToolbox(modelPoint: Point, getPositionToInsertShapeTo: (shape: Shape) => Point, side: ConnectionPointSide, category: string, callback: (shapeType: string) => void) { if(this.onShowContextToolbox && this.render) { const point = this.render.getEventPointByModelPoint(modelPoint); this.onShowContextToolbox(point.x, point.y, side, category, callback); this.render.view.notifyShowContextToolbox(); } } notifyHideContextToolbox() { if(this.onHideContextToolbox && this.render) { this.onHideContextToolbox(); this.render.view.notifyHideContextToolbox(); } } notifyShapeDescriptionChanged(description: ShapeDescription) { this.modelManipulator.updateShapeDescription(description); } notifyImageCacheReadyStateChanged(ready: boolean) { this.barManager.updateItemsState(); } raiseCanvasViewActualZoomChanged() { this.render.view.raiseActualZoomChanged(); } notifyRequestOperation(operation: DiagramModelOperation, args: RequestOperationEventArgs) { if(this.requestOperationByDataSource(operation, args)) return; if(this.onRequestOperation) this.onRequestOperation(operation, args); } private requestOperationByDataSource(operation: DiagramModelOperation, args: RequestOperationEventArgs) { if(!(this.documentDataSource && (this.documentDataSource.IsNodeParentIdMode() || this.documentDataSource.IsNodeItemsMode()))) return false; if(operation === DiagramModelOperation.ChangeConnection) { const e = args as ChangeConnectionEventArgs; const shape = e.shape && this.model.findItem(e.shape.id); const connector = e.connector && <Connector> this.model.findItem(e.connector.id); if(!(shape && connector)) return; if(e.position === ConnectorPosition.End) for(let i = 0; i < shape.attachedConnectors.length; i++) { const attachedConnector = shape.attachedConnectors[i]; if(attachedConnector !== connector && attachedConnector.endItem && attachedConnector.endItem === shape) { e.allowed = false; break; } } if(e.allowed && connector.beginItem && connector.endItem && this.isShapeParent(connector.endItem, connector.beginItem)) e.allowed = false; } return !args.allowed; } private isShapeParent(parentShape: DiagramItem, shape: DiagramItem) { if(parentShape === shape) return true; for(let i = 0; i < parentShape.attachedConnectors.length; i++) { const attachedConnector = parentShape.attachedConnectors[i]; if(attachedConnector.beginItem === parentShape && attachedConnector.endItem) { const childShape = attachedConnector.endItem; if(childShape === shape || this.isShapeParent(childShape, shape)) return true; } } return false; } isChangesLocked(): boolean { return this.updateLockCount > 0; } beginChangesNotification() { if(!this.isChangesLocked()) if(this.changesLockChanged) this.changesLockChanged(true); this.updateLockCount++; } endChangesNotification(preventNotifyReloadContent : boolean) { this.updateLockCount--; if(!this.isChangesLocked()) { this.changesLockChanged(false); if(!preventNotifyReloadContent) setTimeout(() => { this.notifyReloadContent(); this.notifyDataChanges(); }, 0); } } private changesLockChanged(locked: boolean) : void { if(locked) this.lockedReadOnly = this.settings.readOnly; else locked = this.lockedReadOnly; this.commandManager.getCommand(DiagramCommand.ToggleReadOnly).execute(locked); } private notifyDataChanges() { if(this.isChangesLocked()) return; if(this.shouldUpdateItemsByModel) { this.documentDataSource.updateItemsByModel(this.model); this.shouldUpdateItemsByModel = false; } this.raiseOnChanged(); } private notifyReloadContent(): void { if(this.reloadContentNeeded) { if(!this.reloadContentParameters.empty) this.reloadContentCore(this.reloadContentParameters, false); if(!this.reloadContentByExternalChangesParameters.empty) this.reloadContentCore(this.reloadContentByExternalChangesParameters, true); this.reloadContentNeeded = false; } } private raiseOnChanged() { if(this.onChanged) this.onChanged(); } }