devexpress-diagram
Version:
DevExpress Diagram Control
295 lines (283 loc) • 14.3 kB
text/typescript
import { Point } from "@devexpress/utils/lib/geometry/point";
import { Vector } from "@devexpress/utils/lib/geometry/vector";
import { ChangeConnectorPointsHistoryItem } from "../../History/Common/ChangeConnectorPointsHistoryItem";
import { History } from "../../History/History";
import { Connector } from "../../Model/Connectors/Connector";
import { ConnectorRenderPoint } from "../../Model/Connectors/ConnectorRenderPoint";
import { ConnectorRenderPointsContext } from "../../Model/Connectors/Routing/ConnectorRenderPointsContext";
import { DiagramItem, ItemKey } from "../../Model/DiagramItem";
import { DiagramModel } from "../../Model/Model";
import { ModelUtils } from "../../Model/ModelUtils";
import { Shape } from "../../Model/Shapes/Shape";
import { DiagramModelOperation } from "../../ModelOperationSettings";
import { Selection } from "../../Selection/Selection";
import { DiagramMouseEvent, MouseButton, MouseEventElementType } from "../Event";
import { MouseHandler } from "../MouseHandler";
import { IVisualizerManager } from "../Visualizers/VisualizersManager";
import { MouseHandlerDraggingState } from "./MouseHandlerDraggingState";
export class DraggingConnector {
readonly startPoints: Point[];
readonly startRenderContext: ConnectorRenderPointsContext;
constructor(readonly connector: Connector) {
this.startPoints = connector.points.map(x => x.clone());
this.startRenderContext = connector.createRenderPointsContext();
}
}
class DraggingShape {
readonly startPosition: Point;
constructor(public readonly shape: Shape) {
this.startPosition = shape.position.clone();
}
}
export abstract class MouseHandlerDragDiagramItemStateBase extends MouseHandlerDraggingState {
private fixedX: boolean;
private fixedY: boolean;
private lockInitDrag: boolean;
startPoint: Point;
selectedItems : { [key: string] : DiagramItem }
draggingShapes: DraggingShape[];
draggingConnectors: DraggingConnector[];
draggingConnectorsIndexByKey: { [key: string]: number };
modelConnectorsWithoutBeginItemInfo: { connector: Connector, point: Point }[];
modelConnectorsWithoutEndItemInfo: { connector: Connector, point: Point }[];
startScrollLeft = 0;
startScrollTop = 0;
shouldClone: boolean;
protected constructor(handler: MouseHandler, history: History,
protected model: DiagramModel,
protected selection: Selection,
protected visualizerManager: IVisualizerManager) {
super(handler, history);
}
protected abstract get areValidDraggingShapes(): boolean;
protected abstract get areValidDraggingConnectors(): boolean;
finish(): void {
this.visualizerManager.resetExtensionLines();
this.visualizerManager.resetContainerTarget();
this.visualizerManager.resetConnectionTarget();
this.visualizerManager.resetConnectionPoints();
super.finish();
}
onMouseDown(evt: DiagramMouseEvent): void {
this.handler.addDiagramItemToSelection(evt);
this.shouldClone = this.handler.canCopySelectedItems(evt);
this.startPoint = evt.modelPoint;
this.initDrag();
this.lockInitDrag = false;
if(!this.shouldClone)
this.draggingShapes.forEach(draggingShape => this.handler.addInteractingItem(draggingShape.shape, DiagramModelOperation.MoveShape));
super.onMouseDown(evt);
}
onMouseMove(evt: DiagramMouseEvent): void {
this.mouseMoveEvent = evt;
if(evt.button !== MouseButton.Left) {
this.cancelChanges();
this.handler.switchToDefaultState();
}
if(!this.canApplyChangesOnMouseMove(this.startPoint, evt.modelPoint))
return;
if(this.handler.canCopySelectedItems(evt))
if(!this.lockInitDrag) {
this.cancelChanges();
this.shouldClone = true;
this.copySelection();
this.initDrag();
this.lockInitDrag = true;
}
this.onApplyChanges(evt);
this.onAfterApplyChanges();
this.updateContainers(evt);
}
private updateContainers(evt: DiagramMouseEvent) : void {
this.visualizerManager.setExtensionLines(this.selection.getSelectedShapes(false, true));
const container = ModelUtils.findContainerByEventKey(this.model, this.selection, evt.source.key);
if(container && this.allowInsertToContainer(evt, container))
this.visualizerManager.setContainerTarget(container, evt.source.type);
else
this.visualizerManager.resetContainerTarget();
}
onMouseUp(evt: DiagramMouseEvent) : void {
super.onMouseUp(evt);
if(this.handler.canRemoveDiagramItemToSelection(evt) && this.handler.canMultipleSelection(evt))
this.handler.removeDiagramItemFromSelection(evt.button, evt.source.key);
}
onApplyChanges(evt: DiagramMouseEvent): void {
this.calculateFixedPosition(evt);
if(this.draggingShapes.length) {
const selectedShapes = this.draggingShapes.map(ds => ds.shape);
this.draggingShapes.forEach(ds => {
let shape = ds.shape;
while(shape.container) {
if(selectedShapes.indexOf(shape.container) !== -1) return false;
shape = shape.container;
}
this.moveShape(ds, evt);
});
const firstDraggingShape = this.draggingShapes[0];
const offset = Vector.fromPoints(firstDraggingShape.startPosition.clone(), firstDraggingShape.shape.position.clone());
if(offset.x || offset.y)
this.draggingConnectors.forEach(dc => this.moveConnectorCore(dc.connector, dc.startPoints, dc.startRenderContext, offset));
}
else
this.draggingConnectors.forEach(x => this.moveConnector(x, evt));
const container = ModelUtils.findContainerByEventKey(this.model, this.selection, evt.source.key);
if(container && this.allowInsertToContainer(evt, container))
ModelUtils.insertSelectionToContainer(this.history, this.model, this.selection, container);
else
ModelUtils.removeSelectionFromContainer(this.history, this.model, this.selection);
this.handler.tryUpdateModelSize((offsetLeft, offsetTop) => {
this.modelConnectorsWithoutBeginItemInfo.forEach(pi => {
pi.point.x += offsetLeft;
pi.point.y += offsetTop;
});
this.modelConnectorsWithoutEndItemInfo.forEach(pi => {
pi.point.x += offsetLeft;
pi.point.y += offsetTop;
});
});
}
getDraggingElementKeys(): ItemKey[] {
return this.draggingShapes.map(x => x.shape.key).concat(this.draggingConnectors.map(x => x.connector.key));
}
getSnappedPoint(evt: DiagramMouseEvent, point: Point): Point {
return this.handler.getSnappedPointOnDragDiagramItem(evt, point, this.fixedX, this.fixedY, this.startPoint);
}
private initDrag() : void {
this.selectedItems = ModelUtils.createSelectedItems(this.selection);
this.initDraggingShapes();
if(!this.areValidDraggingShapes) {
this.handler.switchToDefaultState();
return;
}
this.initDraggingConnectors();
if(!this.areValidDraggingConnectors) {
this.handler.switchToDefaultState();
return;
}
this.modelConnectorsWithoutBeginItemInfo = this.createModelConnectorsWithoutBeginItemInfo();
this.modelConnectorsWithoutEndItemInfo = this.createModelConnectorsWithoutEndItemInfo();
}
private initDraggingShapes() {
this.draggingShapes = this.selection.getSelectedShapes(false, true).map(s => new DraggingShape(s));
}
private initDraggingConnectors(): void {
this.draggingConnectors = [];
this.draggingConnectorsIndexByKey = {};
this.selection.getSelectedConnectors(false, true).forEach(c => this.registerConnector(c));
if(this.shouldClone)
return;
this.draggingShapes.forEach(x => {
const attachedConnectors = x.shape.attachedConnectors;
if(attachedConnectors)
attachedConnectors.forEach(c => {
if(!this.containsDraggingConnectorByKey(c.key))
this.registerConnector(c);
});
});
}
private copySelection() : void {
ModelUtils.cloneSelectionToOffset(this.history, this.model, (key: ItemKey) => {
const item = this.model.findItem(key);
if(item)
this.handler.addInteractingItem(item, DiagramModelOperation.AddShape);
}, this.selection, 0, 0);
}
private calculateFixedPosition(evt: DiagramMouseEvent) {
this.fixedX = false;
this.fixedY = false;
if(this.handler.canCalculateFixedPosition(evt)) {
const dx = Math.abs(this.startPoint.x - evt.modelPoint.x);
const dy = Math.abs(this.startPoint.y - evt.modelPoint.y);
if(dx < dy)
this.fixedX = true;
else
this.fixedY = true;
}
}
private containsDraggingConnectorByKey(key: string): boolean {
return this.draggingConnectorsIndexByKey[key] !== undefined;
}
private allowInsertToContainer(evt: DiagramMouseEvent, container: Shape): boolean {
if(this.handler.canMultipleSelection(evt))
return false;
return container && container.expanded && ModelUtils.canInsertSelectionToContainer(this.model, this.selection, container);
}
private registerConnector(connector: Connector) {
this.draggingConnectorsIndexByKey[connector.key] = this.draggingConnectors.push(new DraggingConnector(connector)) - 1;
}
private createModelConnectorsWithoutBeginItemInfo(): { connector: Connector, point: Point }[] {
const connectors = this.model.findConnectorsCore(c => !c.beginItem && !this.containsDraggingConnectorByKey(c.key));
return connectors.map(c => {
return {
connector: c,
point: c.points[0].clone()
};
});
}
private createModelConnectorsWithoutEndItemInfo(): { connector: Connector, point: Point }[] {
const connectors = this.model.findConnectorsCore(c => !c.endItem && !this.containsDraggingConnectorByKey(c.key));
return connectors.map(c => {
return {
connector: c,
point: c.points[c.points.length - 1].clone()
};
});
}
private moveConnector(dc: DraggingConnector, evt: DiagramMouseEvent) {
const startPoints = dc.startPoints;
const offset = Vector.fromPoints(startPoints[0].clone(), this.getSnappedPoint(evt, startPoints[0]).clone());
if(offset.x || offset.y)
this.moveConnectorCore(dc.connector, startPoints, dc.startRenderContext, offset);
}
private moveConnectorCore(connector: Connector, startPoints: Point[], startRenderContext: ConnectorRenderPointsContext, offset: Vector) : void {
if(this.shouldClone || ModelUtils.canMoveConnector(this.selectedItems, connector))
this.offsetConnector(connector, startPoints, startRenderContext, offset);
else
this.changeConnector(connector);
}
private moveShape(ds: DraggingShape, evt: DiagramMouseEvent) {
const shape = ds.shape;
const position = this.getSnappedPoint(evt, ds.startPosition);
ModelUtils.setShapePosition(this.history, this.model, shape, position);
ModelUtils.updateMovingShapeConnections(this.history, shape,
this.modelConnectorsWithoutBeginItemInfo, this.modelConnectorsWithoutEndItemInfo,
() => {
this.visualizerManager.resetConnectionTarget();
this.visualizerManager.resetConnectionPoints();
},
(shape, connectionPointIndex) => {
this.visualizerManager.setConnectionTarget(shape, MouseEventElementType.Shape);
this.visualizerManager.setConnectionPoints(shape, MouseEventElementType.Shape, connectionPointIndex, true);
},
(connector) => this.handler.addInteractingItem(connector)
);
if(!this.draggingConnectors.filter(dc => !!this.selectedItems[dc.connector.key]).length)
ModelUtils.updateShapeAttachedConnectors(this.history, this.model, shape);
}
private offsetConnector(connector: Connector, startPoints: Point[], startRenderContext: ConnectorRenderPointsContext, offset: Vector) {
const newPoints = startPoints.map(p => this.offsetPoint(p, offset));
if(!newPoints[0].equals(connector.points[0]))
this.history.addAndRedo(
new ChangeConnectorPointsHistoryItem(
connector.key, newPoints,
this.offsetRenderContext(startRenderContext, offset)
)
);
}
private offsetRenderContext(context: ConnectorRenderPointsContext, offset: Vector) : ConnectorRenderPointsContext {
if(context === undefined)
return undefined;
return new ConnectorRenderPointsContext(context.renderPoints.map(p => {
const newPoint = this.offsetPoint(p, offset);
return new ConnectorRenderPoint(newPoint.x, newPoint.y, p.pointIndex, p.skipped);
}), true, context.actualRoutingMode);
}
private offsetPoint(point: Point, offset: Vector): Point {
const pointOffset = Vector.fromPoints(point, this.startPoint);
return this.startPoint.clone().offset(offset.x - pointOffset.x, offset.y - pointOffset.y);
}
private changeConnector(connector: Connector) {
ModelUtils.removeConnectorIntermediatePoints(this.history, connector);
ModelUtils.updateConnectorAttachedPoints(this.history, this.model, connector);
}
}