1 package org.simantics.district.network.ui.participants;
3 import java.awt.AlphaComposite;
4 import java.awt.Cursor;
5 import java.util.Collection;
7 import org.eclipse.jface.dialogs.IDialogSettings;
8 import org.eclipse.jface.dialogs.IInputValidator;
9 import org.eclipse.jface.dialogs.InputDialog;
10 import org.eclipse.jface.dialogs.MessageDialog;
11 import org.eclipse.jface.window.Window;
12 import org.eclipse.swt.widgets.Display;
13 import org.eclipse.swt.widgets.Shell;
14 import org.eclipse.ui.PlatformUI;
15 import org.simantics.db.Resource;
16 import org.simantics.diagram.ui.DiagramModelHints;
17 import org.simantics.district.network.ui.adapters.DistrictNetworkVertexElement;
18 import org.simantics.district.network.ui.internal.Activator;
19 import org.simantics.district.route.Route;
20 import org.simantics.district.route.RouteEvent;
21 import org.simantics.district.route.RouteService;
22 import org.simantics.district.route.RouteServiceListener;
23 import org.simantics.district.route.Waypoint;
24 import org.simantics.g2d.canvas.ICanvasContext;
25 import org.simantics.g2d.canvas.IMouseCursorContext;
26 import org.simantics.g2d.canvas.IMouseCursorHandle;
27 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
28 import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
29 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
30 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
31 import org.simantics.g2d.diagram.handler.PickContext;
32 import org.simantics.g2d.diagram.participant.Selection;
33 import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;
34 import org.simantics.g2d.element.ElementClass;
35 import org.simantics.g2d.element.ElementUtils;
36 import org.simantics.g2d.element.IElement;
37 import org.simantics.g2d.participant.TransformUtil;
38 import org.simantics.scenegraph.g2d.G2DParentNode;
39 import org.simantics.scenegraph.g2d.events.Event;
40 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
41 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
42 import org.simantics.scenegraph.g2d.events.command.Command;
43 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
44 import org.simantics.scenegraph.g2d.events.command.Commands;
45 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
46 import org.simantics.utils.datastructures.hints.IHintContext.Key;
47 import org.simantics.utils.datastructures.hints.IHintObservable;
48 import org.simantics.utils.ui.AdaptionUtils;
49 import org.simantics.utils.ui.SWTUtils;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * This participant creates routes on district network diagrams using
55 * {@link RouteService}.
57 * @author Tuukka Lehtonen
60 public class RoutingMode extends AbstractMode {
62 private static final Logger LOGGER = LoggerFactory.getLogger(RoutingMode.class);
65 protected TransformUtil util;
68 protected PickContext pickContext;
71 protected Selection selection;
73 protected IMouseCursorHandle cursor;
75 private RouteService routeService;
80 * The node under which the mutated diagram is ghosted in the scene graph.
82 protected G2DParentNode parent;
85 * This stays null until the translated diagram parts have been initialized
86 * into the scene graph. After that, only the translations of the nodes in
87 * the scene graph are modified.
89 protected SingleElementNode node = null;
91 private RouteServiceListener routeServiceListener;
93 public RoutingMode(int mouseId) {
98 public void addedToContext(ICanvasContext ctx) {
99 super.addedToContext(ctx);
100 IMouseCursorContext mcc = getContext().getMouseCursorContext();
101 cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.CROSSHAIR_CURSOR));
102 routeService = Activator.getInstance().getRouteService();
103 if (routeService != null) {
105 routeServiceListener = e -> {
107 case RouteEvent.TYPE_ROUTE_PERSISTED:
112 routeService.addListener(routeServiceListener);
113 Resource diagramResource = getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
114 LOGGER.info("Diagram resource: " + diagramResource);
115 route = routeService.createRoute("[UNPERSISTED] Current", diagramResource);
116 routeService.registerRoute(route);
121 public void removedFromContext(ICanvasContext ctx) {
122 if (cursor != null) {
126 if (routeService != null) {
127 routeService.removeListener(routeServiceListener);
128 // Discard route if it hasn't been persisted before this
129 if (route != null && !route.persisted()) {
130 routeService.discardRoute(route);
134 super.removedFromContext(ctx);
137 @HintListener(Class = Selection.class, Field = "SELECTION0")
138 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
139 Collection<IElement> elements = AdaptionUtils.adaptToCollection(newValue, IElement.class);
140 for (IElement element : elements) {
141 addRoutePoint(element);
145 private void addRoutePoint(IElement element) {
149 Object o = ElementUtils.getObject(element);
150 if (o instanceof Resource && filterAcceptableElement(element, o)) {
151 Waypoint wp = route.createWaypoint(o);
152 route.addWaypoint(wp);
157 * For now, routing can only start from vertices
159 * @return true if element is a vertex
161 private boolean filterAcceptableElement(IElement element, Object o) {
162 ElementClass elementClass = element.getElementClass();
163 return elementClass.getId().equals(DistrictNetworkVertexElement.CLASS.getId());
167 public void initSG(G2DParentNode parent) {
168 this.parent = parent;
169 node = parent.addNode("route highlight", SingleElementNode.class);
170 node.setZIndex(1000);
171 node.setVisible(Boolean.TRUE);
172 node.setComposite(AlphaComposite.SrcOver.derive(0.75f));
176 public void cleanupSG() {
184 @EventHandler(priority = 30)
185 public boolean handleEvent(Event e) {
186 if (e instanceof KeyPressedEvent) {
189 KeyPressedEvent kpe = (KeyPressedEvent) e;
190 if (kpe.keyCode == java.awt.event.KeyEvent.VK_ENTER) {
191 Route committedRoute = route;
193 SWTUtils.asyncExec(Display.getDefault(), () -> {
194 askRouteNameAndPersist(committedRoute);
198 } else if (e instanceof CommandEvent) {
199 Command cmd = ((CommandEvent) e).command;
200 if (cmd.equals(Commands.CANCEL)) {
201 // let's ensure if user wants to dispose possibly unpersisted route
202 if (route != null && !route.persisted()) {
203 Route committedRoute = route;
204 SWTUtils.asyncExec(Display.getDefault(), () -> {
205 if (!askDiscardUnpersistedRoute()) {
206 askRouteNameAndPersist(committedRoute);
211 } else if (cmd.equals(Commands.RENAME)) {
212 // TODO: still needs key binding contribution for the district diagram editor
213 SWTUtils.asyncExec(Display.getDefault(), () -> {
214 String newName = askRouteName("Rename Route", route.getName());
215 if (newName != null) {
216 route.setName(newName);
217 routeService.refreshRoute(route);
225 private void askRouteNameAndPersist(Route committedRoute) {
226 String newName = askRouteName("Confirm Route", committedRoute.getName());
227 if (newName != null) {
228 committedRoute.setName(newName);
229 routeService.persistRoute(committedRoute);
231 routeService.discardRoute(committedRoute);
235 private boolean askDiscardUnpersistedRoute() {
236 MessageDialog dialog = new MessageDialog(
237 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
240 "Discard current unpersisted route?",
241 MessageDialog.INFORMATION,
243 new String[] { "OK", "Cancel" });
244 return dialog.open() == Window.OK;
247 private String askRouteName(String dialogTitle, String initialValue) {
248 InputDialog dialog = new InputDialog(
249 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
253 s -> s.trim().length() > 0 ? null : "Name must be non-empty");
254 if (dialog.open() == Window.OK) {
255 return dialog.getValue();
260 protected boolean dispose() {
266 static class RenameDialog extends InputDialog {
268 public RenameDialog(Shell parent, String title, String message, String initialValue, IInputValidator validator) {
269 super(parent, title, message, initialValue, validator);
273 protected IDialogSettings getDialogBoundsSettings() {
274 String sectionName = getClass().getName() + "_dialogBounds"; //$NON-NLS-1$
275 IDialogSettings settings= Activator.getInstance().getDialogSettings();
276 IDialogSettings section= settings.getSection(sectionName);
278 section= settings.addNewSection(sectionName);
283 protected int getDialogBoundsStrategy() {
284 return DIALOG_PERSISTLOCATION;