package org.simantics.district.network.ui.participants; import java.awt.AlphaComposite; import java.awt.Cursor; import java.util.Collection; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.window.Window; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; import org.simantics.db.Resource; import org.simantics.diagram.ui.DiagramModelHints; import org.simantics.district.network.ui.adapters.DistrictNetworkVertexElement; import org.simantics.district.network.ui.internal.Activator; import org.simantics.district.route.Route; import org.simantics.district.route.RouteEvent; import org.simantics.district.route.RouteService; import org.simantics.district.route.RouteServiceListener; import org.simantics.district.route.Waypoint; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.IMouseCursorContext; import org.simantics.g2d.canvas.IMouseCursorHandle; import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; import org.simantics.g2d.canvas.impl.HintReflection.HintListener; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit; import org.simantics.g2d.diagram.handler.PickContext; import org.simantics.g2d.diagram.participant.Selection; import org.simantics.g2d.diagram.participant.pointertool.AbstractMode; import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.participant.TransformUtil; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.events.Event; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; import org.simantics.scenegraph.g2d.events.command.Command; import org.simantics.scenegraph.g2d.events.command.CommandEvent; import org.simantics.scenegraph.g2d.events.command.Commands; import org.simantics.scenegraph.g2d.nodes.SingleElementNode; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.ui.AdaptionUtils; import org.simantics.utils.ui.SWTUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This participant creates routes on district network diagrams using * {@link RouteService}. * * @author Tuukka Lehtonen * @since 6.09 */ public class RoutingMode extends AbstractMode { private static final Logger LOGGER = LoggerFactory.getLogger(RoutingMode.class); @Dependency protected TransformUtil util; @Dependency protected PickContext pickContext; @Dependency protected Selection selection; protected IMouseCursorHandle cursor; private RouteService routeService; private Route route; /** * The node under which the mutated diagram is ghosted in the scene graph. */ protected G2DParentNode parent; /** * This stays null until the translated diagram parts have been initialized * into the scene graph. After that, only the translations of the nodes in * the scene graph are modified. */ protected SingleElementNode node = null; private RouteServiceListener routeServiceListener; public RoutingMode(int mouseId) { super(mouseId); } @Override public void addedToContext(ICanvasContext ctx) { super.addedToContext(ctx); IMouseCursorContext mcc = getContext().getMouseCursorContext(); cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.CROSSHAIR_CURSOR)); routeService = Activator.getInstance().getRouteService(); if (routeService != null) { routeServiceListener = e -> { switch (e.type) { case RouteEvent.TYPE_ROUTE_PERSISTED: dispose(); break; } }; routeService.addListener(routeServiceListener); Resource diagramResource = getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE); LOGGER.info("Diagram resource: " + diagramResource); route = routeService.createRoute("[UNPERSISTED] Current", diagramResource); routeService.registerRoute(route); } } @Override public void removedFromContext(ICanvasContext ctx) { if (cursor != null) { cursor.remove(); cursor = null; } if (routeService != null) { routeService.removeListener(routeServiceListener); // Discard route if it hasn't been persisted before this if (route != null && !route.persisted()) { routeService.discardRoute(route); route = null; } } super.removedFromContext(ctx); } @HintListener(Class = Selection.class, Field = "SELECTION0") public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { Collection elements = AdaptionUtils.adaptToCollection(newValue, IElement.class); for (IElement element : elements) { addRoutePoint(element); } } private void addRoutePoint(IElement element) { if (route == null) return; Object o = ElementUtils.getObject(element); if (o instanceof Resource && filterAcceptableElement(element, o)) { Waypoint wp = route.createWaypoint(o); route.addWaypoint(wp); } } /** * For now, routing can only start from vertices * * @return true if element is a vertex */ private boolean filterAcceptableElement(IElement element, Object o) { ElementClass elementClass = element.getElementClass(); return elementClass.getId().equals(DistrictNetworkVertexElement.CLASS.getId()); } @SGInit public void initSG(G2DParentNode parent) { this.parent = parent; node = parent.addNode("route highlight", SingleElementNode.class); node.setZIndex(1000); node.setVisible(Boolean.TRUE); node.setComposite(AlphaComposite.SrcOver.derive(0.75f)); } @SGCleanup public void cleanupSG() { if (node != null) { node.remove(); node = null; } parent = null; } @EventHandler(priority = 30) public boolean handleEvent(Event e) { if (e instanceof KeyPressedEvent) { if (route == null) return false; KeyPressedEvent kpe = (KeyPressedEvent) e; if (kpe.keyCode == java.awt.event.KeyEvent.VK_ENTER) { Route committedRoute = route; route = null; SWTUtils.asyncExec(Display.getDefault(), () -> { askRouteNameAndPersist(committedRoute); }); dispose(); } } else if (e instanceof CommandEvent) { Command cmd = ((CommandEvent) e).command; if (cmd.equals(Commands.CANCEL)) { // let's ensure if user wants to dispose possibly unpersisted route if (route != null && !route.persisted()) { Route committedRoute = route; SWTUtils.asyncExec(Display.getDefault(), () -> { if (!askDiscardUnpersistedRoute()) { askRouteNameAndPersist(committedRoute); } }); } return dispose(); } else if (cmd.equals(Commands.RENAME)) { // TODO: still needs key binding contribution for the district diagram editor SWTUtils.asyncExec(Display.getDefault(), () -> { String newName = askRouteName("Rename Route", route.getName()); if (newName != null) { route.setName(newName); routeService.refreshRoute(route); } }); } } return false; } private void askRouteNameAndPersist(Route committedRoute) { String newName = askRouteName("Confirm Route", committedRoute.getName()); if (newName != null) { committedRoute.setName(newName); routeService.persistRoute(committedRoute); } else { routeService.discardRoute(committedRoute); } } private boolean askDiscardUnpersistedRoute() { MessageDialog dialog = new MessageDialog( PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Discard route", null, "Discard current unpersisted route?", MessageDialog.INFORMATION, 0, new String[] { "OK", "Cancel" }); return dialog.open() == Window.OK; } private String askRouteName(String dialogTitle, String initialValue) { InputDialog dialog = new InputDialog( PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), dialogTitle, "Route name", initialValue, s -> s.trim().length() > 0 ? null : "Name must be non-empty"); if (dialog.open() == Window.OK) { return dialog.getValue(); } return null; } protected boolean dispose() { setDirty(); remove(); return false; } static class RenameDialog extends InputDialog { public RenameDialog(Shell parent, String title, String message, String initialValue, IInputValidator validator) { super(parent, title, message, initialValue, validator); } @Override protected IDialogSettings getDialogBoundsSettings() { String sectionName = getClass().getName() + "_dialogBounds"; //$NON-NLS-1$ IDialogSettings settings= Activator.getInstance().getDialogSettings(); IDialogSettings section= settings.getSection(sectionName); if (section == null) section= settings.addNewSection(sectionName); return section; } @Override protected int getDialogBoundsStrategy() { return DIALOG_PERSISTLOCATION; } } }