]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/participant/PanZoomRotateHandler.java
Fixed invalid comparisons which were identified by Eclipse IDE 2018-09
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / participant / PanZoomRotateHandler.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.participant;
13
14 import static org.simantics.g2d.canvas.Hints.KEY_CANVAS_TRANSFORM;
15
16 import java.awt.geom.AffineTransform;
17 import java.awt.geom.Point2D;
18 import java.awt.geom.Rectangle2D;
19 import java.util.Set;
20
21 import org.simantics.g2d.canvas.Hints;
22 import org.simantics.g2d.canvas.ICanvasContext;
23 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
24 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
25 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
26 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
27 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
28 import org.simantics.g2d.diagram.DiagramHints;
29 import org.simantics.g2d.diagram.DiagramUtils;
30 import org.simantics.g2d.diagram.IDiagram;
31 import org.simantics.g2d.diagram.participant.Selection;
32 import org.simantics.g2d.element.ElementUtils;
33 import org.simantics.g2d.element.IElement;
34 import org.simantics.g2d.scenegraph.SceneGraphConstants;
35 import org.simantics.g2d.utils.GeometryUtils;
36 import org.simantics.scenegraph.INode;
37 import org.simantics.scenegraph.g2d.G2DParentNode;
38 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
39 import org.simantics.scenegraph.g2d.events.command.Command;
40 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
41 import org.simantics.scenegraph.g2d.events.command.Commands;
42 import org.simantics.scenegraph.g2d.nodes.NavigationNode;
43 import org.simantics.scenegraph.g2d.nodes.TransformNode;
44 import org.simantics.scenegraph.utils.NodeUtil;
45 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
46 import org.simantics.utils.datastructures.hints.IHintContext.Key;
47 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
48 import org.simantics.utils.datastructures.hints.IHintListener;
49 import org.simantics.utils.datastructures.hints.IHintObservable;
50 import org.simantics.utils.page.MarginUtils;
51 import org.simantics.utils.page.MarginUtils.Margins;
52 import org.simantics.utils.page.PageDesc;
53 import org.simantics.utils.threads.ThreadUtils;
54
55 /**
56  * This participant handles pan, zoom, zoom to fit and rotate commands.
57  * 
58  * Hints:
59  *  KEY_TRANSLATE_AMOUNT
60  *  KEY_ZOOM_AMOUNT
61  *  KEY_ROTATE_AMOUNT
62  *  KEY_ZOOM_TO_FIT_MARGINS
63  *  KEY_ZOOM_OUT_LIMIT
64  *  KEY_ZOOM_IN_LIMIT
65  * 
66  * @author Toni Kalajainen
67  * @author Tuukka Lehtonen
68  */
69 public class PanZoomRotateHandler extends AbstractCanvasParticipant {
70
71     /**
72      * Express whether or not the view should attempt to keep the current zoom
73      * level when the canvas parenting control is resized. If the viewport is
74      * set to be adapted to the resized control, the view transform will be
75      * adjusted to accommodate for this. Otherwise the view transform will be
76      * left alone when the control is resized.
77      * 
78      * If hint is not specified, the default value is <code>true</code>.
79      * 
80      * See {@link NavigationNode} for the zoom level keep implementation.
81      */
82     public final static Key KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL = new KeyOf(Boolean.class, "ADAPT_VIEWPORT_TO_RESIZED_CONTROL");
83
84     /**
85      * Limit for zooming in expressed as a percentage (100% == 1:1 == identity
86      * view transform). If null, there is no limit. Used with an
87      * ICanvasContext's hint context.
88      */
89     public final static Key KEY_ZOOM_OUT_LIMIT = new KeyOf(Double.class, "ZOOM_OUT_LIMIT");
90
91     /**
92      * Limit for zooming in expressed as a percentage (100% == 1:1 == identity
93      * view transform). If null there is no limit. Used with an
94      * ICanvasContext's hint context.
95      */
96     public final static Key KEY_ZOOM_IN_LIMIT = new KeyOf(Double.class, "ZOOM_IN_LIMIT");
97
98     public final static Key KEY_DISABLE_ZOOM = new KeyOf(Boolean.class, "DISABLE_ZOOM");
99
100     public final static Key KEY_DISABLE_PAN = new KeyOf(Boolean.class, "DISABLE_PAN");
101
102
103     @Dependency CanvasGrab grab;
104     @Dependency TransformUtil util;
105     @Dependency KeyUtil keys;
106     @Reference  Selection selection;
107     @Reference  CanvasBoundsParticipant bounds;
108
109     // Capture center point
110     Point2D centerPointControl;
111     Point2D centerPointCanvas;
112     Point2D controlSize;
113
114     final Boolean navigationEnabled;
115
116     protected NavigationNode node = null;
117     protected G2DParentNode oldRoot = null;
118
119     public PanZoomRotateHandler() {
120         this(true);
121     }
122
123     public PanZoomRotateHandler(boolean navigationEnabled) {
124         this.navigationEnabled = navigationEnabled;
125     }
126
127     NavigationNode.TransformListener transformListener = new NavigationNode.TransformListener() {
128         @Override
129         public void transformChanged(final AffineTransform transform) {
130             ThreadUtils.asyncExec(PanZoomRotateHandler.this.getContext().getThreadAccess(), new Runnable() {
131                 @Override
132                 public void run() {
133                     if (isRemoved())
134                         return;
135                     //System.out.println("PanZoomRotateHandler: set canvas transform: " + transform);
136                     setHint(KEY_CANVAS_TRANSFORM, transform);
137                 }
138             });
139         }
140     };
141
142     IHintListener hintListener = new HintListenerAdapter() {
143         @Override
144         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
145             if (node != null) {
146                 if (key == Hints.KEY_DISABLE_PAINTING) {
147                     boolean visible = !Boolean.TRUE.equals(newValue);
148                     if (visible != node.isVisible())
149                         node.setVisible(Boolean.valueOf(visible));
150                 } else if (key == KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL) {
151                     boolean noKeepZoom = Boolean.FALSE.equals(newValue);
152                     if (noKeepZoom == node.getAdaptViewportToResizedControl())
153                         node.setAdaptViewportToResizedControl(Boolean.valueOf(!noKeepZoom));
154                 } else if (key == KEY_ZOOM_OUT_LIMIT) {
155                     node.setZoomOutLimit((Double) newValue);
156                 } else if (key == KEY_ZOOM_IN_LIMIT) {
157                     node.setZoomInLimit((Double) newValue);
158                 } else if (key == KEY_DISABLE_ZOOM) {
159                     node.setZoomEnabled(!Boolean.TRUE.equals(getHint(KEY_DISABLE_ZOOM)));
160                 }
161             }
162         }
163     };
164
165     @Override
166     public void addedToContext(ICanvasContext ctx) {
167         super.addedToContext(ctx);
168         ctx.getDefaultHintContext().addKeyHintListener(Hints.KEY_DISABLE_PAINTING, hintListener);
169         ctx.getDefaultHintContext().addKeyHintListener(KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL, hintListener);
170         ctx.getDefaultHintContext().addKeyHintListener(KEY_ZOOM_OUT_LIMIT, hintListener);
171         ctx.getDefaultHintContext().addKeyHintListener(KEY_ZOOM_IN_LIMIT, hintListener);
172         ctx.getDefaultHintContext().addKeyHintListener(KEY_DISABLE_ZOOM, hintListener);
173         ctx.getDefaultHintContext().addKeyHintListener(KEY_DISABLE_PAN, hintListener);
174     }
175
176     @Override
177     public void removedFromContext(ICanvasContext ctx) {
178         ctx.getDefaultHintContext().removeKeyHintListener(KEY_ZOOM_IN_LIMIT, hintListener);
179         ctx.getDefaultHintContext().removeKeyHintListener(KEY_ZOOM_OUT_LIMIT, hintListener);
180         ctx.getDefaultHintContext().removeKeyHintListener(KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL, hintListener);
181         ctx.getDefaultHintContext().removeKeyHintListener(Hints.KEY_DISABLE_PAINTING, hintListener);
182         ctx.getDefaultHintContext().removeKeyHintListener(KEY_DISABLE_ZOOM, hintListener);
183         ctx.getDefaultHintContext().removeKeyHintListener(KEY_DISABLE_PAN, hintListener);
184         super.removedFromContext(ctx);
185     }
186
187     protected Class<? extends NavigationNode> getNavigationNodeClass() {
188         return NavigationNode.class;
189     }
190
191     @SGInit
192     public void initSG(G2DParentNode parent) {
193         // Replace old NAVIGATION_NODE with a new one
194         INode oldnav = NodeUtil.getRootNode(parent).getNode(SceneGraphConstants.NAVIGATION_NODE_NAME);
195         if(oldnav != null) {
196             node = oldnav.appendParent(SceneGraphConstants.NAVIGATION_NODE_NAME, getNavigationNodeClass());
197             // FIXME : oldnav seems to be the same node as parent (most of the cases).
198             // Deleting it will cause plenty of code to fail, since they refer to the node directly.
199             // The bug was not shown, since deleting() a Node did not actually wipe its structures (until now).             
200             // oldnav.delete();
201         } else {
202             node = parent.addNode(SceneGraphConstants.NAVIGATION_NODE_NAME, getNavigationNodeClass());
203         }
204         node.setLookupId(SceneGraphConstants.NAVIGATION_NODE_NAME);
205         node.setZIndex(0);
206         node.setTransformListener(transformListener);
207         node.setNavigationEnabled(navigationEnabled);
208         node.setZoomEnabled(!Boolean.TRUE.equals(getHint(KEY_DISABLE_ZOOM)));
209         node.setAdaptViewportToResizedControl(!Boolean.FALSE.equals(getHint(KEY_ADAPT_VIEWPORT_TO_RESIZED_CONTROL)));
210         Double z = getHint(KEY_ZOOM_AMOUNT);
211         if(z != null) {
212             util.setTransform(AffineTransform.getScaleInstance(z, z));
213             node.setTransform(AffineTransform.getScaleInstance(z, z));
214         }
215         boolean visible = !Boolean.TRUE.equals(getHint(Hints.KEY_DISABLE_PAINTING));
216         node.setVisible(visible);
217         oldRoot = getContext().getCanvasNode();
218         getContext().setCanvasNode(node);
219     }
220
221     public void update() {
222         if (bounds != null) {
223             Rectangle2D vp = bounds.getControlBounds();
224             controlSize = new Point2D.Double(vp.getMaxX(), vp.getMaxY());
225             centerPointControl = new Point2D.Double(vp.getCenterX(), vp.getCenterY());
226             centerPointCanvas = util.controlToCanvas(centerPointControl, null);
227         }
228     }
229
230     public TransformNode getNode() {
231         return node;
232     }
233
234     /**
235      * Ensures that the navigation node handled by this participant contains the
236      * specified transform and that {@link Hints#KEY_CANVAS_TRANSFORM} will
237      * contain the same value.
238      * 
239      * @param transform
240      */
241     public void setTransform(AffineTransform transform) {
242         getNode().setTransform(transform);
243         transformListener.transformChanged(transform);
244     }
245
246     @SGCleanup
247     public void cleanupSG() {
248         node.remove();
249         node = null;
250         getContext().setCanvasNode(oldRoot);
251     }
252
253
254     /** Arrow key translate */
255     public final static Key KEY_TRANSLATE_AMOUNT = new KeyOf(Integer.class);
256     public final static Key KEY_ZOOM_AMOUNT = new KeyOf(Double.class);
257     public final static Key KEY_ROTATE_AMOUNT = new KeyOf(Double.class);
258
259     /** Amount of arrow key translate */
260     public final static int DEFAULT_KEYBOARD_TRANSLATE_AMOUNT = 30;
261     public final static double DEFAULT_KEYBOARD_ZOOM_AMOUNT = 1.2;
262     public final static double DEFAULT_KEYBOARD_ROTATE_AMOUNT = 0.1;
263     public final static Margins DEFAULT_ZOOM_TO_FIT_MARGINS = RulerPainter.RULER_MARINGS2;
264     public final static Margins DEFAULT_ZOOM_TO_FIT_MARGINS_NO_RULER = MarginUtils.MARGINS2;
265
266     public final static int ROTATE_GRAB_ID = -666;
267
268     @EventHandler(priority = 0)
269     public boolean handleEvent(CommandEvent e) {
270         assertDependencies();
271         update();
272         Command c = e.command;
273         boolean panDisabled = Boolean.TRUE.equals(getHint(KEY_DISABLE_PAN)) ? true : false;
274         boolean zoomDisabled = Boolean.TRUE.equals(getHint(KEY_DISABLE_ZOOM)) ? true : false;
275
276         // Arrow key panning
277         if (Commands.PAN_LEFT.equals(c) && !panDisabled) {
278             util.translateWithControlCoordinates(
279                     new Point2D.Double(
280                             getTranslateAmount(), 0));
281             return true;
282         }
283         if (Commands.PAN_RIGHT.equals(c) && !panDisabled) {
284             util.translateWithControlCoordinates(
285                     new Point2D.Double(
286                             -getTranslateAmount(), 0));
287             return true;
288         }
289         if (Commands.PAN_UP.equals(c) && !panDisabled) {
290             util.translateWithControlCoordinates(
291                     new Point2D.Double(
292                             0, getTranslateAmount()));
293             return true;
294         }
295         if (Commands.PAN_DOWN.equals(c) && !panDisabled) {
296             util.translateWithControlCoordinates(
297                     new Point2D.Double(0, -getTranslateAmount()));
298             return true;
299         }
300         if (Commands.ZOOM_IN.equals(c) && !zoomDisabled) {
301             if (centerPointControl == null) return false;
302             double scaleFactor = getZoomAmount();
303             scaleFactor = limitScaleFactor(scaleFactor);
304             util.zoomAroundControlPoint(scaleFactor, centerPointControl);
305         }
306         if (Commands.ZOOM_OUT.equals(c) && !zoomDisabled) {
307             if (centerPointControl == null) return false;
308             double scaleFactor = 1 / getZoomAmount();
309             scaleFactor = limitScaleFactor(scaleFactor);
310             util.zoomAroundControlPoint(scaleFactor, centerPointControl);
311         }
312
313         if (Commands.ROTATE_CANVAS_CCW.equals(c)) {
314             if (centerPointCanvas == null) return false;
315             util.rotate(centerPointCanvas, -getRotateAmount());
316             setDirty();
317             return true;
318         }
319         if (Commands.ROTATE_CANVAS_CW.equals(c)) {
320             if (centerPointCanvas == null) return false;
321             util.rotate(centerPointCanvas, getRotateAmount());
322             setDirty();
323             return true;
324         }
325         if (Commands.ROTATE_CANVAS_CCW_GRAB.equals(c)) {
326             if (centerPointCanvas == null) return false;
327             util.rotate(centerPointCanvas, -getRotateAmount());
328             grab.grabCanvas(ROTATE_GRAB_ID, centerPointCanvas);
329             grab.grabCanvas(ROTATE_GRAB_ID - 1, centerPointCanvas);
330             setDirty();
331             return true;
332         }
333         if (Commands.ROTATE_CANVAS_CW_GRAB.equals(c)) {
334             if (centerPointCanvas == null) return false;
335             util.rotate(centerPointCanvas, getRotateAmount());
336             grab.grabCanvas(ROTATE_GRAB_ID, centerPointCanvas);
337             grab.grabCanvas(ROTATE_GRAB_ID - 1, centerPointCanvas);
338             setDirty();
339             return true;
340         }
341         if (Commands.ROTATE_CANVAS_CCW_RELEASE.equals(c)) {
342             if (centerPointCanvas == null) return false;
343             grab.releaseCanvas(ROTATE_GRAB_ID);
344             grab.releaseCanvas(ROTATE_GRAB_ID - 1);
345             setDirty();
346             return true;
347         }
348         if (Commands.ROTATE_CANVAS_CW_RELEASE.equals(c)) {
349             if (centerPointCanvas == null) return false;
350             grab.releaseCanvas(ROTATE_GRAB_ID);
351             grab.releaseCanvas(ROTATE_GRAB_ID - 1);
352             setDirty();
353             return true;
354         }
355         if (Commands.ENABLE_PAINTING.equals(c)) {
356             Boolean t = getHint(Hints.KEY_DISABLE_PAINTING);
357             removeHint(Hints.KEY_DISABLE_PAINTING);
358             boolean processed = Boolean.TRUE.equals(t);
359             if (processed)
360                 setDirty();
361             return processed;
362         }
363         if (Commands.ZOOM_TO_FIT.equals(c) && !zoomDisabled) {
364             boolean result = zoomToFit();
365             if (!result)
366                 result = zoomToPage();
367             return result;
368         }
369         if (Commands.ZOOM_TO_SELECTION.equals(c) && !zoomDisabled && selection != null) {
370             if (controlSize==null) return false;
371             IDiagram d = getHint(DiagramHints.KEY_DIAGRAM);
372             if (d==null) return false;
373
374             Set<IElement> selections = selection.getAllSelections();
375             Rectangle2D diagramRect = ElementUtils.getSurroundingElementBoundsOnDiagram(selections);
376             if (diagramRect == null) return false;
377             if (diagramRect.getWidth() <= 0 && diagramRect.getHeight() <= 0)
378                 return false;
379
380             // HACK: prevents straight connections from being unzoomable.
381             if (diagramRect.getWidth() <= 0)
382                 org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 0, 0, 1, 1);
383             if (diagramRect.getHeight() <= 0)
384                 org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 1, 1, 0, 0);
385
386             // Show area
387             Rectangle2D controlArea = new Rectangle2D.Double(0, 0, controlSize.getX(), controlSize.getY());
388             util.fitArea(controlArea, diagramRect, getZoomToFitMargins(getHintStack()));
389             return true;
390         }
391         if (Commands.ZOOM_TO_PAGE.equals(c) && !zoomDisabled) {
392             return zoomToPage();
393         }
394
395         return false;
396     }
397
398     private boolean zoomToFit() {
399         if (controlSize==null) return false;
400         IDiagram d = getHint(DiagramHints.KEY_DIAGRAM);
401         if (d==null) return false;
402
403         Rectangle2D diagramRect = DiagramUtils.getContentRect(d);
404         if (diagramRect==null) return false;
405         if (diagramRect.isEmpty())
406             return false;
407
408         // Show area
409         Rectangle2D controlArea = new Rectangle2D.Double(0, 0, controlSize.getX(), controlSize.getY());
410         //System.out.println("zoomToFit(" + controlArea + ", " + diagramRect + ")");
411         util.fitArea(controlArea, diagramRect, getZoomToFitMargins(getHintStack()));
412
413         return true;
414     }
415
416     private boolean zoomToPage() {
417         if (controlSize==null) return false;
418         PageDesc desc = getHint(Hints.KEY_PAGE_DESC);
419         if (desc == null)
420             return false;
421         if (desc.isInfinite())
422             return false;
423
424         // Show page
425         Rectangle2D diagramRect = new Rectangle2D.Double();
426         desc.getPageRectangle(diagramRect);
427         if (diagramRect.isEmpty())
428             return false;
429
430         Rectangle2D controlArea = new Rectangle2D.Double(0, 0, controlSize.getX(), controlSize.getY());
431         //System.out.println("zoomToPage(" + controlArea + ", " + diagramRect + ")");
432         util.fitArea(controlArea, diagramRect, getZoomToFitMargins(getHintStack()));
433         return true;
434     }
435
436     public double getTranslateAmount()
437     {
438         Integer h = getHint(KEY_TRANSLATE_AMOUNT);
439         if (h==null) return DEFAULT_KEYBOARD_TRANSLATE_AMOUNT;
440         return h;
441     }
442
443     public double getZoomAmount()
444     {
445         Integer h = getHint(KEY_TRANSLATE_AMOUNT);
446         if (h==null) return DEFAULT_KEYBOARD_ZOOM_AMOUNT;
447         return h;
448     }
449
450     public double getRotateAmount()
451     {
452         Integer h = getHint(KEY_ROTATE_AMOUNT);
453         if (h==null) return DEFAULT_KEYBOARD_ROTATE_AMOUNT;
454         return h;
455     }
456
457     public double limitScaleFactor(double scaleFactor) {
458         Double inLimit = getHint(PanZoomRotateHandler.KEY_ZOOM_IN_LIMIT);
459         Double outLimit = getHint(PanZoomRotateHandler.KEY_ZOOM_OUT_LIMIT);
460
461         if (inLimit == null && scaleFactor < 1)
462             return scaleFactor;
463         if (outLimit == null && scaleFactor > 1)
464             return scaleFactor;
465
466         AffineTransform view = util.getTransform();
467         double currentScale = GeometryUtils.getScale(view) * 100.0;
468         double newScale = currentScale * scaleFactor;
469
470         if (inLimit != null && newScale > currentScale && newScale > inLimit) {
471             if (currentScale < inLimit)
472                 scaleFactor = inLimit / currentScale;
473             else
474                 scaleFactor = 1.0;
475         } else if (outLimit != null && newScale < currentScale && newScale < outLimit) {
476             if (currentScale > outLimit)
477                 scaleFactor = outLimit / currentScale;
478             else
479                 scaleFactor = 1.0;
480         }
481         return scaleFactor;
482     }
483
484     public static Margins getZoomToFitMargins(IHintObservable hints) {
485         Margins h = hints.getHint(DiagramHints.KEY_MARGINS);
486         if (h == null) {
487             Boolean b = hints.getHint(RulerPainter.KEY_RULER_ENABLED);
488             boolean rulerEnabled = b == null || Boolean.TRUE.equals(b);
489             if (rulerEnabled) {
490                 return PanZoomRotateHandler.DEFAULT_ZOOM_TO_FIT_MARGINS;
491             } else {
492                 return PanZoomRotateHandler.DEFAULT_ZOOM_TO_FIT_MARGINS_NO_RULER;
493             }
494         }
495         return h;
496     }
497     
498 }