2 * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3 * Copyright (C) 2009-2012 - DIGITEO - Pierre Lando
4 * Copyright (C) 2013 - Scilab Enterprises - Calixte DENIZET
5 * Copyright (C) 2018 - Stéphane MOTTELET
7 * Copyright (C) 2012 - 2016 - Scilab Enterprises
9 * This file is hereby licensed under the terms of the GNU GPL v2.0,
10 * pursuant to article 5.3.4 of the CeCILL v.2.1.
11 * This file was originally licensed under the terms of the CeCILL v2.1,
12 * and continues to be available under such terms.
13 * For more information, see the COPYING file which you should have received
14 * along with this program.
16 package org.scilab.modules.renderer.JoGLView.interaction;
18 import java.awt.Component;
19 import java.awt.Cursor;
20 import java.awt.event.MouseAdapter;
21 import java.awt.event.MouseEvent;
22 import java.awt.event.MouseListener;
23 import java.awt.event.MouseMotionAdapter;
24 import java.awt.event.MouseMotionListener;
25 import java.awt.event.MouseWheelEvent;
26 import java.awt.event.MouseWheelListener;
28 import org.scilab.modules.commons.OS;
29 import org.scilab.modules.graphic_objects.axes.Axes;
30 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
31 import org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties;
32 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
33 import org.scilab.modules.renderer.JoGLView.util.ScaleUtils;
34 import org.scilab.modules.renderer.CallRenderer;
35 import org.scilab.modules.graphic_objects.PolylineData;
36 import org.scilab.modules.renderer.utils.EntityPicker;
37 import org.scilab.modules.renderer.utils.EntityPicker.SurfaceInfo;
41 * This class manage figure interaction.
43 * @author Pierre Lando
45 public class DragZoomRotateInteraction extends FigureInteraction {
47 private static final int XY_TRANSLATION_MODIFIER = MouseEvent.BUTTON1_MASK;
48 private static final int Z_TRANSLATION_MODIFIER = MouseEvent.BUTTON1_MASK | MouseEvent.ALT_MASK;
49 private static final int ROTATION_MODIFIER = MouseEvent.BUTTON3_MASK;
50 private static final int MACOSX_ROTATION_MODIFIER = MouseEvent.BUTTON1_MASK | MouseEvent.CTRL_MASK;
53 * The box size is multiply by this value.
55 private static final double ZOOM_FACTOR = 1.02;
57 private final MouseListener mouseListener;
58 private final MouseWheelListener mouseWheelListener;
59 private final MouseMotionListener mouseMotionListener;
62 * Last important mouse event.
64 private MouseEvent previousEvent;
65 private Axes[] currentAxes;
69 * Default constructor.
70 * @param drawerVisitor parent drawer visitor.
72 public DragZoomRotateInteraction(DrawerVisitor drawerVisitor) {
74 mouseMotionListener = new FigureMouseMotionListener();
75 mouseWheelListener = new FigureMouseWheelListener();
76 mouseListener = new FigureMouseListener();
77 currentAxes = new Axes[0];
81 protected void changeEnable(boolean isEnable) {
82 Component component = getDrawerVisitor().getComponent();
83 if (component != null) {
85 component.addMouseListener(mouseListener);
86 component.addMouseWheelListener(mouseWheelListener);
88 component.removeMouseListener(mouseListener);
89 component.removeMouseMotionListener(mouseMotionListener);
90 component.removeMouseWheelListener(mouseWheelListener);
95 public void setTranslationEnable(boolean status) {
96 ((FigureMouseMotionListener)mouseMotionListener).setTranslateEnable(status);
100 * This {@see MouseListner} activate the {@see MouseMotionListener} when at least
101 * one button is pressed.
102 * The event is saved in {@see previousEvent}
104 private class FigureMouseListener extends MouseAdapter implements MouseListener {
106 private int pressedButtons = 0;
109 public void mousePressed(MouseEvent e) {
110 if (pressedButtons == 0) {
112 if (currentAxes.length == 0) {
113 currentAxes = getAllUnderlyingAxes(e.getPoint());
114 if (currentAxes.length > 0) {
115 getDrawerVisitor().getComponent().addMouseMotionListener(mouseMotionListener);
116 switch (e.getButton()) {
117 case MouseEvent.BUTTON1 :
118 Cursor cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
119 e.getComponent().setCursor(cursor);
121 case MouseEvent.BUTTON3 :
122 // FIXME: add rotation cursor here
132 public void mouseReleased(MouseEvent e) {
133 if (pressedButtons > 0) {
137 if (pressedButtons == 0) {
138 getDrawerVisitor().getComponent().removeMouseMotionListener(mouseMotionListener);
139 currentAxes = new Axes[0];
141 e.getComponent().setCursor(Cursor.getDefaultCursor());
146 * This {@see MouseWheelListener} manage zoom/un-zoom on the figure.
148 private class FigureMouseWheelListener implements MouseWheelListener {
150 private void applyZoom(Axes axes, double scale, double[] position) {
152 Double[] bounds = axes.getDisplayedBounds();
153 double[][] factors = axes.getScaleTranslateFactors();
154 // Zoom only if position of mouse cursor is inside the bounds
155 if (position[0] > bounds[0] && position[0] < bounds[1] &&
156 position[1] > bounds[2] && position[1] < bounds[3] &&
157 position[2] > bounds[4] && position[2] < bounds[5]) {
158 bounds[0] = position[0] + (bounds[0] - position[0]) * scale;
159 bounds[1] = position[0] + (bounds[1] - position[0]) * scale;
160 bounds[2] = position[1] + (bounds[2] - position[1]) * scale;
161 bounds[3] = position[1] + (bounds[3] - position[1]) * scale;
162 bounds[4] = position[2] + (bounds[4] - position[2]) * scale;
163 bounds[5] = position[2] + (bounds[5] - position[2]) * scale;
165 bounds[0] = bounds[0] * factors[0][0] + factors[1][0];
166 bounds[1] = bounds[1] * factors[0][0] + factors[1][0];
167 bounds[2] = bounds[2] * factors[0][1] + factors[1][1];
168 bounds[3] = bounds[3] * factors[0][1] + factors[1][1];
169 bounds[4] = bounds[4] * factors[0][2] + factors[1][2];
170 bounds[5] = bounds[5] * factors[0][2] + factors[1][2];
172 Boolean zoomed = tightZoomBounds(axes, bounds);
174 bounds[0] = (bounds[0] - factors[1][0]) / factors[0][0];
175 bounds[1] = (bounds[1] - factors[1][0]) / factors[0][0];
176 bounds[2] = (bounds[2] - factors[1][1]) / factors[0][1];
177 bounds[3] = (bounds[3] - factors[1][1]) / factors[0][1];
178 bounds[4] = (bounds[4] - factors[1][2]) / factors[0][2];
179 bounds[5] = (bounds[5] - factors[1][2]) / factors[0][2];
181 boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
182 ScaleUtils.applyInverseLogScaleToBounds(bounds, logFlags);
184 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_BOX__, bounds);
185 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_ENABLED__, zoomed);
190 public void mouseWheelMoved(MouseWheelEvent e) {
192 if (e.isControlDown()) {
193 allAxes = getAllVisibleAxes(e.getPoint());
195 allAxes = getAllUnderlyingAxes(e.getPoint());
197 double scale = Math.pow(ZOOM_FACTOR, e.getUnitsToScroll());
198 double[] position = null;
199 for (Axes axes : allAxes) {
200 // beware: components of axes.getDisplayedBounds() are log10 of bounds
201 // when axes.get[X,Y or Z]AxisLogFlag() is true
202 Double[] bounds = axes.getDisplayedBounds();
203 Double[] realBounds = axes.getDisplayedBounds();
204 boolean[] logFlags = {axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
205 ScaleUtils.applyInverseLogScaleToBounds(realBounds, logFlags);
206 // If possible, center the homothecy at a point of a polyline or a point of a surface
207 EntityPicker ep = new EntityPicker();
208 // TODO: picking info should give the Z in order to take the closest object
209 // between polyline and surface
210 Integer polylineUid = ep.pick(axes.getParentFigure(), e.getX(), e.getY());
211 SurfaceInfo surfInfo = ep.pickSurface(axes.getParentFigure(), new Integer[] {e.getX(), e.getY()});
212 if (polylineUid != null) {
213 EntityPicker.PickedPoint picked = ep.pickPoint(polylineUid, e.getX(), e.getY());
214 double[] datax = (double[])PolylineData.getDataX(polylineUid);
215 double[] datay = (double[])PolylineData.getDataY(polylineUid);
216 double[] dataz = (double[])PolylineData.getDataZ(polylineUid);
217 // in 3D ep.pick (for PolyLines) does not check that picked point is in the viewing box
218 if (datax[picked.point] > realBounds[0] && datax[picked.point] < realBounds[1] &&
219 datay[picked.point] > realBounds[2] && datay[picked.point] < realBounds[3] &&
220 dataz[picked.point] > realBounds[4] && dataz[picked.point] < realBounds[5]) {
221 position = new double[] {datax[picked.point], datay[picked.point], dataz[picked.point]};
223 } else if (surfInfo.surface != null) {
224 position = new double[] {surfInfo.point.getX(), surfInfo.point.getY(), surfInfo.point.getZ()};
226 if (position == null) {
227 if (axes.getView() == 0) {
228 // 2D : failsafe homothecy centered at mouse coordinates
229 double[] pixelPosition = {e.getX(), e.getY(), 0};
230 position = CallRenderer.get2dViewFromPixelCoordinates(axes.getIdentifier(), pixelPosition);
231 position[2] = (bounds[4] + bounds[5]) / 2;
233 // 3D : failsafe homothecy centered at center of viewing box
234 position = new double[] {(bounds[1] + bounds[0]) / 2, (bounds[3] + bounds[2]) / 2, (bounds[5] + bounds[4]) / 2};
237 // the zomm is applied in the linear scale, hence coordinates have to be tranformed accordingly
238 ScaleUtils.applyLogScale(position, logFlags);
239 applyZoom(axes, scale, position);
244 private static void applyUnlog(Double[] bounds, Axes axes) {
245 if (axes.getXAxisLogFlag()) {
246 bounds[0] = Math.pow(10, bounds[0]);
247 bounds[1] = Math.pow(10, bounds[1]);
250 if (axes.getYAxisLogFlag()) {
251 bounds[2] = Math.pow(10, bounds[2]);
252 bounds[3] = Math.pow(10, bounds[3]);
255 if (axes.getZAxisLogFlag()) {
256 bounds[4] = Math.pow(10, bounds[4]);
257 bounds[5] = Math.pow(10, bounds[5]);
262 * This {@see MouseMotionListener} manage rotation and translation on the figure.
264 private class FigureMouseMotionListener extends MouseMotionAdapter implements MouseMotionListener {
266 private boolean translateEnabled = true;
268 public void setTranslateEnable(boolean status) {
269 translateEnabled = status;
273 public void mouseMoved(MouseEvent e) {
275 * Mac OS X specific case: the users first presses CTRL and then left-clic.
277 if (OS.get() == OS.MAC && e.isControlDown() && e.getButton() == 0) {
281 public void mouseDragged(MouseEvent e) {
282 switch (e.getModifiers()) {
283 case MACOSX_ROTATION_MODIFIER:
285 * Mac OS X specific case: the users first left-clic and then presses CTRL
287 if (OS.get() == OS.MAC && e.isControlDown()) {
292 case XY_TRANSLATION_MODIFIER:
293 if (translateEnabled) {
297 case Z_TRANSLATION_MODIFIER:
300 case ROTATION_MODIFIER:
307 private void doRotation(MouseEvent e) {
308 int dx = e.getX() - previousEvent.getX();
309 int dy = e.getY() - previousEvent.getY();
311 for (Axes axes : currentAxes) {
312 if (axes.getView() != 0) {
313 Double[] angles = axes.getRotationAngles();
314 angles[0] -= dy / 4.0;
315 angles[1] -= Math.signum(Math.sin(Math.toRadians(angles[0]))) * (dx / 4.0);
316 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ROTATION_ANGLES__, angles);
321 private void doXYTranslation(MouseEvent e) {
322 int dx = e.getX() - previousEvent.getX();
323 int dy = e.getY() - previousEvent.getY();
325 for (Axes axes : currentAxes) {
326 if (axes.getZoomEnabled()) {
327 Double[] bounds = axes.getDisplayedBounds();
329 Integer[] winSize = (Integer[]) GraphicController.getController().getProperty(axes.getParent(), GraphicObjectProperties.__GO_AXES_SIZE__);
330 if (winSize == null) {
332 Double[] position = (Double[]) GraphicController.getController().getProperty(axes.getParent(), GraphicObjectProperties.__GO_POSITION__);
333 winSize = new Integer[2];
334 winSize[0] = position[2].intValue();
335 winSize[1] = position[3].intValue();
337 Double[] axesBounds = (Double[]) GraphicController.getController().getProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_AXES_BOUNDS__);
338 Double[] axesMargins = (Double[]) GraphicController.getController().getProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_MARGINS__);
339 Integer view = (Integer) GraphicController.getController().getProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_VIEW__);
341 // Compute ratio from pixel move to user displayed data bounds
342 double xDelta = Math.abs(bounds[0] - bounds[1]) / (winSize[0] * axesBounds[2] * (1 - axesMargins[0] - axesMargins[1]));
343 double yDelta = Math.abs(bounds[2] - bounds[3]) / (winSize[1] * axesBounds[3] * (1 - axesMargins[2] - axesMargins[3]));
347 bounds[0] -= xDelta * dx;
348 bounds[1] -= xDelta * dx;
350 bounds[2] += yDelta * dy;
351 bounds[3] += yDelta * dy;
354 double orientation = - Math.signum(Math.cos(Math.toRadians(axes.getRotationAngles()[0])));
355 double angle = - orientation * Math.toRadians(axes.getRotationAngles()[1]);
357 double rotatedDX = dx * Math.sin(angle) + dy * Math.cos(angle);
358 double rotatedDY = dx * Math.cos(angle) - dy * Math.sin(angle);
360 bounds[0] -= xDelta * rotatedDX * orientation;
361 bounds[1] -= xDelta * rotatedDX * orientation;
363 bounds[2] += yDelta * rotatedDY;
364 bounds[3] += yDelta * rotatedDY;
367 Boolean zoomed = tightZoomBoxToDataBounds(axes, bounds);
368 boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
369 ScaleUtils.applyInverseLogScaleToBounds(bounds, logFlags);
371 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_BOX__, bounds);
372 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_ENABLED__, zoomed);
377 private void doZTranslation(MouseEvent e) {
378 int dy = e.getY() - previousEvent.getY();
380 for (Axes axes : currentAxes) {
381 Double[] bounds = axes.getDisplayedBounds();
383 double zDelta = (bounds[5] - bounds[4]) / 100;
385 bounds[4] += zDelta * dy;
386 bounds[5] += zDelta * dy;
388 Boolean zoomed = tightZoomBoxToDataBounds(axes, bounds);
389 boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
390 ScaleUtils.applyInverseLogScaleToBounds(bounds, logFlags);
392 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_BOX__, bounds);
393 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_ENABLED__, zoomed);
398 * Tight given bounds to axes data bounds.
399 * Bounds length along axes are conserved.
400 * @param axes the given axes.
401 * @param zoomBounds the zoomBounds.
402 * @return true if actually there is a zoom.
404 private boolean tightZoomBoxToDataBounds(Axes axes, Double[] zoomBounds) {
405 boolean zoomed = false;
406 Double[] dataBounds = axes.getMaximalDisplayedBounds();
407 for (int i : new int[] {0, 2, 4}) {
408 if (zoomBounds[i] < dataBounds[i]) {
409 double delta = dataBounds[i] - zoomBounds[i];
410 zoomBounds[i] = dataBounds[i]; // zoomBounds[i] += delta;
411 zoomBounds[i + 1] += delta;
417 for (int i : new int[] {1, 3, 5}) {
418 if (zoomBounds[i] > dataBounds[i]) {
419 double delta = dataBounds[i] - zoomBounds[i];
420 zoomBounds[i] = dataBounds[i]; // zoomBounds[i] += delta;
421 zoomBounds[i - 1] += delta;