devexpress-diagram
Version:
DevExpress Diagram Control
704 lines (640 loc) • 32.7 kB
text/typescript
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();
}
}