Bug 12496 fixed: zoom_rect could lead to a crash in log scale
[scilab.git] / scilab / modules / renderer / src / java / org / scilab / modules / renderer / JoGLView / interaction / DragZoomRotateInteraction.java
1 /*
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  *
6  * This file must be used under the terms of the CeCILL.
7  * This source file is licensed as described in the file COPYING, which
8  * you should have received as part of this distribution.  The terms
9  * are also available at
10  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
11  */
12 package org.scilab.modules.renderer.JoGLView.interaction;
13
14 import java.awt.Component;
15 import java.awt.Cursor;
16 import java.awt.event.MouseAdapter;
17 import java.awt.event.MouseEvent;
18 import java.awt.event.MouseListener;
19 import java.awt.event.MouseMotionAdapter;
20 import java.awt.event.MouseMotionListener;
21 import java.awt.event.MouseWheelEvent;
22 import java.awt.event.MouseWheelListener;
23
24 import org.scilab.modules.commons.OS;
25 import org.scilab.modules.graphic_objects.axes.Axes;
26 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
27 import org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties;
28 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
29 import org.scilab.modules.renderer.JoGLView.util.ScaleUtils;
30
31 /**
32  * This class manage figure interaction.
33  *
34  * @author Pierre Lando
35  */
36 public class DragZoomRotateInteraction extends FigureInteraction {
37
38     private static final int XY_TRANSLATION_MODIFIER = MouseEvent.BUTTON1_MASK;
39     private static final int Z_TRANSLATION_MODIFIER = MouseEvent.BUTTON1_MASK | MouseEvent.ALT_MASK;
40     private static final int ROTATION_MODIFIER = MouseEvent.BUTTON3_MASK;
41     private static final int MACOSX_ROTATION_MODIFIER = MouseEvent.BUTTON1_MASK | MouseEvent.CTRL_MASK;
42
43     /**
44      * The box size is multiply by this value.
45      */
46     private static final double ZOOM_FACTOR = 1.02;
47
48     private final MouseListener mouseListener;
49     private final MouseWheelListener mouseWheelListener;
50     private final MouseMotionListener mouseMotionListener;
51
52     /**
53      * Last important mouse event.
54      */
55     private MouseEvent previousEvent;
56     private Axes currentAxes;
57
58
59     /**
60      * Default constructor.
61      * @param drawerVisitor parent drawer visitor.
62      */
63     public DragZoomRotateInteraction(DrawerVisitor drawerVisitor) {
64         super(drawerVisitor);
65         mouseMotionListener = new FigureMouseMotionListener();
66         mouseWheelListener = new FigureMouseWheelListener();
67         mouseListener = new FigureMouseListener();
68     }
69
70     @Override
71     protected void changeEnable(boolean isEnable) {
72         Component component = getDrawerVisitor().getComponent();
73         if (component != null) {
74             if (isEnable) {
75                 component.addMouseListener(mouseListener);
76                 component.addMouseWheelListener(mouseWheelListener);
77             } else {
78                 component.removeMouseListener(mouseListener);
79                 component.removeMouseMotionListener(mouseMotionListener);
80                 component.removeMouseWheelListener(mouseWheelListener);
81             }
82         }
83     }
84
85     public void setTranslationEnable(boolean status) {
86         ((FigureMouseMotionListener)mouseMotionListener).setTranslateEnable(status);
87     }
88
89     /**
90      * This {@see MouseListner} activate the {@see MouseMotionListener} when at least
91      * one button is pressed.
92      * The event is saved in {@see previousEvent}
93      */
94     private class FigureMouseListener extends MouseAdapter implements MouseListener {
95
96         private int pressedButtons = 0;
97
98         @Override
99         public void mousePressed(MouseEvent e) {
100             if (pressedButtons == 0) {
101                 previousEvent = e;
102                 if (currentAxes == null) {
103                     currentAxes = getUnderlyingAxes(e.getPoint());
104                     if (currentAxes != null) {
105                         getDrawerVisitor().getComponent().addMouseMotionListener(mouseMotionListener);
106                         switch (e.getButton()) {
107                             case MouseEvent.BUTTON1 :
108                                 Cursor cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
109                                 e.getComponent().setCursor(cursor);
110                                 break;
111                             case MouseEvent.BUTTON3 :
112                                 // FIXME: add rotation cursor here
113                                 break;
114                         }
115                     }
116                 }
117             }
118             pressedButtons++;
119         }
120
121         @Override
122         public void mouseReleased(MouseEvent e) {
123             if (pressedButtons > 0) {
124                 pressedButtons--;
125             }
126
127             if (pressedButtons == 0) {
128                 getDrawerVisitor().getComponent().removeMouseMotionListener(mouseMotionListener);
129                 currentAxes = null;
130             }
131             e.getComponent().setCursor(Cursor.getDefaultCursor());
132         }
133     }
134
135     /**
136      * This {@see MouseWheelListener} manage zoom/un-zoom on the figure.
137      */
138     private class FigureMouseWheelListener implements MouseWheelListener {
139
140         @Override
141         public void mouseWheelMoved(MouseWheelEvent e) {
142             Axes axes = getUnderlyingAxes(e.getPoint());
143             if (axes != null) {
144                 double scale = Math.pow(ZOOM_FACTOR, e.getUnitsToScroll());
145                 Double[] bounds = axes.getDisplayedBounds();
146                 double[][] factors = axes.getScaleTranslateFactors();
147
148                 double xDelta = (bounds[1] - bounds[0]) / 2;
149                 double xMiddle = (bounds[1] + bounds[0]) / 2;
150                 bounds[0] = xMiddle - xDelta * scale;
151                 bounds[1] = xMiddle + xDelta * scale;
152
153                 double yDelta = (bounds[3] - bounds[2]) / 2;
154                 double yMiddle = (bounds[3] + bounds[2]) / 2;
155                 bounds[2] = yMiddle - yDelta * scale;
156                 bounds[3] = yMiddle + yDelta * scale;
157
158                 double zDelta = (bounds[5] - bounds[4]) / 2;
159                 double zMiddle = (bounds[5] + bounds[4]) / 2;
160                 bounds[4] = zMiddle - zDelta * scale;
161                 bounds[5] = zMiddle + zDelta * scale;
162
163                 bounds[0] = bounds[0] * factors[0][0] + factors[1][0];
164                 bounds[1] = bounds[1] * factors[0][0] + factors[1][0];
165                 bounds[2] = bounds[2] * factors[0][1] + factors[1][1];
166                 bounds[3] = bounds[3] * factors[0][1] + factors[1][1];
167                 bounds[4] = bounds[4] * factors[0][2] + factors[1][2];
168                 bounds[5] = bounds[5] * factors[0][2] + factors[1][2];
169
170                 Boolean zoomed = tightZoomBounds(axes, bounds);
171
172                 bounds[0] = (bounds[0] - factors[1][0]) / factors[0][0];
173                 bounds[1] = (bounds[1] - factors[1][0]) / factors[0][0];
174                 bounds[2] = (bounds[2] - factors[1][1]) / factors[0][1];
175                 bounds[3] = (bounds[3] - factors[1][1]) / factors[0][1];
176                 bounds[4] = (bounds[4] - factors[1][2]) / factors[0][2];
177                 bounds[5] = (bounds[5] - factors[1][2]) / factors[0][2];
178
179                 boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
180                 ScaleUtils.applyInverseLogScaleToBounds(bounds, logFlags);
181
182                 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_BOX__, bounds);
183                 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_ENABLED__, zoomed);
184
185             }
186         }
187     }
188
189     private static void applyUnlog(Double[] bounds, Axes axes) {
190         if (axes.getXAxisLogFlag()) {
191             bounds[0] = Math.pow(10, bounds[0]);
192             bounds[1] = Math.pow(10, bounds[1]);
193         }
194
195         if (axes.getYAxisLogFlag()) {
196             bounds[2] = Math.pow(10, bounds[2]);
197             bounds[3] = Math.pow(10, bounds[3]);
198         }
199
200         if (axes.getZAxisLogFlag()) {
201             bounds[4] = Math.pow(10, bounds[4]);
202             bounds[5] = Math.pow(10, bounds[5]);
203         }
204     }
205
206     /**
207      * This {@see MouseMotionListener} manage rotation and translation on the figure.
208      */
209     private class FigureMouseMotionListener extends MouseMotionAdapter implements MouseMotionListener {
210
211         private boolean translateEnabled = true;
212
213         public void setTranslateEnable(boolean status) {
214             translateEnabled = status;
215         }
216
217         @Override
218         public void mouseMoved(MouseEvent e) {
219             /*
220              * Mac OS X specific case: the users first presses CTRL and then left-clic.
221              */
222             if (OS.get() == OS.MAC && e.isControlDown() && e.getButton() == 0) {
223                 doRotation(e);
224             }
225         }
226         public void mouseDragged(MouseEvent e) {
227             switch (e.getModifiers()) {
228                 case MACOSX_ROTATION_MODIFIER:
229                     /*
230                      * Mac OS X specific case: the users first left-clic and then presses CTRL
231                      */
232                     if (OS.get() == OS.MAC && e.isControlDown()) {
233                         doRotation(e);
234                         break;
235                     }
236                 case XY_TRANSLATION_MODIFIER:
237                     if (translateEnabled) {
238                         doXYTranslation(e);
239                     }
240                     break;
241                 case Z_TRANSLATION_MODIFIER:
242                     doZTranslation(e);
243                     break;
244                 case ROTATION_MODIFIER:
245                     doRotation(e);
246                     break;
247             }
248             previousEvent = e;
249         }
250
251         private void doRotation(MouseEvent e) {
252             int dx = e.getX() - previousEvent.getX();
253             int dy = e.getY() - previousEvent.getY();
254
255             if (currentAxes != null) {
256                 Double[] angles = currentAxes.getRotationAngles();
257                 angles[0] -= dy / 4.0;
258                 angles[1] -= Math.signum(Math.sin(Math.toRadians(angles[0]))) * (dx / 4.0);
259                 GraphicController.getController().setProperty(currentAxes.getIdentifier(), GraphicObjectProperties.__GO_ROTATION_ANGLES__, angles);
260             }
261         }
262
263         private void doXYTranslation(MouseEvent e) {
264             int dx = e.getX() - previousEvent.getX();
265             int dy = e.getY() - previousEvent.getY();
266
267             if (currentAxes != null) {
268                 if (currentAxes.getZoomEnabled()) {
269                     Double[] bounds = currentAxes.getDisplayedBounds();
270
271                     Integer[] winSize = (Integer[]) GraphicController.getController().getProperty(currentAxes.getParent(), GraphicObjectProperties.__GO_AXES_SIZE__);
272                     Double[] axesBounds = (Double[]) GraphicController.getController().getProperty(currentAxes.getIdentifier(), GraphicObjectProperties.__GO_AXES_BOUNDS__);
273                     Double[] axesMargins = (Double[]) GraphicController.getController().getProperty(currentAxes.getIdentifier(), GraphicObjectProperties.__GO_MARGINS__);
274                     Integer view = (Integer) GraphicController.getController().getProperty(currentAxes.getIdentifier(), GraphicObjectProperties.__GO_VIEW__);
275
276                     // Compute ratio from pixel move to user displayed data bounds
277                     double xDelta = Math.abs(bounds[0] - bounds[1]) / (winSize[0] * axesBounds[2] * (1 - axesMargins[0] - axesMargins[1]));
278                     double yDelta = Math.abs(bounds[2] - bounds[3]) / (winSize[1] * axesBounds[3] * (1 - axesMargins[2] - axesMargins[3]));
279
280                     if (view == 0) {
281                         // 2D View
282                         bounds[0] -= xDelta * dx;
283                         bounds[1] -= xDelta * dx;
284
285                         bounds[2] += yDelta * dy;
286                         bounds[3] += yDelta * dy;
287                     } else {
288                         // 3D view
289                         double orientation = - Math.signum(Math.cos(Math.toRadians(currentAxes.getRotationAngles()[0])));
290                         double angle = - orientation * Math.toRadians(currentAxes.getRotationAngles()[1]);
291
292                         double rotatedDX = dx * Math.sin(angle) + dy * Math.cos(angle);
293                         double rotatedDY = dx * Math.cos(angle) - dy * Math.sin(angle);
294
295                         bounds[0] -= xDelta * rotatedDX * orientation;
296                         bounds[1] -= xDelta * rotatedDX * orientation;
297
298                         bounds[2] += yDelta * rotatedDY;
299                         bounds[3] += yDelta * rotatedDY;
300                     }
301
302                     Boolean zoomed = tightZoomBoxToDataBounds(currentAxes, bounds);
303                     boolean[] logFlags = { currentAxes.getXAxisLogFlag(), currentAxes.getYAxisLogFlag(), currentAxes.getZAxisLogFlag()};
304                     ScaleUtils.applyInverseLogScaleToBounds(bounds, logFlags);
305
306                     GraphicController.getController().setProperty(currentAxes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_BOX__, bounds);
307                     GraphicController.getController().setProperty(currentAxes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_ENABLED__, zoomed);
308                 }
309             }
310         }
311
312         private void doZTranslation(MouseEvent e) {
313             int dy = e.getY() - previousEvent.getY();
314
315             if (currentAxes != null) {
316                 Double[] bounds = currentAxes.getDisplayedBounds();
317
318                 double zDelta = (bounds[5] - bounds[4]) / 100;
319
320                 bounds[4] += zDelta * dy;
321                 bounds[5] += zDelta * dy;
322
323                 Boolean zoomed = tightZoomBoxToDataBounds(currentAxes, bounds);
324                 boolean[] logFlags = { currentAxes.getXAxisLogFlag(), currentAxes.getYAxisLogFlag(), currentAxes.getZAxisLogFlag()};
325                 ScaleUtils.applyInverseLogScaleToBounds(bounds, logFlags);
326
327                 GraphicController.getController().setProperty(currentAxes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_BOX__, bounds);
328                 GraphicController.getController().setProperty(currentAxes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_ENABLED__, zoomed);
329             }
330         }
331
332         /**
333          * Tight given bounds to axes data bounds.
334          * Bounds length along axes are conserved.
335          * @param axes the given axes.
336          * @param zoomBounds the zoomBounds.
337          * @return true if actually there is a zoom.
338          */
339         private boolean tightZoomBoxToDataBounds(Axes axes, Double[] zoomBounds) {
340             boolean zoomed = false;
341             Double[] dataBounds = axes.getMaximalDisplayedBounds();
342             for (int i : new int[] {0, 2, 4}) {
343                 if (zoomBounds[i] < dataBounds[i]) {
344                     double delta = dataBounds[i] - zoomBounds[i];
345                     zoomBounds[i] = dataBounds[i]; // zoomBounds[i] += delta;
346                     zoomBounds[i + 1] += delta;
347                 } else {
348                     zoomed = true;
349                 }
350             }
351
352             for (int i : new int[] {1, 3, 5}) {
353                 if (zoomBounds[i] > dataBounds[i]) {
354                     double delta = dataBounds[i] - zoomBounds[i];
355                     zoomBounds[i] = dataBounds[i]; // zoomBounds[i] += delta;
356                     zoomBounds[i - 1] += delta;
357                 } else {
358                     zoomed = true;
359                 }
360             }
361
362             return zoomed;
363         }
364     }
365 }