Manifest-Version: 1.0
Bundle-ManifestVersion: 2
-Bundle-Name: http://www.simantics.org/DistrictNetwork-1.0
+Bundle-Name: http://www.simantics.org/DistrictNetwork
Bundle-SymbolicName: org.simantics.district.network.ontology
Bundle-Version: 1.0.0.qualifier
Require-Bundle: org.simantics.layer0,
org.eclipse.e4.core.commands,
org.eclipse.e4.core.contexts,
org.eclipse.jface,
- org.simantics.scl.osgi
+ org.simantics.scl.osgi,
+ org.simantics.district.route.ui;bundle-version="1.0.0",
+ org.simantics.district.route
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: javax.annotation;version="1.0.0";resolution:=optional,
javax.inject;version="1.0.0"
import org.simantics.db.common.request.UniqueRead;
import org.simantics.db.exception.DatabaseException;
import org.simantics.db.procedure.Listener;
+import org.simantics.diagram.ui.DiagramModelHints;
import org.simantics.district.network.DistrictNetworkUtil;
import org.simantics.district.network.ui.participants.DNPointerInteractor;
import org.simantics.district.network.ui.participants.MapRulerPainter;
IHintContext h = ctx.getDefaultHintContext();
h.setHint(PanZoomRotateHandler.KEY_ZOOM_IN_LIMIT, 10000.0);
h.setHint(PanZoomRotateHandler.KEY_ZOOM_OUT_LIMIT, 0.01);
+ h.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagramResource);
}
@Override
package org.simantics.district.network.ui.internal;
-import org.osgi.framework.BundleActivator;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import org.simantics.district.network.ui.breakdown.SubgraphProvider;
+import org.simantics.district.route.RouteService;
-public class Activator implements BundleActivator {
+public class Activator extends AbstractUIPlugin {
public static final String PLUGIN_ID = "org.simantics.district.network.ui";
+
private static Activator instance;
private static BundleContext context;
+
private ServiceTracker<SubgraphProvider, SubgraphProvider> subgraphProviderTracker;
+ private ServiceTracker<RouteService, RouteService> routeServiceTracker;
@Override
public void start(BundleContext context) throws Exception {
subgraphProviderTracker = new ServiceTracker<>(context, SubgraphProvider.class.getName(), null);
subgraphProviderTracker.open();
+ routeServiceTracker = new ServiceTracker<>(context, RouteService.class.getName(), null);
+ routeServiceTracker.open();
}
@Override
public void stop(BundleContext context) throws Exception {
subgraphProviderTracker.close();
+ routeServiceTracker.close();
Activator.instance = null;
Activator.context = null;
return subgraphProviderTracker.getServices(new SubgraphProvider[0]);
}
+ public RouteService getRouteService() {
+ return routeServiceTracker.getService();
+ }
+
}
import org.simantics.g2d.element.ElementHints;
import org.simantics.g2d.element.IElement;
import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;
+import org.simantics.scenegraph.g2d.events.KeyEvent;
import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
public class DNPointerInteractor extends PointerInteractor {
Set<IElement> elementsToDrag) {
return new DNTranslateMode(startCanvasPos, curCanvasPos, mouseId, elementsToDrag);
}
+
+ @EventHandler(priority = 1000)
+ public boolean enterroutingMode(KeyEvent ke) {
+ if (ke.character == 't' && ke instanceof KeyReleasedEvent) {
+ getContext().add(new RoutingMode(0));
+ }
+ return false;
+ }
+
}
--- /dev/null
+package org.simantics.district.network.ui.participants;
+
+import org.simantics.g2d.canvas.IToolMode;
+import org.simantics.g2d.canvas.impl.ToolMode;
+
+public class Modes {
+
+ public static final IToolMode ROUTE_TOOL = new ToolMode("RouteTool");
+
+}
--- /dev/null
+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.window.Window;
+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.internal.Activator;
+import org.simantics.district.route.Route;
+import org.simantics.district.route.RouteService;
+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.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.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;
+
+ 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) {
+ Resource diagramResource = getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
+ LOGGER.info("Diagram resource: " + diagramResource);
+ route = routeService.createRoute("Current", diagramResource);
+ routeService.registerRoute(route);
+ }
+ }
+
+ @Override
+ public void removedFromContext(ICanvasContext ctx) {
+ if (cursor != null) {
+ cursor.remove();
+ cursor = null;
+ }
+ // Discard route if it hasn't been persisted before this
+ if (route != null) {
+ routeService.discardRoute(route);
+ route = null;
+ routeService = null;
+ }
+ super.removedFromContext(ctx);
+ }
+
+ @HintListener(Class = Selection.class, Field = "SELECTION0")
+ public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+ Collection<IElement> 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) {
+ Waypoint wp = route.createWaypoint(o);
+ route.addWaypoint(wp);
+ }
+ }
+
+ @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) {
+ routeService.persistRoute(route);
+ route = null;
+ dispose();
+ }
+ } else if (e instanceof CommandEvent) {
+ Command cmd = ((CommandEvent) e).command;
+ if (cmd.equals(Commands.CANCEL)) {
+ return dispose();
+ } else if (cmd.equals(Commands.RENAME)) {
+ InputDialog dialog = new InputDialog(
+ PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
+ "Rename Route",
+ "Route name",
+ route.getName(),
+ s -> s.trim().length() > 0 ? null : "Name must be non-empty");
+ if (dialog.open() == Window.OK) {
+ route.setName(dialog.getValue());
+ }
+ }
+ }
+ return false;
+ }
+
+ 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;
+ }
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.simantics.district.route.feature</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.pde.FeatureBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.FeatureNature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+bin.includes = feature.xml
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<feature
+ id="org.simantics.district.route.feature"
+ label="District Network Routing"
+ version="1.0.0.qualifier"
+ provider-name="Semantum Oy">
+
+ <description url="http://www.example.com/description">
+ [Enter Feature Description here.]
+ </description>
+
+ <copyright url="http://www.example.com/copyright">
+ [Enter Copyright Description here.]
+ </copyright>
+
+ <license url="http://www.example.com/license">
+ [Enter License Description here.]
+ </license>
+
+ <plugin
+ id="org.simantics.district.route.ui"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.simantics.district.route"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+ <plugin
+ id="org.simantics.district.route.ontology"
+ download-size="0"
+ install-size="0"
+ version="0.0.0"
+ unpack="false"/>
+
+</feature>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.simantics.district</groupId>
+ <artifactId>org.simantics.district.root</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.simantics.district.route.feature</artifactId>
+ <packaging>eclipse-feature</packaging>
+ <version>1.0.0-SNAPSHOT</version>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.simantics.district.route.ontology</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.simantics.graph.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.simantics.graph.nature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: http://www.simantics.org/DistrictNetworkRoutes
+Bundle-SymbolicName: org.simantics.district.route.ontology
+Bundle-Version: 1.0.0.qualifier
+Bundle-Vendor: Semantum Oy
+Require-Bundle: org.simantics.layer0,
+ org.simantics.district.network.ontology;bundle-version="1.0.0"
+Automatic-Module-Name: fi.vtt.apros.district.route.ontology
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Export-Package: org.simantics.district.route.ontology
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ graph.tg
--- /dev/null
+L0 = <http://www.simantics.org/Layer0-1.1>
+DN = <http://www.simantics.org/DistrictNetwork-1.0>
+DIA = <http://www.simantics.org/Diagram-2.2>
+
+DNR = <http://www.simantics.org/DistrictNetworkRoutes-1.0> : L0.Ontology
+ @L0.new
+ L0.HasResourceClass "org.simantics.district.route.ontology.RouteResource"
+
+DNR.RouteFolder <T L0.Entity
+DNR.Route <T L0.List
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.simantics.district</groupId>
+ <artifactId>org.simantics.district.root</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.simantics.district.route.ontology</artifactId>
+ <packaging>eclipse-plugin</packaging>
+ <version>1.0.0-SNAPSHOT</version>
+
+</project>
--- /dev/null
+package org.simantics.district.route.ontology;
+
+import org.simantics.db.RequestProcessor;
+import org.simantics.db.Resource;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.request.Read;
+import org.simantics.db.Session;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.service.QueryControl;
+
+public class RouteResource {
+
+ public final Resource Route;
+ public final Resource RouteFolder;
+
+ public static class URIs {
+ public static final String Route = "http://www.simantics.org/DistrictNetworkRoutes-1.0/Route";
+ public static final String RouteFolder = "http://www.simantics.org/DistrictNetworkRoutes-1.0/RouteFolder";
+ }
+
+ public static Resource getResourceOrNull(ReadGraph graph, String uri) {
+ try {
+ return graph.getResource(uri);
+ } catch(DatabaseException e) {
+ System.err.println(e.getMessage());
+ return null;
+ }
+ }
+
+ public RouteResource(ReadGraph graph) {
+ Route = getResourceOrNull(graph, URIs.Route);
+ RouteFolder = getResourceOrNull(graph, URIs.RouteFolder);
+ }
+
+ public static RouteResource getInstance(ReadGraph graph) {
+ Session session = graph.getSession();
+ RouteResource ret = session.peekService(RouteResource.class);
+ if(ret == null) {
+ QueryControl qc = graph.getService(QueryControl.class);
+ ret = new RouteResource(qc.getIndependentGraph(graph));
+ session.registerService(RouteResource.class, ret);
+ }
+ return ret;
+ }
+
+ public static RouteResource getInstance(RequestProcessor session) throws DatabaseException {
+ RouteResource ret = session.peekService(RouteResource.class);
+ if(ret == null) {
+ ret = session.syncRequest(new Read<RouteResource>() {
+ public RouteResource perform(ReadGraph graph) throws DatabaseException {
+ QueryControl qc = graph.getService(QueryControl.class);
+ return new RouteResource(qc.getIndependentGraph(graph));
+ }
+ });
+ session.registerService(RouteResource.class, ret);
+ }
+ return ret;
+ }
+
+}
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.simantics.district.route.ui</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: District Network Modelling Route UI
+Bundle-SymbolicName: org.simantics.district.route.ui;singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.simantics.district.route.ui.internal.Activator
+Bundle-Vendor: Semantum Oy
+Require-Bundle: org.eclipse.e4.ui.di,
+ org.eclipse.e4.ui.model.workbench,
+ org.eclipse.e4.core.di.annotations,
+ org.eclipse.e4.core.services,
+ org.simantics.ui,
+ org.simantics.district.route,
+ org.slf4j.api,
+ org.eclipse.e4.ui.workbench,
+ org.eclipse.e4.core.di,
+ org.eclipse.e4.ui.services
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Automatic-Module-Name: fi.vtt.apros.district.route.ui
+Import-Package: javax.annotation;version="1.0.0";resolution:=optional,
+ javax.inject;version="1.0.0",
+ org.osgi.service.event;version="1.3.1"
+Bundle-ActivationPolicy: lazy
--- /dev/null
+#Properties file for fi.apros.visualization.table
+view.name = Routes
+command.commandname.1 = Open Routes View
+command.commandname.2 = Select Route on Diagram
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml,\
+ fragment.e4xmi,\
+ OSGI-INF/
--- /dev/null
+<?xml version="1.0" encoding="ASCII"?>
+<fragment:ModelFragments xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:commands="http://www.eclipse.org/ui/2010/UIModel/application/commands" xmlns:fragment="http://www.eclipse.org/ui/2010/UIModel/fragment" xmlns:menu="http://www.eclipse.org/ui/2010/UIModel/application/ui/menu" xmi:id="_BxaXACerEeWxCPrV0pAZQQ">
+ <fragments xsi:type="fragment:StringModelFragment" xmi:id="_n2jsIMq-EeeUz6Cs9kKeKg" featurename="categories" parentElementId="xpath:/"/>
+ <fragments xsi:type="fragment:StringModelFragment" xmi:id="_4fyr4MqVEeeUz6Cs9kKeKg" featurename="commands" parentElementId="xpath:/">
+ <elements xsi:type="commands:Command" xmi:id="_x1nG0H4WEei74IuRct85qQ" elementId="org.simantics.district.route.ui.commands.openRouteView" commandName="%command.commandname.1"/>
+ <elements xsi:type="commands:Command" xmi:id="_k63rkP6zEeiBo8tg-6EPYA" elementId="org.simantics.district.route.ui.command.selectRouteOnDiagram" commandName="%command.commandname.2"/>
+ </fragments>
+ <fragments xsi:type="fragment:StringModelFragment" xmi:id="_1Ds_gMqVEeeUz6Cs9kKeKg" featurename="handlers" parentElementId="xpath:/">
+ <elements xsi:type="commands:Handler" xmi:id="_1G5RYH4WEei74IuRct85qQ" elementId="org.simantics.district.route.ui.handlers.openRouteView" contributionURI="bundleclass://org.simantics.district.route.ui/org.simantics.district.route.ui.OpenRouteView" command="_x1nG0H4WEei74IuRct85qQ"/>
+ <elements xsi:type="commands:Handler" xmi:id="_O9tbQP60EeiBo8tg-6EPYA" elementId="org.simantics.district.route.ui.handlers.selectRouteOnDiagram" contributionURI="bundleclass://org.simantics.district.route.ui/org.simantics.district.route.ui.handlers.SelectRouteOnDiagram" command="_k63rkP6zEeiBo8tg-6EPYA"/>
+ </fragments>
+ <fragments xsi:type="fragment:StringModelFragment" xmi:id="_Fso08MrIEeeUz6Cs9kKeKg" featurename="toolBarContributions" parentElementId="xpath:/"/>
+ <fragments xsi:type="fragment:StringModelFragment" xmi:id="_WNB8EH4oEei74IuRct85qQ" featurename="trimContributions" parentElementId="xpath:/">
+ <elements xsi:type="menu:TrimContribution" xmi:id="_ZW7FMH4oEei74IuRct85qQ" elementId="org.simantics.district.route.ui.trimcontribution.0" parentId="org.eclipse.ui.main.toolbar">
+ <children xsi:type="menu:ToolBar" xmi:id="_tRL0MH4oEei74IuRct85qQ" elementId="org.simantics.district.route.ui.toolbar.0">
+ <children xsi:type="menu:HandledToolItem" xmi:id="_uWPXwH4oEei74IuRct85qQ" elementId="org.simantics.district.route.ui.handledtoolitem.openRouteView" label="" iconURI="platform:/plugin/com.famfamfam.silk/icons/chart_line.png" command="_x1nG0H4WEei74IuRct85qQ"/>
+ </children>
+ </elements>
+ </fragments>
+ <fragments xsi:type="fragment:StringModelFragment" xmi:id="_YrKV8H4pEei74IuRct85qQ" featurename="menuContributions" parentElementId="xpath:/">
+ <elements xsi:type="menu:MenuContribution" xmi:id="_azWxgH4pEei74IuRct85qQ" elementId="org.simantics.district.route.ui.menucontribution.0" parentId="popup:#DistrictDiagramPopup">
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_laBOMH4pEei74IuRct85qQ" elementId="org.simantics.district.route.ui.handledmenuitem.openRouteView" label="" command="_x1nG0H4WEei74IuRct85qQ"/>
+ </elements>
+ <elements xsi:type="menu:MenuContribution" xmi:id="_Y4vWcP6zEeiBo8tg-6EPYA" elementId="org.simantics.district.route.ui.menucontribution.1" parentId="org.simantics.district.route.ui.contextMenu">
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_fH1V8P6zEeiBo8tg-6EPYA" elementId="org.simantics.district.route.ui.handledmenuitem.selectRouteOnDiagram" command="_k63rkP6zEeiBo8tg-6EPYA"/>
+ </elements>
+ </fragments>
+</fragment:ModelFragments>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+ <extension
+ id="org.simantics.district.route.ui.fragment"
+ point="org.eclipse.e4.workbench.model">
+ <fragment
+ apply="initial"
+ uri="fragment.e4xmi">
+ </fragment>
+ </extension>
+ <extension
+ point="org.eclipse.ui.views">
+ <e4view
+ class="org.simantics.district.route.ui.RouteView"
+ icon="platform:/plugin/com.famfamfam.silk/icons/chart_line.png"
+ id="org.simantics.district.route.ui.routeView"
+ name="%view.name"
+ restorable="true"
+ allowMultiple="true">
+ </e4view>
+ </extension>
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.simantics.district</groupId>
+ <artifactId>org.simantics.district.root</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.simantics.district.route.ui</artifactId>
+ <packaging>eclipse-plugin</packaging>
+ <version>1.0.0-SNAPSHOT</version>
+
+</project>
--- /dev/null
+package org.simantics.district.route.ui;
+
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+
+public class OpenRouteView {
+
+ @CanExecute
+ public boolean canExecute(ESelectionService selectionService) {
+ return true;
+ }
+
+ @Execute
+ public void openPropertyTable(ESelectionService selectionService) {
+ RouteUI.openRouteView();
+ }
+
+}
--- /dev/null
+package org.simantics.district.route.ui;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreePath;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.simantics.db.Resource;
+import org.simantics.district.route.Route;
+import org.simantics.district.route.RouteEvent;
+import org.simantics.district.route.RouteJob;
+import org.simantics.district.route.RouteService;
+import org.simantics.district.route.RouteServiceListener;
+import org.simantics.district.route.Waypoint;
+import org.simantics.district.route.ui.internal.Activator;
+import org.simantics.utils.ui.SWTUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 6.09
+ */
+public class RouteTree extends Composite {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RouteTree.class);
+
+ ESelectionService selectionService;
+
+ private final LocalResourceManager resourceManager;
+
+ private TreeViewer tree;
+
+ public RouteTree(Composite parent, int style, ESelectionService selectionService) {
+ super(parent, style);
+ this.selectionService = selectionService;
+ this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
+ defaultInitializeUI();
+ trackRouteInput();
+ }
+
+ private RouteServiceListener routeServiceListener = e -> {
+ System.out.println("Route event: " + e);
+ switch (e.type) {
+ case RouteEvent.TYPE_ROUTE_DISCARDED:
+ runUi(() -> tree.refresh());
+ break;
+
+ case RouteEvent.TYPE_ROUTE_REGISTERED:
+ runUi(() -> {
+ tree.refresh();
+ tree.setExpandedState(e.obj, true);
+ });
+ break;
+
+ case RouteEvent.TYPE_ROUTE_MODIFIED:
+ runUi(() -> {
+ tree.refresh(e.obj);
+ tree.setExpandedState(e.obj, true);
+ });
+ break;
+
+ case RouteEvent.TYPE_ROUTE_RENAMED:
+ runUi(() -> tree.refresh(e.obj));
+ break;
+
+ case RouteEvent.TYPE_ROUTE_SOURCE_CHANGED:
+ runUi(() -> tree.setInput(e.service.listRoutes()));
+ break;
+ }
+ };
+
+ private void trackRouteInput() {
+ RouteService rs = Activator.getDefault().getRouteService();
+ if (rs != null) {
+ rs.addListener(routeServiceListener);
+ addDisposeListener(e -> rs.removeListener(routeServiceListener));
+ setInput(rs.listRoutes());
+ }
+ }
+
+ private void defaultInitializeUI() {
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(this);
+ GridLayoutFactory.fillDefaults().numColumns(1).applyTo(this);
+
+ createTree(this);
+ }
+
+ private void createTree(Composite parent) {
+ tree = new TreeViewer(parent, SWT.SINGLE);
+ tree.setUseHashlookup(true);
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(tree.getControl());
+ tree.setContentProvider(new ContentProvider());
+ tree.setLabelProvider(new LabelProvider());
+ tree.addSelectionChangedListener(this::treeSelectionChanged);
+ //tree.addPostSelectionChangedListener(this::treeSelectionChanged);
+ tree.addDoubleClickListener(this::itemDoubleClicked);
+ // TODO: add route renaming (F2), DnD reordering support
+ }
+
+ private void itemDoubleClicked(DoubleClickEvent e) {
+ // TODO: default action, i.e. highlight route?
+ LOGGER.info("double click {}", e);
+ }
+
+ private void treeSelectionChanged(SelectionChangedEvent e) {
+ selectionService.setSelection(e.getSelection());
+ /*
+ IStructuredSelection ss = (IStructuredSelection) e.getSelection();
+ Object[] arr = ss.toArray();
+ if (arr.length == 1) {
+ Object o = arr[0];
+ if (o instanceof Route) {
+ CompletableFuture<List<Resource>> callback = new CompletableFuture<>();
+ callback.thenAccept(dnElements -> {
+ runUi(() -> selectionService.setSelection(new StructuredSelection(dnElements)));
+ });
+ new RouteJob((Route) o, callback).schedule();
+ } else if (o instanceof Waypoint) {
+ selectionService.setSelection(new StructuredSelection(((Waypoint) o).<Object>getObject()));
+ }
+ }
+ */
+ }
+
+ protected void setInput(List<Route> result) {
+ runUi(() -> {
+ TreePath[] paths = tree.getExpandedTreePaths();
+ tree.setInput(result);
+ tree.setExpandedTreePaths(paths);
+ });
+ }
+
+ protected void runUi(Runnable r) {
+ SWTUtils.asyncExec(getDisplay(), () -> {
+ if (!tree.getControl().isDisposed())
+ r.run();
+ });
+ }
+
+ private static class ContentProvider implements ITreeContentProvider {
+
+ private static final Object[] NONE = {};
+ @SuppressWarnings("unused")
+ private List<?> input;
+
+ @Override
+ public void dispose() {
+ input = null;
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ input = newInput instanceof List ? (List<?>) newInput : null;
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ if (inputElement instanceof List)
+ return ((List<?>) inputElement).toArray();
+ return NONE;
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof Route) {
+ return ((Route) parentElement).waypoints().toArray();
+ }
+ return NONE;
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ return null;
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ if (element instanceof Route) {
+ return !((Route) element).waypoints().isEmpty();
+ }
+ return false;
+ }
+
+ }
+
+ private static class LabelProvider extends ColumnLabelProvider {
+ @Override
+ public String getText(Object element) {
+ if (element instanceof Route) {
+ Route r = (Route) element;
+ return NLS.bind("{0} ({1} waypoints)", r.getName(), r.count());
+ } else if (element instanceof Waypoint) {
+ }
+ return element.toString();
+ }
+ @Override
+ public void update(ViewerCell cell) {
+ Object element = cell.getElement();
+ cell.setText(getText(element));
+ }
+ }
+
+}
--- /dev/null
+package org.simantics.district.route.ui;
+
+import org.simantics.ui.workbench.e4.E4WorkbenchUtils;
+
+/**
+ * Facade
+ *
+ * @author Tuukka Lehtonen
+ * @since 6.09
+ */
+public class RouteUI {
+
+ public static void openRouteView() {
+ E4WorkbenchUtils.openAndShowPart("org.simantics.district.route.ui.routeView"); //$NON-NLS-1$
+ }
+
+}
--- /dev/null
+package org.simantics.district.route.ui;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class RouteUIConstants {
+
+ public static final String TOPIC_ROUTE_UI = "TOPIC_ROUTE_UI"; //$NON-NLS-1$
+ public static final String TOPIC_ROUTE_UI_NEW_ROUTE = TOPIC_ROUTE_UI + "/NEW_ROUTE"; //$NON-NLS-1$
+ public static final String TOPIC_ROUTE_UI_ADD_POINT = TOPIC_ROUTE_UI + "/ADD_POINT"; //$NON-NLS-1$
+
+ public static final String EVENT_ROUTE_NAME = "event.route.name"; //$NON-NLS-1$
+ public static final String EVENT_ROUTE = "event.route.instance"; //$NON-NLS-1$
+ public static final String EVENT_ROUTE_POINT = "event.route.point"; //$NON-NLS-1$
+
+}
--- /dev/null
+package org.simantics.district.route.ui;
+
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+
+import org.eclipse.e4.core.services.events.IEventBroker;
+import org.eclipse.e4.ui.di.Focus;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.model.application.ui.menu.MMenuContribution;
+import org.eclipse.e4.ui.model.application.ui.menu.MMenuFactory;
+import org.eclipse.e4.ui.model.application.ui.menu.MPopupMenu;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 6.08
+ */
+public class RouteView {
+
+ private static final String POPUP_CONTRIBUTION_ID = "org.simantics.district.route.ui.contextMenu";
+ public static final String POPUP_ELEMENT_ID = "org.simantics.district.route.ui.popup";
+
+ @Inject ESelectionService selectionService;
+ @Inject IEventBroker broker;
+ RouteTree ui;
+
+ /**
+ * See
+ * http://www.vogella.com/tutorials/EclipsePlugin/article.html#adding-eclipse-4-x-parts-to-eclipse-3-x-applications-via-the-code-org-eclipse-ui-views-code-extension-point
+ *
+ * Cannot contribute context menu via fragment at this point
+ */
+ @Inject
+ public void init(MPart part, MApplication app) {
+ MPopupMenu popupMenu = MMenuFactory.INSTANCE.createPopupMenu();
+ popupMenu.setElementId(POPUP_ELEMENT_ID);
+ List<MMenuContribution> menuContributions = app.getMenuContributions();
+ for (MMenuContribution menuContribution : menuContributions) {
+ if (POPUP_CONTRIBUTION_ID.equals(menuContribution.getParentId())) {
+ // ok, add to menu
+ popupMenu.getChildren().addAll(menuContribution.getChildren());
+ }
+ }
+ part.getMenus().add(popupMenu);
+ }
+
+ @PostConstruct
+ public void postConstruct(Composite parent) {
+ this.ui = new RouteTree(parent, 0, selectionService);
+ }
+
+ @PreDestroy
+ public void dispose() {
+ ui = null;
+ }
+
+ @Focus
+ public void onFocus() {
+ ui.setFocus();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.simantics.district.route.ui.handlers;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import javax.inject.Named;
+
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.widgets.Shell;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.district.route.Route;
+import org.simantics.district.route.RouteJob;
+import org.simantics.district.route.RouterConfiguration;
+import org.simantics.utils.ui.ISelectionUtils;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class SelectRouteOnDiagram {
+
+ @Execute
+ public void execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell activeShell,
+ @Named(IServiceConstants.ACTIVE_PART) MPart part,
+ @Named(IServiceConstants.ACTIVE_SELECTION) ISelection selection) {
+ // get selected route
+ Route route = ISelectionUtils.filterSingleSelection(selection, Route.class);
+ if (route == null)
+ return;
+
+ CompletableFuture<List<Variable>> result = new CompletableFuture<>();
+ RouterConfiguration config = new RouterConfiguration();
+ new RouteJob(config, route, result).schedule();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.simantics.district.route.ui.internal;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+import org.simantics.district.route.RouteService;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.simantics.district.route.ui"; //$NON-NLS-1$
+
+ // The shared instance
+ private static Activator plugin;
+
+ private ServiceTracker<RouteService, ?> routeServiceTracker;
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+
+ // create a tracker and track the service
+ routeServiceTracker = new ServiceTracker<>(context, RouteService.class.getName(), null);
+ routeServiceTracker.open();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ // close the service tracker
+ routeServiceTracker.close();
+ routeServiceTracker = null;
+
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static Activator getDefault() {
+ return plugin;
+ }
+
+ public RouteService getRouteService() {
+ return (RouteService) routeServiceTracker.getService();
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.simantics.district.route</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: District Network Routing
+Bundle-SymbolicName: org.simantics.district.route
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.simantics.district.route.internal.Activator
+Bundle-Vendor: Semantum Oy
+Require-Bundle: org.eclipse.core.runtime,
+ org.simantics.db.layer0;bundle-version="1.1.0",
+ org.simantics;bundle-version="1.0.0",
+ org.simantics.district.route.ontology;bundle-version="1.0.0",
+ org.slf4j.api;bundle-version="1.7.25",
+ org.simantics.diagram.ontology,
+ org.simantics.district.network.ontology
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Automatic-Module-Name: fi.vtt.apros.district.route
+Bundle-ActivationPolicy: lazy
+Export-Package: org.simantics.district.route
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.simantics.district</groupId>
+ <artifactId>org.simantics.district.root</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.simantics.district.route</artifactId>
+ <packaging>eclipse-plugin</packaging>
+ <version>1.0.0-SNAPSHOT</version>
+
+</project>
--- /dev/null
+package org.simantics.district.route;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public interface Route {
+
+ default String getName() {
+ return "empty"; //$NON-NLS-1$
+ }
+
+ void setName(String name);
+
+ Waypoint createWaypoint(Object backend);
+
+ default void addWaypoint(Waypoint r) {
+ addWaypoint(count(), r);
+ }
+
+ default void addWaypoint(int index, Waypoint r) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removes the specified waypoint from the route
+ */
+ default void removeWaypoint(Waypoint r) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @return amount of points in the route
+ */
+ default int count() {
+ return waypoints().size();
+ }
+
+ default List<Waypoint> waypoints() {
+ return Collections.emptyList();
+ }
+
+ // TODO: reorder route points
+
+}
--- /dev/null
+package org.simantics.district.route;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 6.09
+ */
+public class RouteEvent {
+
+ public static final int TYPE_ROUTER_REGISTERED = 1;
+ public static final int TYPE_ROUTER_UNREGISTERED = 2;
+
+ public static final int TYPE_ROUTE_CREATED = 3;
+ public static final int TYPE_ROUTE_REGISTERED = 4;
+ public static final int TYPE_ROUTE_DISCARDING = 5;
+ public static final int TYPE_ROUTE_DISCARDED = 6;
+ public static final int TYPE_ROUTE_RENAMED = 7;
+ public static final int TYPE_ROUTE_MODIFIED = 8;
+ public static final int TYPE_ROUTE_PERSISTING = 9;
+ public static final int TYPE_ROUTE_PERSISTED = 10;
+ public static final int TYPE_ROUTE_SOURCE_CHANGED = 11;
+
+ public final RouteService service;
+ public final int type;
+ public final Object obj;
+
+ public RouteEvent(RouteService service, int type, Object obj) {
+ this.service = service;
+ this.type = type;
+ this.obj = obj;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[type=" + typeString(type) + ", obj=" + obj + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ private static String typeString(int type) {
+ switch (type) {
+ case TYPE_ROUTER_REGISTERED: return "ROUTER REGISTERED"; //$NON-NLS-1$
+ case TYPE_ROUTER_UNREGISTERED: return "ROUTER UNREGISTERED"; //$NON-NLS-1$
+ case TYPE_ROUTE_CREATED: return "ROUTE CREATED"; //$NON-NLS-1$
+ case TYPE_ROUTE_REGISTERED: return "ROUTE REGISTERED"; //$NON-NLS-1$
+ case TYPE_ROUTE_DISCARDING: return "ROUTE DISCARDING"; //$NON-NLS-1$
+ case TYPE_ROUTE_DISCARDED: return "ROUTE DISCARDED"; //$NON-NLS-1$
+ case TYPE_ROUTE_RENAMED: return "ROUTE RENAMED"; //$NON-NLS-1$
+ case TYPE_ROUTE_MODIFIED: return "ROUTE MODIFIED"; //$NON-NLS-1$
+ case TYPE_ROUTE_PERSISTING: return "ROUTE PERSISTING"; //$NON-NLS-1$
+ case TYPE_ROUTE_PERSISTED: return "ROUTE PERSISTED"; //$NON-NLS-1$
+ case TYPE_ROUTE_SOURCE_CHANGED: return "ROUTE SOURCE CHANGED"; //$NON-NLS-1$
+ default: return "" + type; //$NON-NLS-1$
+ }
+ }
+
+}
--- /dev/null
+package org.simantics.district.route;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.simantics.ObjectIdentitySchedulingRule;
+import org.simantics.db.Resource;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.district.route.internal.Activator;
+import org.simantics.district.route.internal.RoutePersistence;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 6.09
+ */
+public class RouteJob extends Job {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RouteJob.class);
+
+ private RouterConfiguration config;
+ private List<Resource> waypoints;
+ private CompletableFuture<List<Variable>> callback;
+
+ public RouteJob(RouterConfiguration config, Route route, CompletableFuture<List<Variable>> callback) {
+ super("Compute route");
+ Objects.requireNonNull(callback, "Callback must be non-null");
+ setUser(true);
+ setRule(new ObjectIdentitySchedulingRule(route));
+ this.config = config;
+ this.waypoints = RoutePersistence.toResources(route.waypoints());
+ this.callback = callback;
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ List<Router> routers = Activator.getInstance().getRouteService().routers();
+ for (Router router : routers) {
+ try {
+ List<Variable> path = router.findShortestPath(config, waypoints);
+ if (!path.isEmpty()) {
+ callback.complete(path);
+ return Status.OK_STATUS;
+ }
+ } catch (RoutingException e) {
+ LOGGER.error("Routing failed for waypoints {} and router {}", waypoints, router, e);
+ callback.completeExceptionally(e);
+ }
+ }
+ LOGGER.info("No router could calculate route between waypoints {}. Available routers: {}", waypoints, routers);
+ return Status.OK_STATUS;
+ }
+
+}
--- /dev/null
+package org.simantics.district.route;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public interface RouteService {
+
+ void addListener(RouteServiceListener l);
+
+ void removeListener(RouteServiceListener l);
+
+ Route createRoute(String name, Object backendModelEntity);
+
+ void registerRoute(Route route);
+
+ CompletableFuture<Route> persistRoute(Route route);
+
+ CompletableFuture<?> discardRoute(Route route);
+
+ List<Route> listRoutes();
+
+ void registerRouter(Router router);
+
+ void unregisterRouter(Router router);
+
+ List<Router> routers();
+
+}
--- /dev/null
+package org.simantics.district.route;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 6.09
+ */
+@FunctionalInterface
+public interface RouteServiceListener {
+
+ void handleEvent(RouteEvent e);
+
+}
--- /dev/null
+package org.simantics.district.route;
+
+import java.util.List;
+
+import org.simantics.db.Resource;
+import org.simantics.db.layer0.variable.Variable;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public interface Router {
+
+ /**
+ * @return descriptive name of the router
+ */
+ default String name() {
+ return toString();
+ }
+
+ /**
+ * Must be invoked outside of any transaction realm, like a database request or
+ * experiment thread, preferably from a background job thread.
+ *
+ * @param wayPoints waypoints for the route to find in visiting order. The
+ * resources must represents district network diagram elements.
+ * @return the piece-wise shortest path between the specified waypoints as a
+ * fully baked path of district network diagram element resources
+ * @throws RoutingException in case of any problems in routing
+ */
+ List<Variable> findShortestPath(RouterConfiguration config, List<Resource> wayPoints) throws RoutingException;
+
+}
--- /dev/null
+package org.simantics.district.route;
+
+import java.util.HashMap;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class RouterConfiguration extends HashMap<String, Object> {
+
+ private static final long serialVersionUID = -6852913957185485390L;
+
+}
--- /dev/null
+package org.simantics.district.route;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 6.09
+ */
+public class RoutingException extends Exception {
+
+ private static final long serialVersionUID = -718352796086955155L;
+
+ public RoutingException(String message) {
+ super(message);
+ }
+
+ public RoutingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
--- /dev/null
+package org.simantics.district.route;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public interface Waypoint {
+
+ String getLabel();
+ <T> T getObject();
+
+}
--- /dev/null
+package org.simantics.district.route.internal;
+
+import java.util.Hashtable;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+import org.simantics.district.route.RouteService;
+import org.simantics.district.route.Router;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 6.09
+ */
+public class Activator implements BundleActivator, ServiceListener {
+
+ private static BundleContext context;
+ private static Activator instance;
+
+ static BundleContext getContext() {
+ return context;
+ }
+
+ public static Activator getInstance() {
+ return instance;
+ }
+
+ private RouteServiceImpl routeService;
+ private ServiceRegistration<?> routeServiceRegistration;
+ private ServiceTracker<RouteService, ?> routeServiceTracker;
+ private ServiceTracker<Router, ?> routerTracker;
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ Activator.instance = this;
+ Activator.context = context;
+ routeService = new RouteServiceImpl();
+
+ // register the service
+ routeServiceRegistration = context.registerService(RouteService.class.getName(), routeService, new Hashtable<>());
+
+ // create a tracker and track the service
+ routeServiceTracker = new ServiceTracker<>(context, RouteService.class.getName(), null);
+ routeServiceTracker.open();
+
+ // Register initially availble routers
+ routerTracker = new ServiceTracker<>(context, Router.class.getName(), null);
+ routerTracker.open();
+ for (Object router : routerTracker.getServices()) {
+ routeService.registerRouter((Router) router);
+ }
+
+ // have a service listener to implement the whiteboard pattern
+ context.addServiceListener(this, "(" + Constants.OBJECTCLASS + "=" + Router.class.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ // grab the service
+ routeService = (RouteServiceImpl) routeServiceTracker.getService();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ // close the service tracker
+ routeServiceTracker.close();
+ routeServiceTracker = null;
+
+ routeServiceRegistration.unregister();
+
+ routeService.close();
+ routeService = null;
+ context = null;
+ instance = null;
+ }
+
+ public void serviceChanged(ServiceEvent ev) {
+ ServiceReference<?> sr = ev.getServiceReference();
+ switch (ev.getType()) {
+ case ServiceEvent.REGISTERED:
+ routeService.registerRouter((Router) context.getService(sr));
+ break;
+ case ServiceEvent.UNREGISTERING:
+ routeService.unregisterRouter((Router) context.getService(sr));
+ break;
+ }
+ }
+
+ public RouteService getRouteService() {
+ return routeService;
+ }
+
+}
--- /dev/null
+package org.simantics.district.route.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.simantics.db.Resource;
+import org.simantics.district.route.Route;
+import org.simantics.district.route.RouteEvent;
+import org.simantics.district.route.Waypoint;
+
+public class RouteImpl implements Route {
+
+ private String name;
+ private Resource model;
+ private Resource backend;
+
+ private RouteServiceImpl rs;
+
+ private List<Waypoint> waypoints;
+ private List<Waypoint> unmodifiableWaypoints;
+
+ public RouteImpl(String name) {
+ this.name = name;
+ routeService(rs);
+ waypoints(new ArrayList<>());
+ }
+
+ public RouteImpl modelEntity(Resource model) {
+ this.model = model;
+ return this;
+ }
+
+ public Resource modelEntity() {
+ return model;
+ }
+
+ public RouteImpl backend(Resource backend) {
+ this.backend = backend;
+ return this;
+ }
+
+ public Resource backend() {
+ return backend;
+ }
+
+ public RouteImpl waypoints(List<Waypoint> waypoints) {
+ this.waypoints = waypoints;
+ this.unmodifiableWaypoints = Collections.unmodifiableList(waypoints);
+ return this;
+ }
+
+ public RouteImpl routeService(RouteServiceImpl rs) {
+ this.rs = rs;
+ return this;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ fireEvent(RouteEvent.TYPE_ROUTE_RENAMED);
+ }
+
+ @Override
+ public Waypoint createWaypoint(Object backend) {
+ if (backend instanceof Resource)
+ return new WaypointImpl((Resource) backend, "Point 1");
+ throw new IllegalArgumentException("only Resource type waypoints supported, got " + backend); //$NON-NLS-1$
+ }
+
+ @Override
+ public void addWaypoint(int index, Waypoint r) {
+ waypoints.add(index, r);
+ fireEvent(RouteEvent.TYPE_ROUTE_MODIFIED);
+ }
+
+ @Override
+ public void removeWaypoint(Waypoint r) {
+ waypoints.remove(r);
+ fireEvent(RouteEvent.TYPE_ROUTE_MODIFIED);
+ }
+
+ @Override
+ public List<Waypoint> waypoints() {
+ return unmodifiableWaypoints;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s@%x [%s, waypoints=%s]",
+ getClass().getName(),
+ System.identityHashCode(this),
+ name,
+ waypoints.toString());
+ }
+
+ private void fireEvent(int type) {
+ if (rs != null)
+ rs.fireEvent(type, this);
+ }
+
+}
--- /dev/null
+package org.simantics.district.route.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import org.eclipse.osgi.util.NLS;
+import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.util.ObjectUtils;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.ObjectsWithType;
+import org.simantics.db.common.request.UniqueRead;
+import org.simantics.db.common.utils.ListUtils;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.QueryIndexUtils;
+import org.simantics.db.layer0.request.PossibleActiveModel;
+import org.simantics.db.layer0.util.RemoverUtil;
+import org.simantics.district.network.ontology.DistrictNetworkResource;
+import org.simantics.district.route.Waypoint;
+import org.simantics.district.route.ontology.RouteResource;
+import org.simantics.layer0.Layer0;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class RoutePersistence {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RoutePersistence.class);
+
+ public static Resource getRouteFolder(ReadGraph graph, Resource model) throws DatabaseException {
+ List<Resource> diagrams = QueryIndexUtils.searchByType(graph, model, RouteResource.getInstance(graph).RouteFolder);
+ if (diagrams.size() > 0)
+ return diagrams.get(0);
+ return null;
+ }
+
+ public static Resource getOrCreateRouteFolder(WriteGraph graph, Resource model) throws DatabaseException {
+ Resource rf = getRouteFolder(graph, model);
+ if (rf == null) {
+ Layer0 L0 = Layer0.getInstance(graph);
+ RouteResource RR = RouteResource.getInstance(graph);
+ rf = graph.newResource();
+ graph.claim(rf, L0.InstanceOf, null, RR.RouteFolder);
+ graph.claimLiteral(rf, L0.HasName, L0.NameOf, L0.String, UUID.randomUUID().toString(), Bindings.STRING);
+ graph.claim(model, L0.ConsistsOf, L0.PartOf, rf);
+ }
+ return rf;
+ }
+
+ public static Resource createRoute(WriteGraph graph, Resource model, String label, List<Resource> waypoints) throws DatabaseException {
+ Resource rf = getOrCreateRouteFolder(graph, model);
+
+ RouteResource RR = RouteResource.getInstance(graph);
+ Layer0 L0 = Layer0.getInstance(graph);
+
+ Resource route = ListUtils.create(graph, RR.Route, waypoints);
+ graph.claim(rf, L0.ConsistsOf, L0.PartOf, route);
+ graph.claimLiteral(route, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);
+ graph.claimLiteral(route, L0.HasLabel, label, Bindings.STRING);
+
+ LOGGER.info("Persisted route {} with label {} and waypoints {}", route, label, waypoints);
+
+ return route;
+ }
+
+ public static void updateRoute(WriteGraph graph, Resource route, String label, List<Resource> waypoints) throws DatabaseException {
+ RouteResource RR = RouteResource.getInstance(graph);
+ Layer0 L0 = Layer0.getInstance(graph);
+
+ String existingLabel = graph.getPossibleRelatedValue(route, L0.HasLabel, Bindings.STRING);
+ if (ObjectUtils.objectEquals(existingLabel, label)) {
+ graph.claimLiteral(route, L0.HasLabel, label, Bindings.STRING);
+ }
+
+ List<Resource> existingWaypoints = ListUtils.toList(graph, route);
+ if (!existingWaypoints.equals(waypoints)) {
+ // TODO: optimize this
+ ListUtils.removeElements(graph, route, new HashSet<>(existingWaypoints));
+ ListUtils.createExisting(graph, RR.Route, waypoints);
+ }
+
+ LOGGER.info("Updated route {} with label {} and waypoints {}", route, label, waypoints);
+ }
+
+ public static void removeRoute(WriteGraph graph, Resource route) throws DatabaseException {
+ RemoverUtil.remove(graph, route);
+ }
+
+ public static Waypoint toWaypoint(ReadGraph graph, Resource waypoint) throws DatabaseException {
+ DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
+ Set<Resource> types = graph.getTypes(waypoint);
+ String name = NameUtils.getSafeName(graph, waypoint);
+ if (types.contains(DN.Vertex)) {
+ String address = graph.getPossibleRelatedValue(waypoint, DN.Vertex_HasAddress, Bindings.STRING);
+ String label = address != null
+ ? NLS.bind("Node {0} - {1}", name, address).trim()
+ : NLS.bind("Node {0}", name, address).trim();
+ return new WaypointImpl(waypoint, label);
+ } else if (types.contains(DN.Edge)) {
+ return new WaypointImpl(waypoint, NLS.bind("Edge {0}", name));
+ }
+ LOGGER.warn("Tried to convert unrecognized resource {} to a route waypoint", waypoint);
+ return null;
+ }
+
+ public static List<Waypoint> toWaypoints(ReadGraph graph, List<Resource> waypoints) throws DatabaseException {
+ List<Waypoint> result = new ArrayList<>();
+ for (Resource wpr : waypoints) {
+ Waypoint wp = toWaypoint(graph, wpr);
+ if (wp != null)
+ result.add(wp);
+ }
+ return result;
+ }
+
+ public static List<RouteImpl> findRoutes(ReadGraph graph, Resource model) throws DatabaseException {
+ Resource rf = getRouteFolder(graph, model);
+ if (rf == null)
+ return Collections.emptyList();
+
+ Layer0 L0 = Layer0.getInstance(graph);
+ RouteResource RR = RouteResource.getInstance(graph);
+
+ List<RouteImpl> routes = new ArrayList<>();
+
+ for (Resource route : graph.syncRequest(new ObjectsWithType(rf, L0.ConsistsOf, RR.Route))) {
+ RouteImpl ri = new RouteImpl(graph.getRelatedValue(route, L0.HasLabel, Bindings.STRING))
+ .backend(route)
+ .modelEntity(model)
+ .waypoints(toWaypoints(graph, ListUtils.toList(graph, route)));
+ routes.add(ri);
+ }
+
+ return routes;
+ }
+
+ public static class ActiveModelRoutesRequest extends UniqueRead<List<RouteImpl>> {
+ @Override
+ public List<RouteImpl> perform(ReadGraph graph) throws DatabaseException {
+ return findRoutes(graph, graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource())));
+ }
+ }
+
+ public static List<Resource> toResources(List<Waypoint> waypoints) {
+ return waypoints.stream()
+ .<Resource>map(Waypoint::getObject)
+ .collect(Collectors.toList());
+ }
+
+}
--- /dev/null
+package org.simantics.district.route.internal;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.simantics.DatabaseJob;
+import org.simantics.Simantics;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.request.PossibleModel;
+import org.simantics.db.request.Write;
+import org.simantics.district.route.Route;
+import org.simantics.district.route.RouteEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 6.09
+ */
+public class RoutePersistenceJob extends DatabaseJob {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RoutePersistenceJob.class);
+
+ private RouteImpl route;
+ private int eventType;
+ private CompletableFuture<Route> future;
+
+ public RoutePersistenceJob(RouteImpl route, int eventType, CompletableFuture<Route> future) {
+ super("Route Persistence");
+ this.route = route;
+ this.eventType = eventType;
+ this.future = future;
+ setUser(false);
+ setSystem(true);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ Simantics.getSession().syncRequest((Write) graph -> persist(graph));
+ future.complete(route);
+ } catch (DatabaseException e) {
+ LOGGER.error("Failed to persist route {}", route, e);
+ future.completeExceptionally(e);
+ }
+ return Status.OK_STATUS;
+ }
+
+ private void persist(WriteGraph graph) throws DatabaseException {
+ switch (eventType) {
+ case RouteEvent.TYPE_ROUTE_DISCARDING:
+ remove(graph);
+ break;
+
+ case RouteEvent.TYPE_ROUTE_PERSISTING:
+ write(graph);
+ break;
+ }
+ }
+
+ private void write(WriteGraph graph) throws DatabaseException {
+ graph.markUndoPoint();
+ Resource r = route.backend();
+ Resource me = route.modelEntity();
+ if (r == null) {
+ Resource model = graph.sync(new PossibleModel(me));
+ route.backend(
+ RoutePersistence.createRoute(graph,
+ model,
+ route.getName(),
+ RoutePersistence.toResources(route.waypoints()))
+ );
+ } else {
+ RoutePersistence.updateRoute(graph,
+ r,
+ route.getName(),
+ RoutePersistence.toResources(route.waypoints())
+ );
+ }
+ }
+
+ private void remove(WriteGraph graph) throws DatabaseException {
+ graph.markUndoPoint();
+ Resource r = route.backend();
+ if (r != null) {
+ RoutePersistence.removeRoute(graph, r);
+ }
+ }
+
+}
--- /dev/null
+package org.simantics.district.route.internal;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.core.runtime.ListenerList;
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.request.PossibleActiveModel;
+import org.simantics.db.management.ISessionContext;
+import org.simantics.db.management.ISessionContextChangedListener;
+import org.simantics.db.management.SessionContextChangedEvent;
+import org.simantics.db.procedure.SyncListener;
+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.Router;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class RouteServiceImpl implements RouteService, ISessionContextChangedListener, Closeable {
+
+ private final Logger LOGGER = LoggerFactory.getLogger(RouteServiceImpl.class);
+
+ private ListenerList<RouteServiceListener> listeners = new ListenerList<>();
+ private List<Route> routes = new ArrayList<>();
+ private List<Route> unmodifiableRoutes = Collections.unmodifiableList(routes);
+ private List<Router> routers = new ArrayList<>();
+ private List<Router> unmodifiableRouters = Collections.unmodifiableList(routers);
+
+ private class StoreListener implements SyncListener<Resource> {
+ private boolean disposed = false;
+
+ @Override
+ public void execute(ReadGraph graph, Resource activeModel) {
+ try {
+ if (activeModel != null) {
+ resetRoutes(RoutePersistence.findRoutes(graph, activeModel));
+ } else {
+ resetRoutes(Collections.emptyList());
+ }
+ fireEvent(RouteEvent.TYPE_ROUTE_SOURCE_CHANGED, this);
+ } catch (DatabaseException e) {
+ LOGGER.error("Failed to read routes from model {}", activeModel, e);
+ }
+ }
+
+ @Override
+ public void exception(ReadGraph graph, Throwable t) {
+ LOGGER.error("Failed to listen to current route service storage", t);
+ }
+
+ public void dispose() {
+ disposed = true;
+ }
+
+ @Override
+ public boolean isDisposed() {
+ return disposed;
+ }
+ }
+
+ private StoreListener storeListener;
+
+ private synchronized void listenToActiveModels(Session s) {
+ StoreListener sl = storeListener;
+ if (sl != null)
+ sl.dispose();
+ if (s != null) {
+ s.asyncRequest(
+ new PossibleActiveModel(Simantics.getProjectResource()),
+ storeListener = new StoreListener());
+ } else {
+ resetRoutes(Collections.emptyList());
+ }
+ }
+
+ void resetRoutes(List<RouteImpl> newRoutes) {
+ routes.clear();
+ newRoutes.forEach(r -> r.routeService(this));
+ routes.addAll(newRoutes);
+ }
+
+ @Override
+ public void sessionContextChanged(SessionContextChangedEvent event) {
+ ISessionContext ctx = event.getNewValue();
+ Session s = ctx != null ? ctx.getSession() : null;
+ listenToActiveModels(s);
+ }
+
+ public RouteServiceImpl() {
+ Session s = Simantics.peekSession();
+ Simantics.getSessionContextProvider().addContextChangedListener(this);
+ if (s != null)
+ listenToActiveModels(s);
+ }
+
+ @Override
+ public void close() {
+ Simantics.getSessionContextProvider().removeContextChangedListener(this);
+ }
+
+ @Override
+ public void addListener(RouteServiceListener l) {
+ listeners.add(l);
+ }
+
+ @Override
+ public void removeListener(RouteServiceListener l) {
+ listeners.remove(l);
+ }
+
+ @Override
+ public Route createRoute(String name, Object backendModelEntity) {
+ Route r = new RouteImpl(name).modelEntity((Resource) backendModelEntity).routeService(this);
+ fireEvent(RouteEvent.TYPE_ROUTE_CREATED, r);
+ return r;
+ }
+
+ @Override
+ public void registerRoute(Route route) {
+ routes.add(route);
+ fireEvent(RouteEvent.TYPE_ROUTE_REGISTERED, route);
+ }
+
+ @Override
+ public CompletableFuture<Route> persistRoute(Route route) {
+ fireEvent(RouteEvent.TYPE_ROUTE_PERSISTING, route);
+ CompletableFuture<Route> future = new CompletableFuture<>();
+ new RoutePersistenceJob((RouteImpl) route, RouteEvent.TYPE_ROUTE_PERSISTING, future).schedule();
+ future.thenAccept(r -> fireEvent(RouteEvent.TYPE_ROUTE_PERSISTED, r));
+ return future;
+ }
+
+ @Override
+ public CompletableFuture<Route> discardRoute(Route route) {
+ fireEvent(RouteEvent.TYPE_ROUTE_DISCARDING, route);
+ CompletableFuture<Route> result = new CompletableFuture<>();
+ RouteImpl ri = (RouteImpl) route;
+ result.thenAccept(r -> {
+ routes.remove(route);
+ fireEvent(RouteEvent.TYPE_ROUTE_DISCARDED, route);
+ });
+ if (ri.backend() != null) {
+ new RoutePersistenceJob(ri, RouteEvent.TYPE_ROUTE_DISCARDING, result).schedule();
+ } else {
+ result.complete(route);
+ }
+ return result;
+ }
+
+ @Override
+ public List<Route> listRoutes() {
+ return unmodifiableRoutes;
+ }
+
+ @Override
+ public void registerRouter(Router router) {
+ routers.add(router);
+ fireEvent(RouteEvent.TYPE_ROUTER_REGISTERED, router);
+ }
+
+ @Override
+ public void unregisterRouter(Router router) {
+ routers.add(router);
+ fireEvent(RouteEvent.TYPE_ROUTER_UNREGISTERED, router);
+ }
+
+ public List<Router> routers() {
+ return unmodifiableRouters;
+ }
+
+ void fireEvent(int type, Object obj) {
+ RouteEvent e = new RouteEvent(this, type, obj);
+ LOGGER.info("firing route event {}", e);
+ listeners.forEach(l -> {
+ try {
+ l.handleEvent(e);
+ } catch (Exception | LinkageError | AssertionError ex) {
+ LOGGER.error("Failed to invoke RouteListener {}", l, ex);
+ }
+ });
+ }
+
+}
--- /dev/null
+package org.simantics.district.route.internal;
+
+import java.util.Objects;
+
+import org.simantics.db.Resource;
+import org.simantics.district.route.Waypoint;
+
+/**
+ * @author Tuukka Lehtonen
+ * @since 6.09
+ */
+public class WaypointImpl implements Waypoint {
+
+ private final Resource element;
+ private String label;
+
+ public WaypointImpl(Resource element, String label) {
+ Objects.requireNonNull(element, "Non-null element required");
+ this.element = element;
+ this.label = label;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T getObject() {
+ return (T) element;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+
+ public Waypoint withLabel(String label) {
+ return new WaypointImpl(element, label);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + element.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;
+ WaypointImpl other = (WaypointImpl) obj;
+ return element.equals(other.element);
+ }
+
+}
<module>org.simantics.district.region</module>
<module>org.simantics.district.region.ontology</module>
<module>org.simantics.district.region.ui</module>
+ <module>org.simantics.district.route</module>
+ <module>org.simantics.district.route.ontology</module>
+ <module>org.simantics.district.route.ui</module>
<module>org.simantics.maps.server</module>
<module>org.simantics.maps.server.ui</module>
<module>org.simantics.district.feature</module>
<module>org.simantics.district.regions.feature</module>
+ <module>org.simantics.district.route.feature</module>
<module>org.simantics.district.ui.feature</module>
<module>org.simantics.maps.server.feature</module>