package org.simantics.district.network.ui; import java.awt.Color; import java.awt.geom.AffineTransform; import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Supplier; import org.simantics.datatypes.literal.RGB; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.UnaryRead; 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.ontology.DistrictNetworkResource; import org.simantics.district.network.ui.internal.Activator; import org.simantics.district.network.ui.nodes.DistrictRenderingPreparationNode; import org.simantics.district.network.ui.nodes.DistrictSelectionNode; import org.simantics.district.network.ui.participants.DNPointerInteractor; import org.simantics.district.network.ui.participants.DynamicVisualisationContributionsParticipant; import org.simantics.district.network.ui.participants.MapRulerPainter; import org.simantics.district.network.visualisations.DynamicVisualisations; import org.simantics.district.network.visualisations.model.ColorBarOptions; import org.simantics.district.network.visualisations.model.DynamicColorContribution; import org.simantics.district.network.visualisations.model.DynamicSizeContribution; import org.simantics.district.network.visualisations.model.SizeBarOptions; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.impl.CanvasContext; import org.simantics.g2d.diagram.handler.PickRequest.PickFilter; import org.simantics.g2d.diagram.participant.DelayedBatchElementPainter; import org.simantics.g2d.diagram.participant.ElementPainter; import org.simantics.g2d.diagram.participant.ElementPainterConfiguration; import org.simantics.g2d.diagram.participant.Selection; import org.simantics.g2d.diagram.participant.ZOrderHandler; import org.simantics.g2d.participant.BackgroundPainter; import org.simantics.g2d.participant.GridPainter; import org.simantics.g2d.participant.PanZoomRotateHandler; import org.simantics.g2d.participant.RenderingQualityInteractor; import org.simantics.g2d.participant.TransformUtil; import org.simantics.g2d.participant.ZoomToAreaHandler; import org.simantics.g2d.scenegraph.SceneGraphConstants; import org.simantics.maps.MapScalingTransform; import org.simantics.maps.eclipse.MapPainter; import org.simantics.maps.sg.commands.MapCommands; import org.simantics.modeling.ui.diagramEditor.DiagramViewer; import org.simantics.scenegraph.g2d.G2DParentNode; 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.utils.datastructures.hints.IHintContext; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DistrictDiagramViewer extends DiagramViewer { private static final Logger LOGGER = LoggerFactory.getLogger(DistrictDiagramViewer.class); @Override protected void addDiagramParticipants(ICanvasContext ctx) { ctx.add(new ZOrderHandler()); ctx.add(new Selection()); ctx.add(new ElementPainter(new ElementPainterConfiguration().selectionNodeClass(DistrictSelectionNode.class))); ctx.add(new DNPointerInteractor()); AffineTransform tr = new AffineTransform(MapScalingTransform.INSTANCE); ctx.add(new MapPainter(tr)); DynamicVisualisationContributionsParticipant dynamicVisualisationContributionsParticipant = new DynamicVisualisationContributionsParticipant(tr); ctx.add(new NetworkDrawingParticipant(dynamicVisualisationContributionsParticipant, tr)); ctx.add(dynamicVisualisationContributionsParticipant); // Optimize AffineTransform memory allocations during district diagram rendering G2DParentNode spatialRoot = (G2DParentNode) ctx.getSceneGraph().lookupNode(SceneGraphConstants.SPATIAL_ROOT_NODE_ID); DistrictRenderingPreparationNode prepNode = new DistrictRenderingPreparationNode(); prepNode.setZIndex(Integer.MIN_VALUE / 2); spatialRoot.addNode("districtRenderingPrepareNode", prepNode); } protected String getPopupId() { return "#DistrictDiagramPopup"; } @Override protected void fillInitialDiagramHints(Resource diagram, IHintContext initialHints) throws DatabaseException { super.fillInitialDiagramHints(diagram, initialHints); } @Override public void initializeCanvasContext(CanvasContext ctx) { super.initializeCanvasContext(ctx); 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 protected void addPainterParticipants(ICanvasContext ctx) { ctx.add(new RenderingQualityInteractor()); ctx.add(new DelayedBatchElementPainter(PickFilter.FILTER_MONITORS, 500, TimeUnit.MILLISECONDS)); } @Override protected void addGridRulerBackgroundParticipants(CanvasContext ctx) { ctx.add(new GridPainter()); ctx.add(new MapRulerPainter()); ctx.add(new BackgroundPainter()); } protected void addViewManipulationParticipants(CanvasContext ctx) { // Let's replace with our special util TransformUtil util = ctx.getAtMostOneItemOfClass(TransformUtil.class); if (util != null) ctx.remove(util); ctx.add(new DistrictTransformUtil()); ctx.add(new DistrictPanZoomRotateHandler()); //ctx.add(new MousePanZoomInteractor()); //ctx.add(new MultitouchPanZoomRotateInteractor()); // ctx.add( new OrientationRestorer() ); ctx.add(new ZoomToAreaHandler()); } @Override protected void loadPageSettings(ICanvasContext ctx) { super.loadPageSettings(ctx); // this might be the wrong place to start such listening but at least // super.loadPageSettings() does async-db-operations setupDrawMapEnabled(); setupBackgroundColor(); setupColoringObjects(); setupColorBarOptions(); setupSizingObjects(); setupSizeBarOptions(); setupShowElevationServerBoundingBox(); // add listeners DistrictDiagramViewerListener[] listeners = Activator.getInstance().getDistrictDiagramViewerListeners(); if (listeners != null) { for (DistrictDiagramViewerListener listener : listeners) { listener.diagramLoaded(getRuntime(), canvasContext); } } } @Override public void dispose() { DistrictDiagramViewerListener[] listeners = Activator.getInstance().getDistrictDiagramViewerListeners(); if (listeners != null) { Resource runtime = getRuntime(); for (DistrictDiagramViewerListener listener : listeners) { listener.diagramDisposed(runtime, canvasContext); } } super.dispose(); } private void setupDrawMapEnabled() { sessionContext.getSession().asyncRequest(new DrawMapEnabledRequest(getInputResource()), new DrawMapEnabledListener( result -> canvasContext.getEventQueue().queueEvent(new CommandEvent(canvasContext, System.currentTimeMillis(), result ? Commands.MAP_ENABLE : Commands.MAP_DISABLE)), () -> DistrictDiagramViewer.this.isDisposed())); } private void setupBackgroundColor() { sessionContext.getSession().asyncRequest(new MapBackgroundColorRequest(getInputResource()), new MapBackgroundColorListener( result -> queueBackgroundColorChangeEvent(result), () -> DistrictDiagramViewer.this.isDisposed())); } private void queueBackgroundColorChangeEvent(RGB.Integer result) { if (result != null) { Color backgroundColor = new Color(result.red, result.green, result.blue); canvasContext.getDefaultHintContext().setHint(MapCommands.KEY_MAP_BACKGROUND_COLOR, backgroundColor); canvasContext.getEventQueue().queueEvent(new CommandEvent(canvasContext, System.currentTimeMillis(), MapCommands.MAP_BACKGROUND_COLOR_CHANGE)); } } private void setupColoringObjects() { sessionContext.getSession().asyncRequest(new ColoringObjectsRequest(getInputResource()), new ColoringObjectsListener( result -> queueColoringObjectsChangeEvent(result), () -> DistrictDiagramViewer.this.isDisposed())); } private void setupColorBarOptions() { sessionContext.getSession().asyncRequest(new ColorBarOptionsRequest(getInputResource()), new ColorBarOptionsListener( result -> queueColorBarOptionsChangeEvent(result), () -> DistrictDiagramViewer.this.isDisposed())); } private void setupSizingObjects() { sessionContext.getSession().asyncRequest(new SizingObjectsRequest(getInputResource()), new SizingObjectsListener( result -> queueSizingObjectsChangeEvent(result), () -> DistrictDiagramViewer.this.isDisposed())); } private void setupSizeBarOptions() { sessionContext.getSession().asyncRequest(new SizeBarOptionsRequest(getInputResource()), new SizeBarOptionsListener( result -> queueSizeBarOptionsChangeEvent(result), () -> DistrictDiagramViewer.this.isDisposed())); } private void setupShowElevationServerBoundingBox() { sessionContext.getSession().asyncRequest(new ShowElevationServerRequest(getInputResource()), new ShowElevationServerListener( result -> queueShowElevationServerChangeEvent(result), () -> DistrictDiagramViewer.this.isDisposed())); } public static final Key KEY_MAP_COLOR_BAR_OPTIONS = new KeyOf(ColorBarOptions.class, "colorBarOptions"); public static final Command MAP_COLOR_BAR_OPTIONS_CHANGE = new Command("colorBarOptionsChange"); public static final Key KEY_MAP_SIZE_BAR_OPTIONS = new KeyOf(SizeBarOptions.class, "sizeBarOptions"); public static final Command MAP_SIZE_BAR_OPTIONS_CHANGE = new Command("sizeBarOptionsChange"); public static final Key KEY_MAP_COLORING_OBJECTS = new KeyOf(Map.class, "coloringObjects"); public static final Command MAP_COLORING_OBJECTS_CHANGE = new Command("coloringObjectsChange"); public static final Key KEY_MAP_SIZING_OBJECTS = new KeyOf(Map.class, "sizingObjects"); public static final Command MAP_SIZING_OBJECTS_CHANGE = new Command("sizingObjectsChange"); public static final Key KEY_SHOW_ELEVATION_SERVER = new KeyOf(Boolean.class, "showElevationServer"); public static final Command SHOW_ELEVATION_SERVER_CHANGE = new Command("showElevationServerChange"); private void queueColoringObjectsChangeEvent(Map result) { queueEventInternal(KEY_MAP_COLORING_OBJECTS, MAP_COLORING_OBJECTS_CHANGE, result); } private void queueColorBarOptionsChangeEvent(ColorBarOptions result) { queueEventInternal(KEY_MAP_COLOR_BAR_OPTIONS, MAP_COLOR_BAR_OPTIONS_CHANGE, result); } private void queueSizingObjectsChangeEvent(Map result) { queueEventInternal(KEY_MAP_SIZING_OBJECTS, MAP_SIZING_OBJECTS_CHANGE, result); } private void queueSizeBarOptionsChangeEvent(SizeBarOptions result) { queueEventInternal(KEY_MAP_SIZE_BAR_OPTIONS, MAP_SIZE_BAR_OPTIONS_CHANGE, result); } private void queueShowElevationServerChangeEvent(Boolean result) { queueEventInternal(KEY_SHOW_ELEVATION_SERVER, SHOW_ELEVATION_SERVER_CHANGE, result); } private void queueEventInternal(Key key, Command command, Object result) { if (result != null && !canvasContext.isDisposed()) { canvasContext.getThreadAccess().asyncExec(() -> { canvasContext.getDefaultHintContext().setHint(key, result); canvasContext.getEventQueue().queueEvent(new CommandEvent(canvasContext, System.currentTimeMillis(), command)); }); } else { LOGGER.info("Result is either null or canvasContext is disposed", String.valueOf(result)); } } private static class DrawMapEnabledRequest extends UnaryRead { public DrawMapEnabledRequest(Resource diagram) { super(diagram); } @Override public Boolean perform(ReadGraph graph) throws DatabaseException { return DistrictNetworkUtil.drawMapEnabled(graph, parameter); } } private static class DrawMapEnabledListener implements Listener { private static final Logger LOGGER = LoggerFactory.getLogger(DrawMapEnabledListener.class); private Consumer callback; private Supplier isDisposed; private Boolean lastResult; public DrawMapEnabledListener(Consumer callback, Supplier isDisposed) { this.callback = callback; this.isDisposed = isDisposed; } @Override public void execute(Boolean result) { // Minor optimization if (!Objects.equals(lastResult, result)) { lastResult = result; callback.accept(result); } } @Override public void exception(Throwable t) { LOGGER.error("Could not listen if draw map is enabled", t); } @Override public boolean isDisposed() { return isDisposed.get(); } } private static class MapBackgroundColorRequest extends UnaryRead { public MapBackgroundColorRequest(Resource diagram) { super(diagram); } @Override public RGB.Integer perform(ReadGraph graph) throws DatabaseException { return DistrictNetworkUtil.backgroundColor(graph, parameter); } } private static class MapBackgroundColorListener implements Listener { private static final Logger LOGGER = LoggerFactory.getLogger(MapBackgroundColorListener.class); private Consumer callback; private Supplier isDisposed; private RGB.Integer lastResult; public MapBackgroundColorListener(Consumer callback, Supplier isDisposed) { this.callback = callback; this.isDisposed = isDisposed; } @Override public void execute(RGB.Integer result) { if (!Objects.equals(lastResult, result)) { lastResult = result; callback.accept(result); } } @Override public void exception(Throwable t) { LOGGER.error("Could not listen map background color", t); } @Override public boolean isDisposed() { return isDisposed.get(); } } private static class ColorBarOptionsRequest extends UnaryRead { public ColorBarOptionsRequest(Resource diagram) { super(diagram); } @Override public ColorBarOptions perform(ReadGraph graph) throws DatabaseException { DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); Resource model = graph.syncRequest(new PossibleIndexRoot(parameter)); if (model != null) { Resource vf = DynamicVisualisations.getVisualisationFolder(graph, model); if (vf != null) { Resource activeVisualisation = graph.getPossibleObject(vf, DN.Diagram_hasActiveVisualisation); if (activeVisualisation != null) { return DynamicVisualisations.colorBarOptions(graph, activeVisualisation); } } else { LOGGER.debug("No visualisation folder available for model {}", model); } } return ColorBarOptions.useDefault(); } } private static class ColoringObjectsRequest extends UnaryRead> { public ColoringObjectsRequest(Resource diagram) { super(diagram); } @Override public Map perform(ReadGraph graph) throws DatabaseException { DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); Resource model = graph.syncRequest(new PossibleIndexRoot(parameter)); if (model != null) { Resource vf = DynamicVisualisations.getVisualisationFolder(graph, model); if (vf != null) { Resource activeVisualisation = graph.getPossibleObject(vf, DN.Diagram_hasActiveVisualisation); if (activeVisualisation != null) { return DynamicVisualisations.colorContributions(graph, activeVisualisation); } } else { LOGGER.debug("No visualisation folder available for model {}", model); } } return Collections.emptyMap(); } } private static class ColoringObjectsListener implements Listener> { private static final Logger LOGGER = LoggerFactory.getLogger(ColoringObjectsListener.class); private Consumer> callback; private Supplier isDisposed; //private Map lastResult public ColoringObjectsListener(Consumer> callback, Supplier isDisposed) { this.callback = callback; this.isDisposed = isDisposed; } @Override public void execute(Map result) { callback.accept(result); } @Override public void exception(Throwable t) { LOGGER.error("Could not listen ColoringObjects", t); } @Override public boolean isDisposed() { return isDisposed.get(); } } private static class ColorBarOptionsListener implements Listener { private static final Logger LOGGER = LoggerFactory.getLogger(ColorBarOptionsListener.class); private Consumer callback; private Supplier isDisposed; public ColorBarOptionsListener(Consumer callback, Supplier isDisposed) { this.callback = callback; this.isDisposed = isDisposed; } @Override public void execute(ColorBarOptions result) { callback.accept(result); } @Override public void exception(Throwable t) { LOGGER.error("Could not listen ColorBarOptions", t); } @Override public boolean isDisposed() { return isDisposed.get(); } } private static class SizeBarOptionsRequest extends UnaryRead { public SizeBarOptionsRequest(Resource diagram) { super(diagram); } @Override public SizeBarOptions perform(ReadGraph graph) throws DatabaseException { DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); Resource model = graph.syncRequest(new PossibleIndexRoot(parameter)); if (model != null) { Resource vf = DynamicVisualisations.getVisualisationFolder(graph, model); if (vf != null) { Resource activeVisualisation = graph.getPossibleObject(vf, DN.Diagram_hasActiveVisualisation); if (activeVisualisation != null) { return DynamicVisualisations.sizeBarOptions(graph, activeVisualisation); } } else { LOGGER.debug("No visualisation folder available for model {}", model); } } return SizeBarOptions.useDefault(); } } private static class SizeBarOptionsListener implements Listener { private static final Logger LOGGER = LoggerFactory.getLogger(SizeBarOptionsListener.class); private Consumer callback; private Supplier isDisposed; public SizeBarOptionsListener(Consumer callback, Supplier isDisposed) { this.callback = callback; this.isDisposed = isDisposed; } @Override public void execute(SizeBarOptions result) { callback.accept(result); } @Override public void exception(Throwable t) { LOGGER.error("Could not listen SizeBarOptions", t); } @Override public boolean isDisposed() { return isDisposed.get(); } } private static class SizingObjectsRequest extends UnaryRead> { public SizingObjectsRequest(Resource diagram) { super(diagram); } @Override public Map perform(ReadGraph graph) throws DatabaseException { DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); Resource model = graph.syncRequest(new PossibleIndexRoot(parameter)); if (model != null) { Resource vf = DynamicVisualisations.getVisualisationFolder(graph, model); if (vf != null) { Resource activeVisualisation = graph.getPossibleObject(vf, DN.Diagram_hasActiveVisualisation); if (activeVisualisation != null) { return DynamicVisualisations.sizeContributions(graph, activeVisualisation); } } else { LOGGER.debug("No visualisation folder available for model {}", model); } } return Collections.emptyMap(); } } private static class SizingObjectsListener implements Listener> { private static final Logger LOGGER = LoggerFactory.getLogger(SizingObjectsListener.class); private Consumer> callback; private Supplier isDisposed; public SizingObjectsListener(Consumer> callback, Supplier isDisposed) { this.callback = callback; this.isDisposed = isDisposed; } @Override public void execute(Map result) { callback.accept(result); } @Override public void exception(Throwable t) { LOGGER.error("Could not listen SizingObjectsOptions", t); } @Override public boolean isDisposed() { return isDisposed.get(); } } private static class ShowElevationServerRequest extends UnaryRead { public ShowElevationServerRequest(Resource diagram) { super(diagram); } @Override public Boolean perform(ReadGraph graph) throws DatabaseException { DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); Resource model = graph.syncRequest(new PossibleIndexRoot(parameter)); if (model != null) { Resource vf = DynamicVisualisations.getVisualisationFolder(graph, model); if (vf != null) { Resource activeVisualisation = graph.getPossibleObject(vf, DN.Diagram_hasActiveVisualisation); if (activeVisualisation != null) { return DynamicVisualisations.showElevationServerBoundingBox(graph, activeVisualisation); } } else { LOGGER.debug("No visualisation folder available for model {}", model); } } return false; } } private static class ShowElevationServerListener implements Listener { private static final Logger LOGGER = LoggerFactory.getLogger(ShowElevationServerListener.class); private Consumer callback; private Supplier isDisposed; public ShowElevationServerListener(Consumer callback, Supplier isDisposed) { this.callback = callback; this.isDisposed = isDisposed; } @Override public void execute(Boolean result) { callback.accept(result); } @Override public void exception(Throwable t) { LOGGER.error("Could not listen Show Elevation Server", t); } @Override public boolean isDisposed() { return isDisposed.get(); } } }