X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;ds=sidebyside;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fparticipant%2FPanZoomRotateHandler.java;h=6ca4f786247b9e4cc82db6892ad150e0db685a41;hb=3598f987e691cb4f35ab8f283d4dfd760bcdd410;hp=b7321a386e6e919430bd28ac03d533037049774d;hpb=969bd23cab98a79ca9101af33334000879fb60c5;p=simantics%2Fplatform.git
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/PanZoomRotateHandler.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/PanZoomRotateHandler.java
index b7321a386..6ca4f7862 100644
--- a/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/PanZoomRotateHandler.java
+++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/PanZoomRotateHandler.java
@@ -1,496 +1,498 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2010 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.g2d.participant;
-
-import static org.simantics.g2d.canvas.Hints.KEY_CANVAS_TRANSFORM;
-
-import java.awt.Shape;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.util.Set;
-
-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.DependencyReflection.Dependency;
-import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
-import org.simantics.g2d.diagram.DiagramHints;
-import org.simantics.g2d.diagram.DiagramUtils;
-import org.simantics.g2d.diagram.IDiagram;
-import org.simantics.g2d.diagram.participant.Selection;
-import org.simantics.g2d.element.ElementUtils;
-import org.simantics.g2d.element.IElement;
-import org.simantics.g2d.scenegraph.SceneGraphConstants;
-import org.simantics.g2d.utils.GeometryUtils;
-import org.simantics.scenegraph.INode;
-import org.simantics.scenegraph.g2d.G2DParentNode;
-import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
-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.NavigationNode;
-import org.simantics.scenegraph.g2d.nodes.TransformNode;
-import org.simantics.scenegraph.utils.NodeUtil;
-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.page.MarginUtils;
-import org.simantics.utils.page.MarginUtils.Margins;
-import org.simantics.utils.page.PageDesc;
-import org.simantics.utils.threads.ThreadUtils;
-
-/**
- * This participant handles pan, zoom, zoom to fit and rotate commands.
- *
- * Hints:
- * KEY_TRANSLATE_AMOUNT
- * KEY_ZOOM_AMOUNT
- * KEY_ROTATE_AMOUNT
- * KEY_ZOOM_TO_FIT_MARGINS
- * KEY_ZOOM_OUT_LIMIT
- * KEY_ZOOM_IN_LIMIT
- *
- * @author Toni Kalajainen
- * @author Tuukka Lehtonen
- */
-public class PanZoomRotateHandler extends AbstractCanvasParticipant {
-
- /**
- * Express whether or not the view should attempt to keep the current zoom
- * level when the canvas parenting control is resized. If the viewport is
- * set to be adapted to the resized control, the view transform will be
- * adjusted to accommodate for this. Otherwise the view transform will be
- * left alone when the control is resized.
- *
- * If hint is not specified, the default value is true
.
- *
- * See {@link NavigationNode} for the zoom level keep implementation.
- */
- public final static Key KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL = new KeyOf(Boolean.class, "ADAPT_VIEWPORT_TO_RESIZED_CONTROL");
-
- /**
- * Limit for zooming in expressed as a percentage (100% == 1:1 == identity
- * view transform). If null, there is no limit. Used with an
- * ICanvasContext's hint context.
- */
- public final static Key KEY_ZOOM_OUT_LIMIT = new KeyOf(Double.class, "ZOOM_OUT_LIMIT");
-
- /**
- * Limit for zooming in expressed as a percentage (100% == 1:1 == identity
- * view transform). If null there is no limit. Used with an
- * ICanvasContext's hint context.
- */
- public final static Key KEY_ZOOM_IN_LIMIT = new KeyOf(Double.class, "ZOOM_IN_LIMIT");
-
- public final static Key KEY_DISABLE_ZOOM = new KeyOf(Boolean.class, "DISABLE_ZOOM");
-
- public final static Key KEY_DISABLE_PAN = new KeyOf(Boolean.class, "DISABLE_PAN");
-
-
- @Dependency CanvasGrab grab;
- @Dependency TransformUtil util;
- @Dependency KeyUtil keys;
- @Reference Selection selection;
- @Reference CanvasBoundsParticipant bounds;
-
- // Capture center point
- Point2D centerPointControl;
- Point2D centerPointCanvas;
- Point2D controlSize;
-
- final Boolean navigationEnabled;
-
- protected NavigationNode node = null;
- protected G2DParentNode oldRoot = null;
-
- public PanZoomRotateHandler() {
- this(true);
- }
-
- public PanZoomRotateHandler(boolean navigationEnabled) {
- this.navigationEnabled = navigationEnabled;
- }
-
- NavigationNode.TransformListener transformListener = new NavigationNode.TransformListener() {
- @Override
- public void transformChanged(final AffineTransform transform) {
- ThreadUtils.asyncExec(PanZoomRotateHandler.this.getContext().getThreadAccess(), new Runnable() {
- @Override
- public void run() {
- if (isRemoved())
- return;
- //System.out.println("PanZoomRotateHandler: set canvas transform: " + transform);
- setHint(KEY_CANVAS_TRANSFORM, transform);
- }
- });
- }
- };
-
- IHintListener hintListener = new HintListenerAdapter() {
- @Override
- public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
- if (node != null) {
- if (key == Hints.KEY_DISABLE_PAINTING) {
- boolean visible = !Boolean.TRUE.equals(newValue);
- if (visible != node.isVisible())
- node.setVisible(Boolean.valueOf(visible));
- } else if (key == KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL) {
- boolean noKeepZoom = Boolean.FALSE.equals(newValue);
- if (noKeepZoom == node.getAdaptViewportToResizedControl())
- node.setAdaptViewportToResizedControl(Boolean.valueOf(!noKeepZoom));
- } else if (key == KEY_ZOOM_OUT_LIMIT) {
- node.setZoomOutLimit((Double) newValue);
- } else if (key == KEY_ZOOM_IN_LIMIT) {
- node.setZoomInLimit((Double) newValue);
- } else if (key == KEY_DISABLE_ZOOM) {
- node.setZoomEnabled(!Boolean.TRUE.equals(getHint(KEY_DISABLE_ZOOM)));
- }
- }
- }
- };
-
- @Override
- public void addedToContext(ICanvasContext ctx) {
- super.addedToContext(ctx);
- ctx.getDefaultHintContext().addKeyHintListener(Hints.KEY_DISABLE_PAINTING, hintListener);
- ctx.getDefaultHintContext().addKeyHintListener(KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL, hintListener);
- ctx.getDefaultHintContext().addKeyHintListener(KEY_ZOOM_OUT_LIMIT, hintListener);
- ctx.getDefaultHintContext().addKeyHintListener(KEY_ZOOM_IN_LIMIT, hintListener);
- ctx.getDefaultHintContext().addKeyHintListener(KEY_DISABLE_ZOOM, hintListener);
- ctx.getDefaultHintContext().addKeyHintListener(KEY_DISABLE_PAN, hintListener);
- }
-
- @Override
- public void removedFromContext(ICanvasContext ctx) {
- ctx.getDefaultHintContext().removeKeyHintListener(KEY_ZOOM_IN_LIMIT, hintListener);
- ctx.getDefaultHintContext().removeKeyHintListener(KEY_ZOOM_OUT_LIMIT, hintListener);
- ctx.getDefaultHintContext().removeKeyHintListener(KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL, hintListener);
- ctx.getDefaultHintContext().removeKeyHintListener(Hints.KEY_DISABLE_PAINTING, hintListener);
- ctx.getDefaultHintContext().removeKeyHintListener(KEY_DISABLE_ZOOM, hintListener);
- ctx.getDefaultHintContext().removeKeyHintListener(KEY_DISABLE_PAN, hintListener);
- super.removedFromContext(ctx);
- }
-
- @SGInit
- public void initSG(G2DParentNode parent) {
- // Replace old NAVIGATION_NODE with a new one
- INode oldnav = NodeUtil.getRootNode(parent).getNode(SceneGraphConstants.NAVIGATION_NODE_NAME);
- if(oldnav != null) {
- node = oldnav.appendParent(SceneGraphConstants.NAVIGATION_NODE_NAME, NavigationNode.class);
- // FIXME : oldnav seems to be the same node as parent (most of the cases).
- // Deleting it will cause plenty of code to fail, since they refer to the node directly.
- // The bug was not shown, since deleting() a Node did not actually wipe its structures (until now).
- // oldnav.delete();
- } else {
- node = parent.addNode(SceneGraphConstants.NAVIGATION_NODE_NAME, NavigationNode.class);
- }
- node.setLookupId(SceneGraphConstants.NAVIGATION_NODE_NAME);
- node.setZIndex(0);
- node.setTransformListener(transformListener);
- node.setNavigationEnabled(navigationEnabled);
- node.setZoomEnabled(!Boolean.TRUE.equals(getHint(KEY_DISABLE_ZOOM)));
- node.setAdaptViewportToResizedControl(!Boolean.FALSE.equals(getHint(KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL)));
- Double z = getHint(KEY_ZOOM_AMOUNT);
- if(z != null) {
- util.setTransform(AffineTransform.getScaleInstance(z, z));
- node.setTransform(AffineTransform.getScaleInstance(z, z));
- }
- boolean visible = !Boolean.TRUE.equals(getHint(Hints.KEY_DISABLE_PAINTING));
- node.setVisible(visible);
- oldRoot = getContext().getCanvasNode();
- getContext().setCanvasNode(node);
- }
-
- public void update() {
- if (bounds != null) {
- Rectangle2D vp = bounds.getControlBounds();
- controlSize = new Point2D.Double(vp.getMaxX(), vp.getMaxY());
- centerPointControl = new Point2D.Double(vp.getCenterX(), vp.getCenterY());
- centerPointCanvas = util.controlToCanvas(centerPointControl, null);
- }
- }
-
- public TransformNode getNode() {
- return node;
- }
-
- /**
- * Ensures that the navigation node handled by this participant contains the
- * specified transform and that {@link Hints#KEY_CANVAS_TRANSFORM} will
- * contain the same value.
- *
- * @param transform
- */
- public void setTransform(AffineTransform transform) {
- getNode().setTransform(transform);
- transformListener.transformChanged(transform);
- }
-
- @SGCleanup
- public void cleanupSG() {
- node.remove();
- node = null;
- getContext().setCanvasNode(oldRoot);
- }
-
-
- /** Arrow key translate */
- public final static Key KEY_TRANSLATE_AMOUNT = new KeyOf(Integer.class);
- public final static Key KEY_ZOOM_AMOUNT = new KeyOf(Double.class);
- public final static Key KEY_ROTATE_AMOUNT = new KeyOf(Double.class);
-
- /** Amount of arrow key translate */
- public final static int DEFAULT_KEYBOARD_TRANSLATE_AMOUNT = 30;
- public final static double DEFAULT_KEYBOARD_ZOOM_AMOUNT = 1.2;
- public final static double DEFAULT_KEYBOARD_ROTATE_AMOUNT = 0.1;
- public final static Margins DEFAULT_ZOOM_TO_FIT_MARGINS = RulerPainter.RULER_MARINGS2;
- public final static Margins DEFAULT_ZOOM_TO_FIT_MARGINS_NO_RULER = MarginUtils.MARGINS2;
-
- public final static int ROTATE_GRAB_ID = -666;
-
- @EventHandler(priority = 0)
- public boolean handleEvent(CommandEvent e) {
- assertDependencies();
- update();
- Command c = e.command;
- boolean panDisabled = Boolean.TRUE.equals(getHint(KEY_DISABLE_PAN)) ? true : false;
- boolean zoomDisabled = Boolean.TRUE.equals(getHint(KEY_DISABLE_ZOOM)) ? true : false;
-
- // Arrow key panning
- if (Commands.PAN_LEFT.equals(c) && !panDisabled) {
- util.translateWithControlCoordinates(
- new Point2D.Double(
- getTranslateAmount(), 0));
- return true;
- }
- if (Commands.PAN_RIGHT.equals(c) && !panDisabled) {
- util.translateWithControlCoordinates(
- new Point2D.Double(
- -getTranslateAmount(), 0));
- return true;
- }
- if (Commands.PAN_UP.equals(c) && !panDisabled) {
- util.translateWithControlCoordinates(
- new Point2D.Double(
- 0, getTranslateAmount()));
- return true;
- }
- if (Commands.PAN_DOWN.equals(c) && !panDisabled) {
- util.translateWithControlCoordinates(
- new Point2D.Double(0, -getTranslateAmount()));
- return true;
- }
- if (Commands.ZOOM_IN.equals(c) && !zoomDisabled) {
- if (centerPointControl == null) return false;
- double scaleFactor = getZoomAmount();
- scaleFactor = limitScaleFactor(scaleFactor);
- util.zoomAroundControlPoint(scaleFactor, centerPointControl);
- }
- if (Commands.ZOOM_OUT.equals(c) && !zoomDisabled) {
- if (centerPointControl == null) return false;
- double scaleFactor = 1 / getZoomAmount();
- scaleFactor = limitScaleFactor(scaleFactor);
- util.zoomAroundControlPoint(scaleFactor, centerPointControl);
- }
-
- if (Commands.ROTATE_CANVAS_CCW.equals(c)) {
- if (centerPointCanvas == null) return false;
- util.rotate(centerPointCanvas, -getRotateAmount());
- setDirty();
- return true;
- }
- if (Commands.ROTATE_CANVAS_CW.equals(c)) {
- if (centerPointCanvas == null) return false;
- util.rotate(centerPointCanvas, getRotateAmount());
- setDirty();
- return true;
- }
- if (Commands.ROTATE_CANVAS_CCW_GRAB.equals(c)) {
- if (centerPointCanvas == null) return false;
- util.rotate(centerPointCanvas, -getRotateAmount());
- grab.grabCanvas(ROTATE_GRAB_ID, centerPointCanvas);
- grab.grabCanvas(ROTATE_GRAB_ID - 1, centerPointCanvas);
- setDirty();
- return true;
- }
- if (Commands.ROTATE_CANVAS_CW_GRAB.equals(c)) {
- if (centerPointCanvas == null) return false;
- util.rotate(centerPointCanvas, getRotateAmount());
- grab.grabCanvas(ROTATE_GRAB_ID, centerPointCanvas);
- grab.grabCanvas(ROTATE_GRAB_ID - 1, centerPointCanvas);
- setDirty();
- return true;
- }
- if (Commands.ROTATE_CANVAS_CCW_RELEASE.equals(c)) {
- if (centerPointCanvas == null) return false;
- grab.releaseCanvas(ROTATE_GRAB_ID);
- grab.releaseCanvas(ROTATE_GRAB_ID - 1);
- setDirty();
- return true;
- }
- if (Commands.ROTATE_CANVAS_CW_RELEASE.equals(c)) {
- if (centerPointCanvas == null) return false;
- grab.releaseCanvas(ROTATE_GRAB_ID);
- grab.releaseCanvas(ROTATE_GRAB_ID - 1);
- setDirty();
- return true;
- }
- if (Commands.ENABLE_PAINTING.equals(c)) {
- Boolean t = getHint(Hints.KEY_DISABLE_PAINTING);
- removeHint(Hints.KEY_DISABLE_PAINTING);
- boolean processed = Boolean.TRUE.equals(t);
- if (processed)
- setDirty();
- return processed;
- }
- if (Commands.ZOOM_TO_FIT.equals(c) && !zoomDisabled) {
- boolean result = zoomToFit();
- if (!result)
- result = zoomToPage();
- return result;
- }
- if (Commands.ZOOM_TO_SELECTION.equals(c) && !zoomDisabled && selection != null) {
- if (controlSize==null) return false;
- IDiagram d = getHint(DiagramHints.KEY_DIAGRAM);
- if (d==null) return false;
-
- Set selections = selection.getAllSelections();
- Shape bounds = ElementUtils.getElementBoundsOnDiagram(selections);
- if (bounds == null) return false;
- Rectangle2D diagramRect = bounds.getBounds2D();
- if (diagramRect.getWidth() <= 0 && diagramRect.getHeight() <= 0)
- return false;
-
- // HACK: prevents straight connections from being unzoomable.
- if (diagramRect.getWidth() <= 0)
- org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 0, 0, 1, 1);
- if (diagramRect.getHeight() <= 0)
- org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 1, 1, 0, 0);
-
- // Show area
- Rectangle2D controlArea = new Rectangle2D.Double(0, 0, controlSize.getX(), controlSize.getY());
- util.fitArea(controlArea, diagramRect, getZoomToFitMargins(getHintStack()));
- return true;
- }
- if (Commands.ZOOM_TO_PAGE.equals(c) && !zoomDisabled) {
- return zoomToPage();
- }
-
- return false;
- }
-
- private boolean zoomToFit() {
- if (controlSize==null) return false;
- IDiagram d = getHint(DiagramHints.KEY_DIAGRAM);
- if (d==null) return false;
-
- Rectangle2D diagramRect = DiagramUtils.getContentRect(d);
- if (diagramRect==null) return false;
- if (diagramRect.isEmpty())
- return false;
-
- // Show area
- Rectangle2D controlArea = new Rectangle2D.Double(0, 0, controlSize.getX(), controlSize.getY());
- //System.out.println("zoomToFit(" + controlArea + ", " + diagramRect + ")");
- util.fitArea(controlArea, diagramRect, getZoomToFitMargins(getHintStack()));
-
- return true;
- }
-
- private boolean zoomToPage() {
- if (controlSize==null) return false;
- PageDesc desc = getHint(Hints.KEY_PAGE_DESC);
- if (desc == null)
- return false;
- if (desc.isInfinite())
- return false;
-
- // Show page
- Rectangle2D diagramRect = new Rectangle2D.Double();
- desc.getPageRectangle(diagramRect);
- if (diagramRect.isEmpty())
- return false;
-
- Rectangle2D controlArea = new Rectangle2D.Double(0, 0, controlSize.getX(), controlSize.getY());
- //System.out.println("zoomToPage(" + controlArea + ", " + diagramRect + ")");
- util.fitArea(controlArea, diagramRect, getZoomToFitMargins(getHintStack()));
- return true;
- }
-
- public double getTranslateAmount()
- {
- Integer h = getHint(KEY_TRANSLATE_AMOUNT);
- if (h==null) return DEFAULT_KEYBOARD_TRANSLATE_AMOUNT;
- return h;
- }
-
- public double getZoomAmount()
- {
- Integer h = getHint(KEY_TRANSLATE_AMOUNT);
- if (h==null) return DEFAULT_KEYBOARD_ZOOM_AMOUNT;
- return h;
- }
-
- public double getRotateAmount()
- {
- Integer h = getHint(KEY_ROTATE_AMOUNT);
- if (h==null) return DEFAULT_KEYBOARD_ROTATE_AMOUNT;
- return h;
- }
-
- public double limitScaleFactor(double scaleFactor) {
- Double inLimit = getHint(PanZoomRotateHandler.KEY_ZOOM_IN_LIMIT);
- Double outLimit = getHint(PanZoomRotateHandler.KEY_ZOOM_OUT_LIMIT);
-
- if (inLimit == null && scaleFactor < 1)
- return scaleFactor;
- if (outLimit == null && scaleFactor > 1)
- return scaleFactor;
-
- AffineTransform view = util.getTransform();
- double currentScale = GeometryUtils.getScale(view) * 100.0;
- double newScale = currentScale * scaleFactor;
-
- if (inLimit != null && newScale > currentScale && newScale > inLimit) {
- if (currentScale < inLimit)
- scaleFactor = inLimit / currentScale;
- else
- scaleFactor = 1.0;
- } else if (outLimit != null && newScale < currentScale && newScale < outLimit) {
- if (currentScale > outLimit)
- scaleFactor = outLimit / currentScale;
- else
- scaleFactor = 1.0;
- }
- return scaleFactor;
- }
-
- public static Margins getZoomToFitMargins(IHintObservable hints) {
- Margins h = hints.getHint(DiagramHints.KEY_MARGINS);
- if (h == null) {
- Boolean b = hints.getHint(RulerPainter.KEY_RULER_ENABLED);
- boolean rulerEnabled = b == null || Boolean.TRUE.equals(b);
- if (rulerEnabled) {
- return PanZoomRotateHandler.DEFAULT_ZOOM_TO_FIT_MARGINS;
- } else {
- return PanZoomRotateHandler.DEFAULT_ZOOM_TO_FIT_MARGINS_NO_RULER;
- }
- }
- return h;
- }
-
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 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.g2d.participant;
+
+import static org.simantics.g2d.canvas.Hints.KEY_CANVAS_TRANSFORM;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Set;
+
+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.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
+import org.simantics.g2d.diagram.DiagramHints;
+import org.simantics.g2d.diagram.DiagramUtils;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.participant.Selection;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.scenegraph.SceneGraphConstants;
+import org.simantics.g2d.utils.GeometryUtils;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+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.NavigationNode;
+import org.simantics.scenegraph.g2d.nodes.TransformNode;
+import org.simantics.scenegraph.utils.NodeUtil;
+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.page.MarginUtils;
+import org.simantics.utils.page.MarginUtils.Margins;
+import org.simantics.utils.page.PageDesc;
+import org.simantics.utils.threads.ThreadUtils;
+
+/**
+ * This participant handles pan, zoom, zoom to fit and rotate commands.
+ *
+ * Hints:
+ * KEY_TRANSLATE_AMOUNT
+ * KEY_ZOOM_AMOUNT
+ * KEY_ROTATE_AMOUNT
+ * KEY_ZOOM_TO_FIT_MARGINS
+ * KEY_ZOOM_OUT_LIMIT
+ * KEY_ZOOM_IN_LIMIT
+ *
+ * @author Toni Kalajainen
+ * @author Tuukka Lehtonen
+ */
+public class PanZoomRotateHandler extends AbstractCanvasParticipant {
+
+ /**
+ * Express whether or not the view should attempt to keep the current zoom
+ * level when the canvas parenting control is resized. If the viewport is
+ * set to be adapted to the resized control, the view transform will be
+ * adjusted to accommodate for this. Otherwise the view transform will be
+ * left alone when the control is resized.
+ *
+ * If hint is not specified, the default value is true
.
+ *
+ * See {@link NavigationNode} for the zoom level keep implementation.
+ */
+ public final static Key KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL = new KeyOf(Boolean.class, "ADAPT_VIEWPORT_TO_RESIZED_CONTROL");
+
+ /**
+ * Limit for zooming in expressed as a percentage (100% == 1:1 == identity
+ * view transform). If null, there is no limit. Used with an
+ * ICanvasContext's hint context.
+ */
+ public final static Key KEY_ZOOM_OUT_LIMIT = new KeyOf(Double.class, "ZOOM_OUT_LIMIT");
+
+ /**
+ * Limit for zooming in expressed as a percentage (100% == 1:1 == identity
+ * view transform). If null there is no limit. Used with an
+ * ICanvasContext's hint context.
+ */
+ public final static Key KEY_ZOOM_IN_LIMIT = new KeyOf(Double.class, "ZOOM_IN_LIMIT");
+
+ public final static Key KEY_DISABLE_ZOOM = new KeyOf(Boolean.class, "DISABLE_ZOOM");
+
+ public final static Key KEY_DISABLE_PAN = new KeyOf(Boolean.class, "DISABLE_PAN");
+
+
+ @Dependency CanvasGrab grab;
+ @Dependency TransformUtil util;
+ @Dependency KeyUtil keys;
+ @Reference Selection selection;
+ @Reference CanvasBoundsParticipant bounds;
+
+ // Capture center point
+ Point2D centerPointControl;
+ Point2D centerPointCanvas;
+ Point2D controlSize;
+
+ final Boolean navigationEnabled;
+
+ protected NavigationNode node = null;
+ protected G2DParentNode oldRoot = null;
+
+ public PanZoomRotateHandler() {
+ this(true);
+ }
+
+ public PanZoomRotateHandler(boolean navigationEnabled) {
+ this.navigationEnabled = navigationEnabled;
+ }
+
+ NavigationNode.TransformListener transformListener = new NavigationNode.TransformListener() {
+ @Override
+ public void transformChanged(final AffineTransform transform) {
+ ThreadUtils.asyncExec(PanZoomRotateHandler.this.getContext().getThreadAccess(), new Runnable() {
+ @Override
+ public void run() {
+ if (isRemoved())
+ return;
+ //System.out.println("PanZoomRotateHandler: set canvas transform: " + transform);
+ setHint(KEY_CANVAS_TRANSFORM, transform);
+ }
+ });
+ }
+ };
+
+ IHintListener hintListener = new HintListenerAdapter() {
+ @Override
+ public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+ if (node != null) {
+ if (key == Hints.KEY_DISABLE_PAINTING) {
+ boolean visible = !Boolean.TRUE.equals(newValue);
+ if (visible != node.isVisible())
+ node.setVisible(Boolean.valueOf(visible));
+ } else if (key == KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL) {
+ boolean noKeepZoom = Boolean.FALSE.equals(newValue);
+ if (noKeepZoom == node.getAdaptViewportToResizedControl())
+ node.setAdaptViewportToResizedControl(Boolean.valueOf(!noKeepZoom));
+ } else if (key == KEY_ZOOM_OUT_LIMIT) {
+ node.setZoomOutLimit((Double) newValue);
+ } else if (key == KEY_ZOOM_IN_LIMIT) {
+ node.setZoomInLimit((Double) newValue);
+ } else if (key == KEY_DISABLE_ZOOM) {
+ node.setZoomEnabled(!Boolean.TRUE.equals(getHint(KEY_DISABLE_ZOOM)));
+ }
+ }
+ }
+ };
+
+ @Override
+ public void addedToContext(ICanvasContext ctx) {
+ super.addedToContext(ctx);
+ ctx.getDefaultHintContext().addKeyHintListener(Hints.KEY_DISABLE_PAINTING, hintListener);
+ ctx.getDefaultHintContext().addKeyHintListener(KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL, hintListener);
+ ctx.getDefaultHintContext().addKeyHintListener(KEY_ZOOM_OUT_LIMIT, hintListener);
+ ctx.getDefaultHintContext().addKeyHintListener(KEY_ZOOM_IN_LIMIT, hintListener);
+ ctx.getDefaultHintContext().addKeyHintListener(KEY_DISABLE_ZOOM, hintListener);
+ ctx.getDefaultHintContext().addKeyHintListener(KEY_DISABLE_PAN, hintListener);
+ }
+
+ @Override
+ public void removedFromContext(ICanvasContext ctx) {
+ ctx.getDefaultHintContext().removeKeyHintListener(KEY_ZOOM_IN_LIMIT, hintListener);
+ ctx.getDefaultHintContext().removeKeyHintListener(KEY_ZOOM_OUT_LIMIT, hintListener);
+ ctx.getDefaultHintContext().removeKeyHintListener(KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL, hintListener);
+ ctx.getDefaultHintContext().removeKeyHintListener(Hints.KEY_DISABLE_PAINTING, hintListener);
+ ctx.getDefaultHintContext().removeKeyHintListener(KEY_DISABLE_ZOOM, hintListener);
+ ctx.getDefaultHintContext().removeKeyHintListener(KEY_DISABLE_PAN, hintListener);
+ super.removedFromContext(ctx);
+ }
+
+ protected Class extends NavigationNode> getNavigationNodeClass() {
+ return NavigationNode.class;
+ }
+
+ @SGInit
+ public void initSG(G2DParentNode parent) {
+ // Replace old NAVIGATION_NODE with a new one
+ INode oldnav = NodeUtil.getRootNode(parent).getNode(SceneGraphConstants.NAVIGATION_NODE_NAME);
+ if(oldnav != null) {
+ node = oldnav.appendParent(SceneGraphConstants.NAVIGATION_NODE_NAME, getNavigationNodeClass());
+ // FIXME : oldnav seems to be the same node as parent (most of the cases).
+ // Deleting it will cause plenty of code to fail, since they refer to the node directly.
+ // The bug was not shown, since deleting() a Node did not actually wipe its structures (until now).
+ // oldnav.delete();
+ } else {
+ node = parent.addNode(SceneGraphConstants.NAVIGATION_NODE_NAME, getNavigationNodeClass());
+ }
+ node.setLookupId(SceneGraphConstants.NAVIGATION_NODE_NAME);
+ node.setZIndex(0);
+ node.setTransformListener(transformListener);
+ node.setNavigationEnabled(navigationEnabled);
+ node.setZoomEnabled(!Boolean.TRUE.equals(getHint(KEY_DISABLE_ZOOM)));
+ node.setAdaptViewportToResizedControl(!Boolean.FALSE.equals(getHint(KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL)));
+ Double z = getHint(KEY_ZOOM_AMOUNT);
+ if(z != null) {
+ util.setTransform(AffineTransform.getScaleInstance(z, z));
+ node.setTransform(AffineTransform.getScaleInstance(z, z));
+ }
+ boolean visible = !Boolean.TRUE.equals(getHint(Hints.KEY_DISABLE_PAINTING));
+ node.setVisible(visible);
+ oldRoot = getContext().getCanvasNode();
+ getContext().setCanvasNode(node);
+ }
+
+ public void update() {
+ if (bounds != null) {
+ Rectangle2D vp = bounds.getControlBounds();
+ controlSize = new Point2D.Double(vp.getMaxX(), vp.getMaxY());
+ centerPointControl = new Point2D.Double(vp.getCenterX(), vp.getCenterY());
+ centerPointCanvas = util.controlToCanvas(centerPointControl, null);
+ }
+ }
+
+ public TransformNode getNode() {
+ return node;
+ }
+
+ /**
+ * Ensures that the navigation node handled by this participant contains the
+ * specified transform and that {@link Hints#KEY_CANVAS_TRANSFORM} will
+ * contain the same value.
+ *
+ * @param transform
+ */
+ public void setTransform(AffineTransform transform) {
+ getNode().setTransform(transform);
+ transformListener.transformChanged(transform);
+ }
+
+ @SGCleanup
+ public void cleanupSG() {
+ node.remove();
+ node = null;
+ getContext().setCanvasNode(oldRoot);
+ }
+
+
+ /** Arrow key translate */
+ public final static Key KEY_TRANSLATE_AMOUNT = new KeyOf(Integer.class);
+ public final static Key KEY_ZOOM_AMOUNT = new KeyOf(Double.class);
+ public final static Key KEY_ROTATE_AMOUNT = new KeyOf(Double.class);
+
+ /** Amount of arrow key translate */
+ public final static int DEFAULT_KEYBOARD_TRANSLATE_AMOUNT = 30;
+ public final static double DEFAULT_KEYBOARD_ZOOM_AMOUNT = 1.2;
+ public final static double DEFAULT_KEYBOARD_ROTATE_AMOUNT = 0.1;
+ public final static Margins DEFAULT_ZOOM_TO_FIT_MARGINS = RulerPainter.RULER_MARINGS2;
+ public final static Margins DEFAULT_ZOOM_TO_FIT_MARGINS_NO_RULER = MarginUtils.MARGINS2;
+
+ public final static int ROTATE_GRAB_ID = -666;
+
+ @EventHandler(priority = 0)
+ public boolean handleEvent(CommandEvent e) {
+ assertDependencies();
+ update();
+ Command c = e.command;
+ boolean panDisabled = Boolean.TRUE.equals(getHint(KEY_DISABLE_PAN)) ? true : false;
+ boolean zoomDisabled = Boolean.TRUE.equals(getHint(KEY_DISABLE_ZOOM)) ? true : false;
+
+ // Arrow key panning
+ if (Commands.PAN_LEFT.equals(c) && !panDisabled) {
+ util.translateWithControlCoordinates(
+ new Point2D.Double(
+ getTranslateAmount(), 0));
+ return true;
+ }
+ if (Commands.PAN_RIGHT.equals(c) && !panDisabled) {
+ util.translateWithControlCoordinates(
+ new Point2D.Double(
+ -getTranslateAmount(), 0));
+ return true;
+ }
+ if (Commands.PAN_UP.equals(c) && !panDisabled) {
+ util.translateWithControlCoordinates(
+ new Point2D.Double(
+ 0, getTranslateAmount()));
+ return true;
+ }
+ if (Commands.PAN_DOWN.equals(c) && !panDisabled) {
+ util.translateWithControlCoordinates(
+ new Point2D.Double(0, -getTranslateAmount()));
+ return true;
+ }
+ if (Commands.ZOOM_IN.equals(c) && !zoomDisabled) {
+ if (centerPointControl == null) return false;
+ double scaleFactor = getZoomAmount();
+ scaleFactor = limitScaleFactor(scaleFactor);
+ util.zoomAroundControlPoint(scaleFactor, centerPointControl);
+ }
+ if (Commands.ZOOM_OUT.equals(c) && !zoomDisabled) {
+ if (centerPointControl == null) return false;
+ double scaleFactor = 1 / getZoomAmount();
+ scaleFactor = limitScaleFactor(scaleFactor);
+ util.zoomAroundControlPoint(scaleFactor, centerPointControl);
+ }
+
+ if (Commands.ROTATE_CANVAS_CCW.equals(c)) {
+ if (centerPointCanvas == null) return false;
+ util.rotate(centerPointCanvas, -getRotateAmount());
+ setDirty();
+ return true;
+ }
+ if (Commands.ROTATE_CANVAS_CW.equals(c)) {
+ if (centerPointCanvas == null) return false;
+ util.rotate(centerPointCanvas, getRotateAmount());
+ setDirty();
+ return true;
+ }
+ if (Commands.ROTATE_CANVAS_CCW_GRAB.equals(c)) {
+ if (centerPointCanvas == null) return false;
+ util.rotate(centerPointCanvas, -getRotateAmount());
+ grab.grabCanvas(ROTATE_GRAB_ID, centerPointCanvas);
+ grab.grabCanvas(ROTATE_GRAB_ID - 1, centerPointCanvas);
+ setDirty();
+ return true;
+ }
+ if (Commands.ROTATE_CANVAS_CW_GRAB.equals(c)) {
+ if (centerPointCanvas == null) return false;
+ util.rotate(centerPointCanvas, getRotateAmount());
+ grab.grabCanvas(ROTATE_GRAB_ID, centerPointCanvas);
+ grab.grabCanvas(ROTATE_GRAB_ID - 1, centerPointCanvas);
+ setDirty();
+ return true;
+ }
+ if (Commands.ROTATE_CANVAS_CCW_RELEASE.equals(c)) {
+ if (centerPointCanvas == null) return false;
+ grab.releaseCanvas(ROTATE_GRAB_ID);
+ grab.releaseCanvas(ROTATE_GRAB_ID - 1);
+ setDirty();
+ return true;
+ }
+ if (Commands.ROTATE_CANVAS_CW_RELEASE.equals(c)) {
+ if (centerPointCanvas == null) return false;
+ grab.releaseCanvas(ROTATE_GRAB_ID);
+ grab.releaseCanvas(ROTATE_GRAB_ID - 1);
+ setDirty();
+ return true;
+ }
+ if (Commands.ENABLE_PAINTING.equals(c)) {
+ Boolean t = getHint(Hints.KEY_DISABLE_PAINTING);
+ removeHint(Hints.KEY_DISABLE_PAINTING);
+ boolean processed = Boolean.TRUE.equals(t);
+ if (processed)
+ setDirty();
+ return processed;
+ }
+ if (Commands.ZOOM_TO_FIT.equals(c) && !zoomDisabled) {
+ boolean result = zoomToFit();
+ if (!result)
+ result = zoomToPage();
+ return result;
+ }
+ if (Commands.ZOOM_TO_SELECTION.equals(c) && !zoomDisabled && selection != null) {
+ if (controlSize==null) return false;
+ IDiagram d = getHint(DiagramHints.KEY_DIAGRAM);
+ if (d==null) return false;
+
+ Set selections = selection.getAllSelections();
+ Rectangle2D diagramRect = ElementUtils.getSurroundingElementBoundsOnDiagram(selections);
+ if (diagramRect == null) return false;
+ if (diagramRect.getWidth() <= 0 && diagramRect.getHeight() <= 0)
+ return false;
+
+ // HACK: prevents straight connections from being unzoomable.
+ if (diagramRect.getWidth() <= 0)
+ org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 0, 0, 1, 1);
+ if (diagramRect.getHeight() <= 0)
+ org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 1, 1, 0, 0);
+
+ // Show area
+ Rectangle2D controlArea = new Rectangle2D.Double(0, 0, controlSize.getX(), controlSize.getY());
+ util.fitArea(controlArea, diagramRect, getZoomToFitMargins(getHintStack()));
+ return true;
+ }
+ if (Commands.ZOOM_TO_PAGE.equals(c) && !zoomDisabled) {
+ return zoomToPage();
+ }
+
+ return false;
+ }
+
+ private boolean zoomToFit() {
+ if (controlSize==null) return false;
+ IDiagram d = getHint(DiagramHints.KEY_DIAGRAM);
+ if (d==null) return false;
+
+ Rectangle2D diagramRect = DiagramUtils.getContentRect(d);
+ if (diagramRect==null) return false;
+ if (diagramRect.isEmpty())
+ return false;
+
+ // Show area
+ Rectangle2D controlArea = new Rectangle2D.Double(0, 0, controlSize.getX(), controlSize.getY());
+ //System.out.println("zoomToFit(" + controlArea + ", " + diagramRect + ")");
+ util.fitArea(controlArea, diagramRect, getZoomToFitMargins(getHintStack()));
+
+ return true;
+ }
+
+ private boolean zoomToPage() {
+ if (controlSize==null) return false;
+ PageDesc desc = getHint(Hints.KEY_PAGE_DESC);
+ if (desc == null)
+ return false;
+ if (desc.isInfinite())
+ return false;
+
+ // Show page
+ Rectangle2D diagramRect = new Rectangle2D.Double();
+ desc.getPageRectangle(diagramRect);
+ if (diagramRect.isEmpty())
+ return false;
+
+ Rectangle2D controlArea = new Rectangle2D.Double(0, 0, controlSize.getX(), controlSize.getY());
+ //System.out.println("zoomToPage(" + controlArea + ", " + diagramRect + ")");
+ util.fitArea(controlArea, diagramRect, getZoomToFitMargins(getHintStack()));
+ return true;
+ }
+
+ public double getTranslateAmount()
+ {
+ Integer h = getHint(KEY_TRANSLATE_AMOUNT);
+ if (h==null) return DEFAULT_KEYBOARD_TRANSLATE_AMOUNT;
+ return h;
+ }
+
+ public double getZoomAmount()
+ {
+ Integer h = getHint(KEY_TRANSLATE_AMOUNT);
+ if (h==null) return DEFAULT_KEYBOARD_ZOOM_AMOUNT;
+ return h;
+ }
+
+ public double getRotateAmount()
+ {
+ Integer h = getHint(KEY_ROTATE_AMOUNT);
+ if (h==null) return DEFAULT_KEYBOARD_ROTATE_AMOUNT;
+ return h;
+ }
+
+ public double limitScaleFactor(double scaleFactor) {
+ Double inLimit = getHint(PanZoomRotateHandler.KEY_ZOOM_IN_LIMIT);
+ Double outLimit = getHint(PanZoomRotateHandler.KEY_ZOOM_OUT_LIMIT);
+
+ if (inLimit == null && scaleFactor < 1)
+ return scaleFactor;
+ if (outLimit == null && scaleFactor > 1)
+ return scaleFactor;
+
+ AffineTransform view = util.getTransform();
+ double currentScale = GeometryUtils.getScale(view) * 100.0;
+ double newScale = currentScale * scaleFactor;
+
+ if (inLimit != null && newScale > currentScale && newScale > inLimit) {
+ if (currentScale < inLimit)
+ scaleFactor = inLimit / currentScale;
+ else
+ scaleFactor = 1.0;
+ } else if (outLimit != null && newScale < currentScale && newScale < outLimit) {
+ if (currentScale > outLimit)
+ scaleFactor = outLimit / currentScale;
+ else
+ scaleFactor = 1.0;
+ }
+ return scaleFactor;
+ }
+
+ public static Margins getZoomToFitMargins(IHintObservable hints) {
+ Margins h = hints.getHint(DiagramHints.KEY_MARGINS);
+ if (h == null) {
+ Boolean b = hints.getHint(RulerPainter.KEY_RULER_ENABLED);
+ boolean rulerEnabled = b == null || Boolean.TRUE.equals(b);
+ if (rulerEnabled) {
+ return PanZoomRotateHandler.DEFAULT_ZOOM_TO_FIT_MARGINS;
+ } else {
+ return PanZoomRotateHandler.DEFAULT_ZOOM_TO_FIT_MARGINS_NO_RULER;
+ }
+ }
+ return h;
+ }
+
}
\ No newline at end of file