]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/RoutingMode.java
More sensible caching for connection lines.
[simantics/district.git] / org.simantics.district.network.ui / src / org / simantics / district / network / ui / participants / RoutingMode.java
1 package org.simantics.district.network.ui.participants;
2
3 import java.awt.AlphaComposite;
4 import java.awt.Cursor;
5 import java.util.Collection;
6
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;
52
53 /**
54  * This participant creates routes on district network diagrams using
55  * {@link RouteService}.
56  *
57  * @author Tuukka Lehtonen
58  * @since 6.09
59  */
60 public class RoutingMode extends AbstractMode {
61
62     private static final Logger LOGGER = LoggerFactory.getLogger(RoutingMode.class);
63
64     @Dependency
65     protected TransformUtil        util;
66
67     @Dependency
68     protected PickContext          pickContext;
69
70     @Dependency
71     protected Selection            selection;
72
73     protected IMouseCursorHandle   cursor;
74
75     private RouteService           routeService;
76
77     private Route                  route;
78
79     /**
80      * The node under which the mutated diagram is ghosted in the scene graph.
81      */
82     protected G2DParentNode        parent;
83
84     /**
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.
88      */
89     protected SingleElementNode    node = null;
90
91     private RouteServiceListener routeServiceListener;
92
93     public RoutingMode(int mouseId) {
94         super(mouseId);
95     }
96
97     @Override
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) {
104             
105             routeServiceListener = e -> {
106                 switch (e.type) {
107                 case RouteEvent.TYPE_ROUTE_PERSISTED:
108                     dispose();
109                     break;
110                 }
111             };
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);
117         }
118     }
119
120     @Override
121     public void removedFromContext(ICanvasContext ctx) {
122         if (cursor != null) {
123             cursor.remove();
124             cursor = null;
125         }
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);
131                 route = null;
132             }
133         }
134         super.removedFromContext(ctx);
135     }
136
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);
142         }
143     }
144
145     private void addRoutePoint(IElement element) {
146         if (route == null)
147             return;
148
149         Object o = ElementUtils.getObject(element);
150         if (o instanceof Resource && filterAcceptableElement(element, o)) {
151             Waypoint wp = route.createWaypoint(o);
152             route.addWaypoint(wp);
153         }
154     }
155
156     /**
157      * For now, routing can only start from vertices
158      * 
159      * @return true if element is a vertex
160      */
161     private boolean filterAcceptableElement(IElement element, Object o) {
162         ElementClass elementClass = element.getElementClass();
163         return elementClass.getId().equals(DistrictNetworkVertexElement.CLASS.getId());
164     }
165
166     @SGInit
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));
173     }
174
175     @SGCleanup
176     public void cleanupSG() {
177         if (node != null) {
178             node.remove();
179             node = null;
180         }
181         parent = null;
182     }
183
184     @EventHandler(priority = 30)
185     public boolean handleEvent(Event e) {
186         if (e instanceof KeyPressedEvent) {
187             if (route == null)
188                 return false;
189             KeyPressedEvent kpe = (KeyPressedEvent) e;
190             if (kpe.keyCode == java.awt.event.KeyEvent.VK_ENTER) {
191                 Route committedRoute = route;
192                 route = null;
193                 SWTUtils.asyncExec(Display.getDefault(), () -> {
194                     askRouteNameAndPersist(committedRoute);
195                 });
196                 dispose();
197             }
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);
207                         }
208                     });
209                 }
210                 return dispose();
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);
218                     }
219                 });
220             }
221         }
222         return false;
223     }
224
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);
230         } else {
231             routeService.discardRoute(committedRoute);
232         }
233     }
234     
235     private boolean askDiscardUnpersistedRoute() {
236         MessageDialog dialog = new MessageDialog(
237                 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
238                 "Discard route",
239                 null,
240                 "Discard current unpersisted route?",
241                 MessageDialog.INFORMATION,
242                 0,
243                 new String[] { "OK", "Cancel" });
244         return dialog.open() == Window.OK;
245     }
246     
247     private String askRouteName(String dialogTitle, String initialValue) {
248         InputDialog dialog = new InputDialog(
249                 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
250                 dialogTitle,
251                 "Route name",
252                 initialValue,
253                 s -> s.trim().length() > 0 ? null : "Name must be non-empty");
254         if (dialog.open() == Window.OK) {
255             return dialog.getValue();
256         }
257         return null;
258     }
259
260     protected boolean dispose() {
261         setDirty();
262         remove();
263         return false;
264     }
265
266     static class RenameDialog extends InputDialog {
267
268         public RenameDialog(Shell parent, String title, String message, String initialValue, IInputValidator validator) {
269             super(parent, title, message, initialValue, validator);
270         }
271
272         @Override
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);
277             if (section == null)
278                 section= settings.addNewSection(sectionName);
279             return section;
280         }
281
282         @Override
283         protected int getDialogBoundsStrategy() {
284             return DIALOG_PERSISTLOCATION;
285         }
286     }
287
288 }