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