]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/RoutingMode.java
Minor usability fixes for routing in district diagrams
[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.internal.Activator;
18 import org.simantics.district.route.Route;
19 import org.simantics.district.route.RouteEvent;
20 import org.simantics.district.route.RouteService;
21 import org.simantics.district.route.RouteServiceListener;
22 import org.simantics.district.route.Waypoint;
23 import org.simantics.g2d.canvas.ICanvasContext;
24 import org.simantics.g2d.canvas.IMouseCursorContext;
25 import org.simantics.g2d.canvas.IMouseCursorHandle;
26 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
27 import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
28 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
29 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
30 import org.simantics.g2d.diagram.handler.PickContext;
31 import org.simantics.g2d.diagram.participant.Selection;
32 import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;
33 import org.simantics.g2d.element.ElementUtils;
34 import org.simantics.g2d.element.IElement;
35 import org.simantics.g2d.participant.TransformUtil;
36 import org.simantics.scenegraph.g2d.G2DParentNode;
37 import org.simantics.scenegraph.g2d.events.Event;
38 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
39 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
40 import org.simantics.scenegraph.g2d.events.command.Command;
41 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
42 import org.simantics.scenegraph.g2d.events.command.Commands;
43 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
44 import org.simantics.utils.datastructures.hints.IHintContext.Key;
45 import org.simantics.utils.datastructures.hints.IHintObservable;
46 import org.simantics.utils.ui.AdaptionUtils;
47 import org.simantics.utils.ui.SWTUtils;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * This participant creates routes on district network diagrams using
53  * {@link RouteService}.
54  *
55  * @author Tuukka Lehtonen
56  * @since 6.09
57  */
58 public class RoutingMode extends AbstractMode {
59
60     private static final Logger LOGGER = LoggerFactory.getLogger(RoutingMode.class);
61
62     @Dependency
63     protected TransformUtil        util;
64
65     @Dependency
66     protected PickContext          pickContext;
67
68     @Dependency
69     protected Selection            selection;
70
71     protected IMouseCursorHandle   cursor;
72
73     private RouteService           routeService;
74
75     private Route                  route;
76
77     /**
78      * The node under which the mutated diagram is ghosted in the scene graph.
79      */
80     protected G2DParentNode        parent;
81
82     /**
83      * This stays null until the translated diagram parts have been initialized
84      * into the scene graph. After that, only the translations of the nodes in
85      * the scene graph are modified.
86      */
87     protected SingleElementNode    node = null;
88
89     private RouteServiceListener routeServiceListener;
90
91     public RoutingMode(int mouseId) {
92         super(mouseId);
93     }
94
95     @Override
96     public void addedToContext(ICanvasContext ctx) {
97         super.addedToContext(ctx);
98         IMouseCursorContext mcc = getContext().getMouseCursorContext();
99         cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.CROSSHAIR_CURSOR));
100         routeService = Activator.getInstance().getRouteService();
101         if (routeService != null) {
102             
103             routeServiceListener = e -> {
104                 switch (e.type) {
105                 case RouteEvent.TYPE_ROUTE_PERSISTED:
106                     dispose();
107                     break;
108                 }
109             };
110             routeService.addListener(routeServiceListener);
111             Resource diagramResource = getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
112             LOGGER.info("Diagram resource: " + diagramResource);
113             route = routeService.createRoute("[UNPERSISTED] Current", diagramResource);
114             routeService.registerRoute(route);
115         }
116     }
117
118     @Override
119     public void removedFromContext(ICanvasContext ctx) {
120         if (cursor != null) {
121             cursor.remove();
122             cursor = null;
123         }
124         if (routeService != null) {
125             routeService.removeListener(routeServiceListener);
126             // Discard route if it hasn't been persisted before this
127             if (route != null && !route.persisted()) {
128                 routeService.discardRoute(route);
129                 route = null;
130             }
131         }
132         super.removedFromContext(ctx);
133     }
134
135     @HintListener(Class = Selection.class, Field = "SELECTION0")
136     public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
137         Collection<IElement> elements = AdaptionUtils.adaptToCollection(newValue, IElement.class);
138         for (IElement element : elements) {
139             addRoutePoint(element);
140         }
141     }
142
143     private void addRoutePoint(IElement element) {
144         if (route == null)
145             return;
146
147         Object o = ElementUtils.getObject(element);
148         if (o instanceof Resource) {
149             Waypoint wp = route.createWaypoint(o);
150             route.addWaypoint(wp);
151         }
152     }
153
154     @SGInit
155     public void initSG(G2DParentNode parent) {
156         this.parent = parent;
157         node = parent.addNode("route highlight", SingleElementNode.class);
158         node.setZIndex(1000);
159         node.setVisible(Boolean.TRUE);
160         node.setComposite(AlphaComposite.SrcOver.derive(0.75f));
161     }
162
163     @SGCleanup
164     public void cleanupSG() {
165         if (node != null) {
166             node.remove();
167             node = null;
168         }
169         parent = null;
170     }
171
172     @EventHandler(priority = 30)
173     public boolean handleEvent(Event e) {
174         if (e instanceof KeyPressedEvent) {
175             if (route == null)
176                 return false;
177             KeyPressedEvent kpe = (KeyPressedEvent) e;
178             if (kpe.keyCode == java.awt.event.KeyEvent.VK_ENTER) {
179                 Route committedRoute = route;
180                 route = null;
181                 SWTUtils.asyncExec(Display.getDefault(), () -> {
182                     askRouteNameAndPersist(committedRoute);
183                 });
184                 dispose();
185             }
186         } else if (e instanceof CommandEvent) {
187             Command cmd = ((CommandEvent) e).command;
188             if (cmd.equals(Commands.CANCEL)) {
189                 // let's ensure if user wants to dispose possibly unpersisted route
190                 if (route != null && !route.persisted()) {
191                     Route committedRoute = route;
192                     SWTUtils.asyncExec(Display.getDefault(), () -> {
193                         if (!askDiscardUnpersistedRoute()) {
194                             askRouteNameAndPersist(committedRoute);
195                         }
196                     });
197                 }
198                 return dispose();
199             } else if (cmd.equals(Commands.RENAME)) {
200                 // TODO: still needs key binding contribution for the district diagram editor
201                 SWTUtils.asyncExec(Display.getDefault(), () -> {
202                     String newName = askRouteName("Rename Route", route.getName());
203                     if (newName != null) {
204                         route.setName(newName);
205                         routeService.refreshRoute(route);
206                     }
207                 });
208             }
209         }
210         return false;
211     }
212
213     private void askRouteNameAndPersist(Route committedRoute) {
214         String newName = askRouteName("Confirm Route", committedRoute.getName());
215         if (newName != null) {
216             committedRoute.setName(newName);
217             routeService.persistRoute(committedRoute);
218         } else {
219             routeService.discardRoute(committedRoute);
220         }
221     }
222     
223     private boolean askDiscardUnpersistedRoute() {
224         MessageDialog dialog = new MessageDialog(
225                 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
226                 "Discard route",
227                 null,
228                 "Discard current unpersisted route?",
229                 MessageDialog.INFORMATION,
230                 0,
231                 new String[] { "OK", "Cancel" });
232         return dialog.open() == Window.OK;
233     }
234     
235     private String askRouteName(String dialogTitle, String initialValue) {
236         InputDialog dialog = new InputDialog(
237                 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
238                 dialogTitle,
239                 "Route name",
240                 initialValue,
241                 s -> s.trim().length() > 0 ? null : "Name must be non-empty");
242         if (dialog.open() == Window.OK) {
243             return dialog.getValue();
244         }
245         return null;
246     }
247
248     protected boolean dispose() {
249         setDirty();
250         remove();
251         return false;
252     }
253
254     static class RenameDialog extends InputDialog {
255
256         public RenameDialog(Shell parent, String title, String message, String initialValue, IInputValidator validator) {
257             super(parent, title, message, initialValue, validator);
258         }
259
260         @Override
261         protected IDialogSettings getDialogBoundsSettings() {
262             String sectionName = getClass().getName() + "_dialogBounds"; //$NON-NLS-1$
263             IDialogSettings settings= Activator.getInstance().getDialogSettings();
264             IDialogSettings section= settings.getSection(sectionName);
265             if (section == null)
266                 section= settings.addNewSection(sectionName);
267             return section;
268         }
269
270         @Override
271         protected int getDialogBoundsStrategy() {
272             return DIALOG_PERSISTLOCATION;
273         }
274     }
275
276 }