/******************************************************************************* * Copyright (c) 2012 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.maps.eclipse; import java.awt.Color; import java.awt.geom.AffineTransform; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.simantics.g2d.canvas.Hints; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit; import org.simantics.g2d.participant.MouseUtil; import org.simantics.maps.sg.MapAttributionNode; import org.simantics.maps.sg.MapInfoNode; import org.simantics.maps.sg.MapLocationInfoNode; import org.simantics.maps.sg.MapNode; import org.simantics.maps.sg.MapScaleNode; import org.simantics.maps.sg.commands.MapCommands; 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.MouseEvent.MouseMovedEvent; import org.simantics.scenegraph.g2d.events.command.CommandEvent; import org.simantics.scenegraph.g2d.events.command.Commands; import org.simantics.utils.datastructures.hints.HintListenerAdapter; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; import org.simantics.utils.datastructures.hints.IHintListener; import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.threads.AWTThread; import org.simantics.utils.threads.ThreadUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * MapPainter is an ICanvasContext participant that uses the scene graph * {@link MapNode} to draw tiled maps in the background of the canvas. * * @author J-P Laine * * @see MapNode */ public class MapPainter extends AbstractCanvasParticipant { private static final Logger LOGGER = LoggerFactory.getLogger(MapPainter.class); /** * Grid enabled status. Default value is True */ public static final Key KEY_MAP_ENABLED = new KeyOf(Boolean.class); public static final Key KEY_MAP_BACKGROUND_COLOR = new KeyOf(Object.class); public static final double ZOOM_IN_LIMIT = 10000000.0; public static final double ZOOM_OUT_LIMIT = 10.0; IHintListener mapListener = new HintListenerAdapter() { public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { ICanvasContext cc = getContext(); if (cc != null) { updateNode(); cc.getContentContext().setDirty(); } } }; private MapNode mapNode; private MapLocationInfoNode locationInfoNode; private MapScaleNode scaleNode; private MapInfoNode attributionNode; private AffineTransform transform; private ScheduledFuture schedule; public MapPainter(AffineTransform transform) { this.transform = transform; } @Override public void addedToContext(ICanvasContext ctx) { super.addedToContext(ctx); getHintStack().addKeyHintListener(getThread(), KEY_MAP_ENABLED, mapListener); getHintStack().addKeyHintListener(getThread(), KEY_MAP_BACKGROUND_COLOR, mapListener); } @Override public void removedFromContext(ICanvasContext ctx) { getHintStack().removeKeyHintListener(getThread(), KEY_MAP_ENABLED, mapListener); getHintStack().removeKeyHintListener(getThread(), KEY_MAP_BACKGROUND_COLOR, mapListener); super.removedFromContext(ctx); } @EventHandler(priority = 0) public boolean handleKeyEvent(CommandEvent e) { if (e.command.equals( Commands.MAP_ENABLE )) { setEnabled(true); updateNode(); setDirty(); return true; } else if (e.command.equals( MapCommands.MAP_BACKGROUND_COLOR_CHANGE )) { ICanvasContext context = (ICanvasContext) e.getContext(); Color s = context.getHintStack().getHint(MapCommands.KEY_MAP_BACKGROUND_COLOR); setBackgroundColor(s); setDirty(); return true; } else if (e.command.equals( Commands.MAP_DISABLE )) { setEnabled(false); updateNode(); setDirty(); return true; } else if (e.command.equals( Commands.MAP_TOGGLE )) { setEnabled(!isMapEnabled()); updateNode(); setDirty(); return true; } else if (e.command.equals( Commands.ENABLE_PAINTING )) { enablePainting(); updateNode(); setDirty(); return true; } return false; } @EventHandler(priority = 31) public boolean handleEvent(Event e) { if (e instanceof MouseMovedEvent) { // here we should somehow re-render ? if (locationInfoNode != null && locationInfoNode.isEnabled()) { if (schedule == null || schedule.isDone()) { LOGGER.debug("current setDirty time" + System.currentTimeMillis()); schedule = ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> { AWTThread.getThreadAccess().asyncExec(this::setDirty); }, 100, TimeUnit.MILLISECONDS); } else { //LOGGER.debug("ingoring setDirty time" + System.currentTimeMillis()); } } } return false; } @SGInit public void initSG(G2DParentNode parent) { // Just under the grid mapNode = parent.addNode("map", MapNode.class); mapNode.setTransform(transform); mapNode.setEnabled(true); mapNode.setZIndex(Integer.MIN_VALUE + 999); // On top of pretty much everything attributionNode = parent.addNode("mapAttribution", MapAttributionNode.class); attributionNode.setTransform(transform); attributionNode.setZIndex(Integer.MAX_VALUE - 999); attributionNode.setEnabled(true); scaleNode = parent.addNode("mapScale", MapScaleNode.class); scaleNode.setTransform(transform); scaleNode.setZIndex(Integer.MAX_VALUE - 998); scaleNode.setEnabled(true); locationInfoNode = parent.addNode("mapLocationInfo", MapLocationInfoNode.class); locationInfoNode.setTransform(transform); locationInfoNode.setZIndex(Integer.MAX_VALUE - 997); locationInfoNode.setEnabled(true); locationInfoNode.setMouseUtil(getContext().getAtMostOneItemOfClass(MouseUtil.class)); } @SGCleanup public void cleanupSG() { mapNode.remove(); attributionNode.remove(); scaleNode.remove(); locationInfoNode.remove(); } protected void updateNode() { mapNode.setEnabled(isPaintingEnabled()); mapNode.setEnabled(isMapEnabled()); mapNode.setBackgroundColor(getBackgroundColor()); } boolean isPaintingEnabled() { boolean enabled = isMapEnabled(); Boolean globalDisable = getHint(Hints.KEY_DISABLE_PAINTING); return enabled && !Boolean.TRUE.equals(globalDisable); } public boolean isMapEnabled() { Boolean enabled = getHint(KEY_MAP_ENABLED); return !Boolean.FALSE.equals(enabled); } public void setEnabled(boolean enabled) { setHint(KEY_MAP_ENABLED, enabled); } private void enablePainting() { setHint(Hints.KEY_DISABLE_PAINTING, false); } private void setBackgroundColor(Color backgroundColor) { setHint(KEY_MAP_BACKGROUND_COLOR, backgroundColor); } private Color getBackgroundColor() { return getHint(KEY_MAP_BACKGROUND_COLOR); } }