From 2817d55e2cd91b57e5e6ba41c48dc57cd62e19b3 Mon Sep 17 00:00:00 2001 From: Gabriel Dulca Date: Wed, 18 Sep 2019 16:02:51 +0200 Subject: [PATCH] Manhattan routing improved to avoid crossing through diagram elements. --- .../workflow-sprotty/src/di.config.ts | 4 +- .../src/features/change-bounds/edges.ts | 34 ++ .../src/features/tools/change-bounds-tool.ts | 442 +++++++++++++++++- .../model-source/websocket-diagram-server.ts | 9 +- .../eclipsesource/glsp/api/action/Action.java | 3 +- .../api/action/kind/SaveModelEdgesAction.java | 38 ++ .../api/model/ElementAndRoutingPoints.java | 64 +++ .../actionhandler/SaveModelEdgesHandler.java | 47 ++ .../glsp/server/di/DefaultGLSPModule.java | 3 +- .../provider/DefaultActionProvider.java | 8 +- 10 files changed, 630 insertions(+), 22 deletions(-) create mode 100644 client/packages/sprotty-client/src/features/change-bounds/edges.ts create mode 100644 server/glsp-api/src/main/java/com/eclipsesource/glsp/api/action/kind/SaveModelEdgesAction.java create mode 100644 server/glsp-api/src/main/java/com/eclipsesource/glsp/api/model/ElementAndRoutingPoints.java create mode 100644 server/glsp-server/src/main/java/com/eclipsesource/glsp/server/actionhandler/SaveModelEdgesHandler.java diff --git a/client/examples/workflow/workflow-sprotty/src/di.config.ts b/client/examples/workflow/workflow-sprotty/src/di.config.ts index 7d26600f..fcd29334 100644 --- a/client/examples/workflow/workflow-sprotty/src/di.config.ts +++ b/client/examples/workflow/workflow-sprotty/src/di.config.ts @@ -106,10 +106,10 @@ const workflowDiagramModule = new ContainerModule((bind, unbind, isBound, rebind export default function createContainer(widgetId: string): Container { const container = new Container(); - container.load(decorationModule, validationModule, defaultModule, glspMouseToolModule, defaultGLSPModule, glspSelectModule, boundsModule, viewportModule, + container.load(routingModule, decorationModule, validationModule, defaultModule, glspMouseToolModule, defaultGLSPModule, glspSelectModule, boundsModule, viewportModule, hoverModule, fadeModule, exportModule, expandModule, openModule, buttonModule, modelSourceModule, labelEditModule, labelEditUiModule, glspEditLabelValidationModule, workflowDiagramModule, saveModule, executeCommandModule, toolFeedbackModule, modelHintsModule, - commandPaletteModule, glspCommandPaletteModule, paletteModule, requestResponseModule, routingModule, edgeLayoutModule, + commandPaletteModule, glspCommandPaletteModule, paletteModule, requestResponseModule, edgeLayoutModule, layoutCommandsModule); overrideGLSPViewerOptions(container, { diff --git a/client/packages/sprotty-client/src/features/change-bounds/edges.ts b/client/packages/sprotty-client/src/features/change-bounds/edges.ts new file mode 100644 index 00000000..7a75e569 --- /dev/null +++ b/client/packages/sprotty-client/src/features/change-bounds/edges.ts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Action, KeyListener, SModelRoot } from "sprotty/lib"; +import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; + +import { ElementAndRoutingPoints } from "../tools/change-bounds-tool"; + +export class SaveModelEdgesAction implements Action { + static readonly KIND = "saveModelEdges"; + readonly kind = SaveModelEdgesAction.KIND; + constructor(public newRoutingPoints: ElementAndRoutingPoints[]) { } +} + +export class SaveModelKeyboardListener extends KeyListener { + keyDown(element: SModelRoot, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, 'KeyS', 'ctrlCmd')) { + return [new SaveModelEdgesAction([])]; + } + return []; + } +} diff --git a/client/packages/sprotty-client/src/features/tools/change-bounds-tool.ts b/client/packages/sprotty-client/src/features/tools/change-bounds-tool.ts index 15a6d1bc..6c19e3e2 100644 --- a/client/packages/sprotty-client/src/features/tools/change-bounds-tool.ts +++ b/client/packages/sprotty-client/src/features/tools/change-bounds-tool.ts @@ -16,15 +16,27 @@ import { inject, injectable } from "inversify"; import { Action, + almostEquals, Bounds, BoundsAware, + EdgeRouterRegistry, ElementAndBounds, findParentByFeature, + isBoundsAware, + isConnectable, + isMoveable, + isSelectable, isViewport, KeyTool, + ManhattanEdgeRouter, MouseListener, Point, + PointToPointLine, + RoutedPoint, + SEdge, + Selectable, SetBoundsAction, + Side, SModelElement, SModelRoot, SParentElement, @@ -33,16 +45,19 @@ import { import { GLSPViewerOptions } from "../../base/views/viewer-options"; import { GLSP_TYPES } from "../../types"; -import { forEachElement, isNonRoutableSelectedBoundsAware, isSelected } from "../../utils/smodel-util"; +import { forEachElement, isSelected } from "../../utils/smodel-util"; +import { SaveModelEdgesAction } from "../change-bounds/edges"; import { isBoundsAwareMoveable, isResizeable, ResizeHandleLocation, SResizeHandle } from "../change-bounds/model"; import { IMouseTool } from "../mouse-tool/mouse-tool"; import { ChangeBoundsOperationAction } from "../operation/operation-actions"; +import { isRoutable } from "../reconnect/model"; import { SelectionListener, SelectionService } from "../select/selection-service"; import { FeedbackMoveMouseListener, HideChangeBoundsToolResizeFeedbackAction, ShowChangeBoundsToolResizeFeedbackAction } from "../tool-feedback/change-bounds-tool-feedback"; +import { SwitchRoutingModeAction } from "../tool-feedback/edge-edit-tool-feedback"; import { IFeedbackActionDispatcher } from "../tool-feedback/feedback-action-dispatcher"; /** @@ -70,7 +85,10 @@ export class ChangeBoundsTool implements Tool { @inject(GLSP_TYPES.MouseTool) protected mouseTool: IMouseTool, @inject(KeyTool) protected keyTool: KeyTool, @inject(GLSP_TYPES.IFeedbackActionDispatcher) protected feedbackDispatcher: IFeedbackActionDispatcher, - @inject(GLSP_TYPES.ViewerOptions) protected opts: GLSPViewerOptions) { } + @inject(GLSP_TYPES.ViewerOptions) protected opts: GLSPViewerOptions, + @inject(EdgeRouterRegistry) protected edgeRouterRegistry: EdgeRouterRegistry, + @inject(ManhattanEdgeRouter) protected manhattanRouter: ManhattanEdgeRouter) { } + enable() { // install feedback move mouse listener for client-side move updates @@ -78,7 +96,7 @@ export class ChangeBoundsTool implements Tool { this.mouseTool.register(this.feedbackMoveMouseListener); // instlal change bounds listener for client-side resize updates and server-side updates - this.changeBoundsListener = new ChangeBoundsListener(this); + this.changeBoundsListener = new ChangeBoundsListener(this, this.edgeRouterRegistry, this.manhattanRouter); this.mouseTool.register(this.changeBoundsListener); this.selectionService.register(this.changeBoundsListener); this.feedbackDispatcher.registerFeedback(this, [new ShowChangeBoundsToolResizeFeedbackAction]); @@ -105,7 +123,10 @@ class ChangeBoundsListener extends MouseListener implements SelectionListener { private activeResizeElementId: string | undefined = undefined; private activeResizeHandle: SResizeHandle | undefined = undefined; - constructor(protected tool: ChangeBoundsTool) { + // Spacing unit used for rerouting in case of collision + private SPACING: number = 5; + + constructor(protected tool: ChangeBoundsTool, protected edgeRouterRegistry: EdgeRouterRegistry, protected manhattanRouter: ManhattanEdgeRouter) { super(); } @@ -155,16 +176,403 @@ class ChangeBoundsListener extends MouseListener implements SelectionListener { } else { // Bounds... Change Bounds. const newBounds: ElementAndBounds[] = []; - forEachElement(target, isNonRoutableSelectedBoundsAware, element => - createElementAndBounds(element).forEach(bounds => newBounds.push(bounds))); + const newRoutingPoints: ElementAndRoutingPoints[] = []; + + forEachElement(target, isNonRoutableBoundsAware, element => { + + + + createElementAndBounds(element).forEach(bounds => { + // Push new bounds only for selected elements + if (isSelected(element)) { + newBounds.push(bounds); + } + }); + + newRoutingPoints.push.apply(newRoutingPoints, this.getConnectedRoutingPoints(element)); + // Going through the routing points + newRoutingPoints.forEach(elementAndRoutingPoint => { + this.tool.dispatchFeedback([new SwitchRoutingModeAction([elementAndRoutingPoint.elementId])]); + // In case of a straight edge, no routing points + if (elementAndRoutingPoint.routingPoints.length === 0) { + this.checkOverlapBetweenAnchors(target, elementAndRoutingPoint); + } + // Check Overlap between the source and first routing point + this.checkOverlapBetweenSourceAndPoint(target, elementAndRoutingPoint); + // Check Overlap between the target and the last routing point + this.checkOverlapBetweenTargetAndPoint(target, elementAndRoutingPoint); + const nrRoutingPoints = elementAndRoutingPoint.routingPoints.length; + const overlaps: String[] = []; + for (let i = 0; i < nrRoutingPoints - 1; i++) { + // Take a segment between 2 routing points and check it + const firstPoint: Point = elementAndRoutingPoint.routingPoints[i]; + const secondPoint: Point = elementAndRoutingPoint.routingPoints[i + 1]; + target.root.index.all() + .filter(element2 => isSelectable(element2)) + .forEach(element2 => { + if (isMoveable(element2) && isResizeable(element2)) { + const isOverlapping = this.isLineOverlappingElement(firstPoint, secondPoint, element2.bounds); + if (isOverlapping) { + overlaps.push(element2.id); + const closestSideAndDistance = this.getClosestSideAndDistance(firstPoint, secondPoint, element2.bounds); + const shiftedPoints: Point[] = this.getShiftedRoutingPoints(firstPoint, secondPoint, closestSideAndDistance); + elementAndRoutingPoint.routingPoints[i] = shiftedPoints[0]; + elementAndRoutingPoint.routingPoints[i + 1] = shiftedPoints[1]; + } + } + }); + } + + }); + }); if (newBounds.length > 0) { actions.push(new ChangeBoundsOperationAction(newBounds)); } + // Push new routing points + if (newRoutingPoints.length > 0) { + actions.push(new SaveModelEdgesAction(newRoutingPoints)); + } + + } this.resetPosition(); return actions; } + generateNewRoutingPoint(sideAndDistance: SideAndDistance, anchor: Point, routingPoint: Point, bounds: Bounds): Point { + let newPoint: Point; + // Create additional point + if (sideAndDistance.side === Side.BOTTOM || sideAndDistance.side === Side.TOP) { + // Add point left of the bounds + if (routingPoint.x > anchor.x) { + newPoint = { + x: bounds.x - this.SPACING, + y: anchor.y + }; + } else { + // Add point right of the bounds + newPoint = { + x: bounds.x + bounds.width + this.SPACING, + y: anchor.y + }; + } + + } else { + // Add point to the top of bounds + if (anchor.y < routingPoint.y) { + newPoint = { + x: anchor.x, + y: bounds.y - this.SPACING + }; + } else { + // Add point to the bottom of bounds + newPoint = { + x: anchor.x, + y: bounds.y + bounds.height + this.SPACING + }; + } + + } + return newPoint; + } + + checkOverlapBetweenTargetAndPoint(target: SModelElement, elementAndRoutingPoint: ElementAndRoutingPoints) { + const targetAnchor: Point = elementAndRoutingPoint.target; + const routingPoint: Point = elementAndRoutingPoint.routingPoints[elementAndRoutingPoint.routingPoints.length - 1]; + target.root.index.all() + .filter(element => isSelectable(element)) + .forEach(element => { + if (isMoveable(element) && isResizeable(element)) { + const isOverlapping = this.isLineOverlappingElement(targetAnchor, routingPoint, element.bounds); + const edge = target.root.index.getById(elementAndRoutingPoint.elementId); + let edgeSourceId; + let edgeTargetId; + let newPoint: Point; + if (edge instanceof SEdge && isRoutable(edge)) { + edgeSourceId = edge.sourceId; + edgeTargetId = edge.targetId; + } + if (isOverlapping && element.id !== edgeSourceId && element.id !== edgeTargetId) { + const sideAndDistance = this.getClosestSideAndDistance(targetAnchor, routingPoint, element.bounds); + // Create additional point + newPoint = this.generateNewRoutingPoint(sideAndDistance, targetAnchor, routingPoint, element.bounds); + // Shift the old and new point + const shiftedPoints: Point[] = this.getShiftedRoutingPoints(newPoint, routingPoint, sideAndDistance); + elementAndRoutingPoint.routingPoints[elementAndRoutingPoint.routingPoints.length - 1] = shiftedPoints[1]; + elementAndRoutingPoint.routingPoints.push(shiftedPoints[0]); + + } + } + }); + } + + checkOverlapBetweenSourceAndPoint(target: SModelElement, elementAndRoutingPoint: ElementAndRoutingPoints) { + const source: Point = elementAndRoutingPoint.source; + const routingPoint: Point = elementAndRoutingPoint.routingPoints[0]; + target.root.index.all() + .filter(element => isSelectable(element)) + .forEach(element => { + if (isMoveable(element) && isResizeable(element)) { + const isOverlapping = this.isLineOverlappingElement(source, routingPoint, element.bounds); + const edge = target.root.index.getById(elementAndRoutingPoint.elementId); + let edgeSourceId; + let edgeTargetId; + let newPoint: Point; + if (edge instanceof SEdge && isRoutable(edge)) { + edgeSourceId = edge.sourceId; + edgeTargetId = edge.targetId; + } + if (isOverlapping && element.id !== edgeSourceId && element.id !== edgeTargetId) { + const sideAndDistance = this.getClosestSideAndDistance(source, routingPoint, element.bounds); + // Create additional point + newPoint = this.generateNewRoutingPoint(sideAndDistance, source, routingPoint, element.bounds); + // Shift the old and new point + const shiftedPoints: Point[] = this.getShiftedRoutingPoints(newPoint, routingPoint, sideAndDistance); + elementAndRoutingPoint.routingPoints[0] = shiftedPoints[1]; + elementAndRoutingPoint.routingPoints.unshift(shiftedPoints[0]); + + } + } + }); + } + + // Straigth line between elements, no additional routing points + checkOverlapBetweenAnchors(target: SModelElement, elementAndRoutingPoint: ElementAndRoutingPoints) { + const sourceAnchor: Point = elementAndRoutingPoint.source; + const targetAnchor: Point = elementAndRoutingPoint.target; + target.root.index.all() + .filter(element => isSelectable(element)) + .forEach(element => { + if (isMoveable(element) && isResizeable(element)) { + const isOverlapping = this.isLineOverlappingElement(sourceAnchor, targetAnchor, element.bounds); + if (isOverlapping) { + const sideAndDistance: SideAndDistance = this.getClosestSideAndDistance(sourceAnchor, targetAnchor, element.bounds); + // Create additional points + if (sideAndDistance.side === Side.BOTTOM || sideAndDistance.side === Side.TOP) { + const firstPoint: Point = { + x: element.bounds.x - this.SPACING, + y: sourceAnchor.y + }; + const secondPoint: Point = { + x: element.bounds.x + element.bounds.width + this.SPACING, + y: sourceAnchor.y + }; + elementAndRoutingPoint.routingPoints.push(firstPoint); + elementAndRoutingPoint.routingPoints.push(secondPoint); + } else { + const firstPoint: Point = { + x: sourceAnchor.x, + y: element.bounds.y - this.SPACING + }; + const secondPoint: Point = { + x: sourceAnchor.y, + y: element.bounds.y + this.SPACING + }; + elementAndRoutingPoint.routingPoints.push(firstPoint); + elementAndRoutingPoint.routingPoints.push(secondPoint); + } + } + } + }); + } + + getDistanceBetweenParallelLines(p1: Point, p2: Point, secondLine: PointToPointLine): Number { + const numerator: number = Math.abs((secondLine.a * p1.x) + (secondLine.b * p1.y) - secondLine.c); + const denominator: number = Math.sqrt(Math.pow(secondLine.a, 2) + Math.pow(secondLine.b, 2)); + return numerator / denominator; + } + + getShiftedRoutingPoints(firstPoint: Point, secondPoint: Point, sideAndDistance: SideAndDistance): Point[] { + const distance: Number = sideAndDistance.distance; + const shiftedRoutingPoints: Point[] = []; + const side: Side = sideAndDistance.side; + let shiftedX1: number = firstPoint.x; + let shiftedY1: number = firstPoint.y; + let shiftedX2: number = secondPoint.x; + let shiftedY2: number = secondPoint.y; + if (side === Side.LEFT) { + shiftedX1 = firstPoint.x - distance.valueOf() - this.SPACING; + shiftedX2 = secondPoint.x - distance.valueOf() - this.SPACING; + } else if (side === Side.RIGHT) { + shiftedX1 = firstPoint.x + distance.valueOf() + this.SPACING; + shiftedX2 = secondPoint.x + distance.valueOf() + this.SPACING; + } else if (side === Side.TOP) { + shiftedY1 = firstPoint.y - distance.valueOf() - this.SPACING; + shiftedY2 = secondPoint.y - distance.valueOf() - this.SPACING; + } else if (side === Side.BOTTOM) { + shiftedY1 = firstPoint.y + distance.valueOf() + this.SPACING; + shiftedY2 = secondPoint.y + distance.valueOf() + this.SPACING; + } + const shiftedFirstPoint: Point = { + x: shiftedX1, + y: shiftedY1 + }; + const shiftedSecondPoint: Point = { + x: shiftedX2, + y: shiftedY2 + }; + + shiftedRoutingPoints.push(shiftedFirstPoint); + shiftedRoutingPoints.push(shiftedSecondPoint); + return shiftedRoutingPoints; + } + + getClosestSideAndDistance(firstPoint: Point, secondPoint: Point, bounds: Bounds): SideAndDistance { + const parallelLines: PointToPointLine[] = this.getParallelLines(firstPoint, secondPoint, bounds); + const distanceFirstLine = this.getDistanceBetweenParallelLines(firstPoint, secondPoint, parallelLines[0]); + const distanceSecondLine = this.getDistanceBetweenParallelLines(firstPoint, secondPoint, parallelLines[1]); + let distanceMinLine: Number; + let closestSide; + + // Horizontal line so we have to consider top/bottom + if (parallelLines[0].a === 0) { + if (distanceFirstLine <= distanceSecondLine) { + distanceMinLine = distanceFirstLine; + closestSide = Side.TOP; + } else { + distanceMinLine = distanceSecondLine; + closestSide = Side.BOTTOM; + } + + } else { + // Vertical line so we have to consider left/right + if (distanceFirstLine <= distanceSecondLine) { + distanceMinLine = distanceFirstLine; + closestSide = Side.LEFT; + } else { + distanceMinLine = distanceSecondLine; + closestSide = Side.RIGHT; + } + } + + return { side: closestSide, distance: distanceMinLine }; + } + + getParallelLines(a1: Point, a2: Point, bounds: Bounds): PointToPointLine[] { + const topLeft: Point = { + x: bounds.x, + y: bounds.y + }; + const topRight: Point = { + x: bounds.x + bounds.width, + y: bounds.y + }; + const bottomLeft: Point = { + x: bounds.x, + y: bounds.y + bounds.height + }; + const bottomRight: Point = { + x: bounds.x + bounds.width, + y: bounds.y + bounds.height + }; + + const parallelLines: PointToPointLine[] = []; + if (this.areLinesParallel(a1, a2, topLeft, topRight)) { + parallelLines.push(new PointToPointLine(topLeft, topRight)); + } + if (this.areLinesParallel(a1, a2, bottomLeft, bottomRight)) { + parallelLines.push(new PointToPointLine(bottomLeft, bottomRight)); + } + if (this.areLinesParallel(a1, a2, topLeft, bottomLeft)) { + parallelLines.push(new PointToPointLine(topLeft, bottomLeft)); + } + if (this.areLinesParallel(a1, a2, topRight, bottomRight)) { + parallelLines.push(new PointToPointLine(topRight, bottomRight)); + } + + return parallelLines; + } + + + areLinesParallel(a1: Point, a2: Point, b1: Point, b2: Point): boolean { + const firstLine: PointToPointLine = new PointToPointLine(a1, a2); + const secondLine: PointToPointLine = new PointToPointLine(b1, b2); + const parallelEquation = (firstLine.a * secondLine.b) - (firstLine.b * secondLine.a); + return almostEquals(parallelEquation, 0); + } + + isLineOverlappingElement(p1: Point, p2: Point, bounds: Bounds) { + + let minX = p1.x; + let maxX = p2.x; + const left = bounds.x; + const top = bounds.y; + const width = bounds.width; + const height = bounds.height; + + if (p1.x > p2.x) { + minX = p2.x; + maxX = p1.x; + } + + if (maxX > left + width) + maxX = left + width; + + if (minX < left) + minX = left; + + if (minX > maxX) + return false; + + let minY = p1.y; + let maxY = p2.y; + + const dx = p2.x - p1.x; + + if (Math.abs(dx) > 0.0000001) { + const a = (p2.y - p1.y) / dx; + const b = p1.y - a * p1.x; + minY = a * minX + b; + maxY = a * maxX + b; + } + + if (minY > maxY) { + const tmp = maxY; + maxY = minY; + minY = tmp; + } + + if (maxY > top + height) + maxY = top + height; + + if (minY < top) + minY = top; + + if (minY > maxY) + return false; + + return true; + + } + + getConnectedRoutingPoints(element: SModelElement): ElementAndRoutingPoints[] { + const elementsAndRoutingPoints: ElementAndRoutingPoints[] = []; + + if (isMoveable(element) && isResizeable(element) && isSelectable(element) && isConnectable(element)) { + + element.incomingEdges.forEach(edge => { + const router = this.edgeRouterRegistry.get(edge.routerKind); + router.createRoutingHandles(edge); + const routedPoints: RoutedPoint[] = router.route(edge); + const source: Point = routedPoints[0]; + const target: Point = routedPoints[routedPoints.length - 1]; + elementsAndRoutingPoints.push.apply(elementsAndRoutingPoints, createElementAndRoutingPoints(edge, source, target)); + + }); + element.outgoingEdges.forEach(edge => { + const router = this.edgeRouterRegistry.get(edge.routerKind); + router.createRoutingHandles(edge); + const routedPoints: RoutedPoint[] = router.route(edge); + const source: Point = routedPoints[0]; + const target: Point = routedPoints[routedPoints.length - 1]; + // edge.routingPoints = routedPoints; + elementsAndRoutingPoints.push.apply(elementsAndRoutingPoints, createElementAndRoutingPoints(edge, source, target)); + + }); + } + return elementsAndRoutingPoints; + } + selectionChanged(root: SModelRoot, selectedElements: string[]): void { if (this.activeResizeElementId) { if (selectedElements.indexOf(this.activeResizeElementId) > -1) { @@ -314,3 +722,25 @@ function minHeight(element: SModelElement & BoundsAware): number { // currently there are no element-specific constraints return 1; } + +function isNonRoutableBoundsAware(element: SModelElement): element is SModelElement & BoundsAware & Selectable { + return isBoundsAware(element) /*&& isSelected(element) */ && !isRoutable(element); +} + +function createElementAndRoutingPoints(element: SEdge & BoundsAware, source: Point, target: Point): ElementAndRoutingPoints[] { + return [{ elementId: element.id, source, target, routingPoints: element.routingPoints }]; +} + + + +export interface ElementAndRoutingPoints { + elementId: string + source: Point + target: Point + routingPoints: Point[] +} + +export interface SideAndDistance { + side: Side + distance: Number +} diff --git a/client/packages/sprotty-client/src/model-source/websocket-diagram-server.ts b/client/packages/sprotty-client/src/model-source/websocket-diagram-server.ts index cf6790c9..9263c591 100644 --- a/client/packages/sprotty-client/src/model-source/websocket-diagram-server.ts +++ b/client/packages/sprotty-client/src/model-source/websocket-diagram-server.ts @@ -16,7 +16,7 @@ import { injectable } from "inversify"; import { Action, ActionHandlerRegistry, ActionMessage, - ApplyLabelEditAction, CollapseExpandAction, CollapseExpandAllAction, + CollapseExpandAction, CollapseExpandAllAction, ComputedBoundsAction, DiagramServer, ExportSvgAction, ICommand, LayoutAction, OpenAction, RequestBoundsCommand, RequestModelAction, RequestPopupModelAction, ServerStatusAction, SwitchEditModeCommand @@ -31,6 +31,7 @@ import { IdentifiableRequestAction } from "../features/request-response/action-d import { SaveModelAction } from "../features/save/save"; import { GlspRedoAction, GlspUndoAction } from "../features/undo-redo/model"; import { RequestMarkersAction } from "../features/validation/validate"; +import { SaveModelEdgesAction } from "../features/change-bounds/edges"; @injectable() export class GLSPWebsocketDiagramServer extends DiagramServer { @@ -71,10 +72,6 @@ export class GLSPWebsocketDiagramServer extends DiagramServer { public getSourceURI(): string { return this._sourceUri; } - - protected handleComputedBounds(action: ComputedBoundsAction): boolean { - return true; - } } export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry, diagramServer: DiagramServer) { @@ -103,7 +100,7 @@ export function registerDefaultGLSPServerActions(registry: ActionHandlerRegistry registry.register(IdentifiableRequestAction.KIND, diagramServer); registry.register(RequestMarkersAction.KIND, diagramServer); registry.register(LayoutAction.KIND, diagramServer); - registry.register(ApplyLabelEditAction.KIND, diagramServer); + registry.register(SaveModelEdgesAction.KIND, diagramServer); // Register an empty handler for SwitchEditMode, to avoid runtime exceptions. // We don't want to support SwitchEditMode, but sprotty still sends some corresponding diff --git a/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/action/Action.java b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/action/Action.java index 4029fec3..5733c040 100644 --- a/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/action/Action.java +++ b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/action/Action.java @@ -113,6 +113,7 @@ public static class Kind { public static final String LAYOUT = "layout"; public static final String VALIDATE_LABEL_EDIT_ACTION = "validateLabelEdit"; public static final String SET_LABEL_EDIT_VALIDATION_RESULT_ACTION = "setLabelEditValidationResult"; + public static final String SAVE_MODEL_EDGES = "saveModelEdges"; } -} +} \ No newline at end of file diff --git a/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/action/kind/SaveModelEdgesAction.java b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/action/kind/SaveModelEdgesAction.java new file mode 100644 index 00000000..507fc82a --- /dev/null +++ b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/action/kind/SaveModelEdgesAction.java @@ -0,0 +1,38 @@ +package com.eclipsesource.glsp.api.action.kind; + +import java.util.List; + +import com.eclipsesource.glsp.api.action.Action; +import com.eclipsesource.glsp.api.model.ElementAndRoutingPoints; + +public class SaveModelEdgesAction extends Action{ + + private List newRoutingPoints; + + public SaveModelEdgesAction() { + super(Action.Kind.SAVE_MODEL_EDGES); + } + + public SaveModelEdgesAction(List elementAndRoutingPoints) { + this(); + this.newRoutingPoints = elementAndRoutingPoints; + } + + public List getNewRoutingPoints() { + return newRoutingPoints; + } + + public void setNewRoutingPoints(List newRoutingPoints) { + this.newRoutingPoints = newRoutingPoints; + } + + @Override + public String toString() { + return "SaveModelEdgesAction [newRoutingPoints=" + newRoutingPoints + "]"; + } + + + + + +} diff --git a/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/model/ElementAndRoutingPoints.java b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/model/ElementAndRoutingPoints.java new file mode 100644 index 00000000..ac8dd52d --- /dev/null +++ b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/model/ElementAndRoutingPoints.java @@ -0,0 +1,64 @@ +package com.eclipsesource.glsp.api.model; + +import java.util.List; + +import com.eclipsesource.glsp.graph.GPoint; + +public class ElementAndRoutingPoints { + + private String elementId; + private List routingPoints; + + + public String getElementId() { + return elementId; + + } + public void setElementId(String elementId) { + this.elementId = elementId; + } + public List getRoutingPoints() { + return routingPoints; + } + public void setRoutingPoints(List routingPoints) { + this.routingPoints = routingPoints; + } + @Override + public String toString() { + return "ElementAndRoutingPoints [elementId=" + elementId + ", routingPoints=" + routingPoints + "]"; + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((elementId == null) ? 0 : elementId.hashCode()); + result = prime * result + ((routingPoints == null) ? 0 : routingPoints.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ElementAndRoutingPoints other = (ElementAndRoutingPoints) obj; + if (elementId == null) { + if (other.elementId != null) + return false; + } else if (!elementId.equals(other.elementId)) + return false; + if (routingPoints == null) { + if (other.routingPoints != null) + return false; + } else if (!routingPoints.equals(other.routingPoints)) + return false; + return true; + } + + + + + +} diff --git a/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/actionhandler/SaveModelEdgesHandler.java b/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/actionhandler/SaveModelEdgesHandler.java new file mode 100644 index 00000000..d10b32e4 --- /dev/null +++ b/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/actionhandler/SaveModelEdgesHandler.java @@ -0,0 +1,47 @@ +package com.eclipsesource.glsp.server.actionhandler; + +import java.util.List; +import java.util.Optional; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.util.EList; + +import com.eclipsesource.glsp.api.action.Action; +import com.eclipsesource.glsp.api.action.kind.SaveModelEdgesAction; +import com.eclipsesource.glsp.api.model.ElementAndRoutingPoints; +import com.eclipsesource.glsp.api.model.GraphicalModelState; +import com.eclipsesource.glsp.graph.GEdge; +import com.eclipsesource.glsp.graph.GModelElement; +import com.eclipsesource.glsp.graph.GPoint; +import com.google.inject.Inject; + +public class SaveModelEdgesHandler extends AbstractActionHandler{ + + private static final Logger LOG = Logger.getLogger(SaveModelActionHandler.class); + + @Inject + private ModelSubmissionHandler modelSubmissionHandler; + + @Override + public boolean handles(Action action) { + return action instanceof SaveModelEdgesAction; + } + @Override + protected Optional execute(Action action, GraphicalModelState modelState) { + + SaveModelEdgesAction saveModelEdgesAction = (SaveModelEdgesAction)action; + List elementAndRoutingPointsList = saveModelEdgesAction.getNewRoutingPoints(); + for(ElementAndRoutingPoints elementAndRoutingPoints : elementAndRoutingPointsList) { + Optional modelElement = modelState.getIndex().get(elementAndRoutingPoints.getElementId()); + if(modelElement.isPresent() && modelElement.get() instanceof GEdge) { + GEdge edge = (GEdge) modelElement.get(); + EList routingPoints = edge.getRoutingPoints(); + routingPoints.clear(); + routingPoints.addAll(elementAndRoutingPoints.getRoutingPoints()); + + } + + } + return modelSubmissionHandler.doSubmitModel(true, modelState); + } +} diff --git a/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/di/DefaultGLSPModule.java b/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/di/DefaultGLSPModule.java index 718fc508..875fa70e 100644 --- a/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/di/DefaultGLSPModule.java +++ b/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/di/DefaultGLSPModule.java @@ -52,6 +52,7 @@ import com.eclipsesource.glsp.server.actionhandler.SelectActionHandler; import com.eclipsesource.glsp.server.actionhandler.UndoRedoActionHandler; import com.eclipsesource.glsp.server.actionhandler.ValidateLabelEditActionHandler; +import com.eclipsesource.glsp.server.actionhandler.SaveModelEdgesHandler; import com.eclipsesource.glsp.server.diagram.DIDiagramConfigurationProvider; import com.eclipsesource.glsp.server.factory.DefaultGraphGsonConfiguratorFactory; import com.eclipsesource.glsp.server.jsonrpc.DefaultGLSPClientProvider; @@ -78,7 +79,7 @@ public abstract class DefaultGLSPModule extends GLSPModule { RequestPopupModelActionHandler.class, SaveModelActionHandler.class, UndoRedoActionHandler.class, SelectActionHandler.class, ExecuteServerCommandActionHandler.class, RequestTypeHintsActionHandler.class, RequestCommandPaletteActionsHandler.class, RequestMarkersHandler.class, LayoutActionHandler.class, - ValidateLabelEditActionHandler.class); + ValidateLabelEditActionHandler.class, SaveModelEdgesHandler.class); @Override protected void configure() { diff --git a/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/provider/DefaultActionProvider.java b/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/provider/DefaultActionProvider.java index 24552850..25f2d731 100644 --- a/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/provider/DefaultActionProvider.java +++ b/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/provider/DefaultActionProvider.java @@ -19,7 +19,6 @@ import java.util.Set; import com.eclipsesource.glsp.api.action.Action; -import com.eclipsesource.glsp.api.action.kind.ApplyLabelEditOperationAction; import com.eclipsesource.glsp.api.action.kind.CenterAction; import com.eclipsesource.glsp.api.action.kind.ChangeBoundsOperationAction; import com.eclipsesource.glsp.api.action.kind.ChangeContainerOperationAction; @@ -48,11 +47,11 @@ import com.eclipsesource.glsp.api.action.kind.RequestTypeHintsAction; import com.eclipsesource.glsp.api.action.kind.RerouteConnectionOperationAction; import com.eclipsesource.glsp.api.action.kind.SaveModelAction; +import com.eclipsesource.glsp.api.action.kind.SaveModelEdgesAction; import com.eclipsesource.glsp.api.action.kind.SelectAction; import com.eclipsesource.glsp.api.action.kind.SelectAllAction; import com.eclipsesource.glsp.api.action.kind.ServerStatusAction; import com.eclipsesource.glsp.api.action.kind.SetBoundsAction; -import com.eclipsesource.glsp.api.action.kind.SetEditLabelValidationResultAction; import com.eclipsesource.glsp.api.action.kind.SetLayersAction; import com.eclipsesource.glsp.api.action.kind.SetModelAction; import com.eclipsesource.glsp.api.action.kind.SetOperationsAction; @@ -60,7 +59,6 @@ import com.eclipsesource.glsp.api.action.kind.ToogleLayerAction; import com.eclipsesource.glsp.api.action.kind.UndoAction; import com.eclipsesource.glsp.api.action.kind.UpdateModelAction; -import com.eclipsesource.glsp.api.action.kind.ValidateLabelEditAction; import com.eclipsesource.glsp.api.provider.ActionProvider; public class DefaultActionProvider implements ActionProvider { @@ -72,7 +70,7 @@ public DefaultActionProvider() { } private void addDefaultActions() { - defaultActions.add(new ApplyLabelEditOperationAction()); + defaultActions.add(new SaveModelEdgesAction()); defaultActions.add(new CenterAction()); defaultActions.add(new ChangeBoundsOperationAction()); defaultActions.add(new CollapseExpandAction()); @@ -104,7 +102,6 @@ private void addDefaultActions() { defaultActions.add(new SetModelAction()); defaultActions.add(new SetOperationsAction()); defaultActions.add(new SetPopupModelAction()); - defaultActions.add(new SetEditLabelValidationResultAction()); defaultActions.add(new ToogleLayerAction()); defaultActions.add(new UpdateModelAction()); defaultActions.add(new ExecuteServerCommandAction()); @@ -113,7 +110,6 @@ private void addDefaultActions() { defaultActions.add(new ReconnectConnectionOperationAction()); defaultActions.add(new RerouteConnectionOperationAction()); defaultActions.add(new LayoutAction()); - defaultActions.add(new ValidateLabelEditAction()); } @Override