Fix some Coverity issues after 33b80b 50fe1d 5f73e9 and c473f9
[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  * Copyright (C) 2018 - St├ęphane MOTTELET
6  *
7  * Copyright (C) 2012 - 2016 - Scilab Enterprises
8  *
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.
15  */
16 package org.scilab.modules.renderer.JoGLView.interaction;
17
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;
27
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;
38
39
40 /**
41  * This class manage figure interaction.
42  *
43  * @author Pierre Lando
44  */
45 public class DragZoomRotateInteraction extends FigureInteraction {
46
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;
51
52     /**
53      * The box size is multiply by this value.
54      */
55     private static final double ZOOM_FACTOR = 1.02;
56
57     private final MouseListener mouseListener;
58     private final MouseWheelListener mouseWheelListener;
59     private final MouseMotionListener mouseMotionListener;
60
61     /**
62      * Last important mouse event.
63      */
64     private MouseEvent previousEvent;
65     private Axes[] currentAxes;
66
67
68     /**
69      * Default constructor.
70      * @param drawerVisitor parent drawer visitor.
71      */
72     public DragZoomRotateInteraction(DrawerVisitor drawerVisitor) {
73         super(drawerVisitor);
74         mouseMotionListener = new FigureMouseMotionListener();
75         mouseWheelListener = new FigureMouseWheelListener();
76         mouseListener = new FigureMouseListener();
77         currentAxes = new Axes[0];
78     }
79
80     @Override
81     protected void changeEnable(boolean isEnable) {
82         Component component = getDrawerVisitor().getComponent();
83         if (component != null) {
84             if (isEnable) {
85                 component.addMouseListener(mouseListener);
86                 component.addMouseWheelListener(mouseWheelListener);
87             } else {
88                 component.removeMouseListener(mouseListener);
89                 component.removeMouseMotionListener(mouseMotionListener);
90                 component.removeMouseWheelListener(mouseWheelListener);
91             }
92         }
93     }
94
95     public void setTranslationEnable(boolean status) {
96         ((FigureMouseMotionListener)mouseMotionListener).setTranslateEnable(status);
97     }
98
99     /**
100      * This {@see MouseListner} activate the {@see MouseMotionListener} when at least
101      * one button is pressed.
102      * The event is saved in {@see previousEvent}
103      */
104     private class FigureMouseListener extends MouseAdapter implements MouseListener {
105
106         private int pressedButtons = 0;
107
108         @Override
109         public void mousePressed(MouseEvent e) {
110             if (pressedButtons == 0) {
111                 previousEvent = e;
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);
120                                 break;
121                             case MouseEvent.BUTTON3 :
122                                 // FIXME: add rotation cursor here
123                                 break;
124                         }
125                     }
126                 }
127             }
128             pressedButtons++;
129         }
130
131         @Override
132         public void mouseReleased(MouseEvent e) {
133             if (pressedButtons > 0) {
134                 pressedButtons--;
135             }
136
137             if (pressedButtons == 0) {
138                 getDrawerVisitor().getComponent().removeMouseMotionListener(mouseMotionListener);
139                 currentAxes = new Axes[0];
140             }
141             e.getComponent().setCursor(Cursor.getDefaultCursor());
142         }
143     }
144
145     /**
146      * This {@see MouseWheelListener} manage zoom/un-zoom on the figure.
147      */
148     private class FigureMouseWheelListener implements MouseWheelListener {
149
150         private void applyZoom(Axes axes, double scale, double[] position) {
151             if (axes != null) {
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;
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         }
189         @Override
190         public void mouseWheelMoved(MouseWheelEvent e) {
191             Axes[] allAxes;
192             if (e.isControlDown()) {
193                 allAxes = getAllVisibleAxes(e.getPoint());
194             } else {
195                 allAxes = getAllUnderlyingAxes(e.getPoint());
196             }
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]};
222                     }
223                 } else if (surfInfo != null && surfInfo.point != null) {
224                     position = new double[] {surfInfo.point.getX(), surfInfo.point.getY(), surfInfo.point.getZ()};
225                 }
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;
232                     } else {
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};
235                     }
236                 }
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);
240             }
241         }
242     }
243
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]);
248         }
249
250         if (axes.getYAxisLogFlag()) {
251             bounds[2] = Math.pow(10, bounds[2]);
252             bounds[3] = Math.pow(10, bounds[3]);
253         }
254
255         if (axes.getZAxisLogFlag()) {
256             bounds[4] = Math.pow(10, bounds[4]);
257             bounds[5] = Math.pow(10, bounds[5]);
258         }
259     }
260
261     /**
262      * This {@see MouseMotionListener} manage rotation and translation on the figure.
263      */
264     private class FigureMouseMotionListener extends MouseMotionAdapter implements MouseMotionListener {
265
266         private boolean translateEnabled = true;
267
268         public void setTranslateEnable(boolean status) {
269             translateEnabled = status;
270         }
271
272         @Override
273         public void mouseMoved(MouseEvent e) {
274             /*
275              * Mac OS X specific case: the users first presses CTRL and then left-clic.
276              */
277             if (OS.get() == OS.MAC && e.isControlDown() && e.getButton() == 0) {
278                 doRotation(e);
279             }
280         }
281         public void mouseDragged(MouseEvent e) {
282             switch (e.getModifiers()) {
283                 case MACOSX_ROTATION_MODIFIER:
284                     /*
285                      * Mac OS X specific case: the users first left-clic and then presses CTRL
286                      */
287                     if (OS.get() == OS.MAC && e.isControlDown()) {
288                         doRotation(e);
289                         break;
290                     }
291                     break;
292                 case XY_TRANSLATION_MODIFIER:
293                     if (translateEnabled) {
294                         doXYTranslation(e);
295                     }
296                     break;
297                 case Z_TRANSLATION_MODIFIER:
298                     doZTranslation(e);
299                     break;
300                 case ROTATION_MODIFIER:
301                     doRotation(e);
302                     break;
303             }
304             previousEvent = e;
305         }
306
307         private void doRotation(MouseEvent e) {
308             int dx = e.getX() - previousEvent.getX();
309             int dy = e.getY() - previousEvent.getY();
310
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);
317                 }
318             }
319         }
320
321         private void doXYTranslation(MouseEvent e) {
322             int dx = e.getX() - previousEvent.getX();
323             int dy = e.getY() - previousEvent.getY();
324
325             for (Axes axes : currentAxes) {
326                 if (axes.getZoomEnabled()) {
327                     Double[] bounds = axes.getDisplayedBounds();
328
329                     Integer[] winSize = (Integer[]) GraphicController.getController().getProperty(axes.getParent(), GraphicObjectProperties.__GO_AXES_SIZE__);
330                     if (winSize == null) {
331                         // We are in a Frame
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();
336                     }
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__);
340
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]));
344
345                     if (view == 0) {
346                         // 2D View
347                         bounds[0] -= xDelta * dx;
348                         bounds[1] -= xDelta * dx;
349
350                         bounds[2] += yDelta * dy;
351                         bounds[3] += yDelta * dy;
352                     } else {
353                         // 3D view
354                         double orientation = - Math.signum(Math.cos(Math.toRadians(axes.getRotationAngles()[0])));
355                         double angle = - orientation * Math.toRadians(axes.getRotationAngles()[1]);
356
357                         double rotatedDX = dx * Math.sin(angle) + dy * Math.cos(angle);
358                         double rotatedDY = dx * Math.cos(angle) - dy * Math.sin(angle);
359
360                         bounds[0] -= xDelta * rotatedDX * orientation;
361                         bounds[1] -= xDelta * rotatedDX * orientation;
362
363                         bounds[2] += yDelta * rotatedDY;
364                         bounds[3] += yDelta * rotatedDY;
365                     }
366
367                     Boolean zoomed = tightZoomBoxToDataBounds(axes, bounds);
368                     boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
369                     ScaleUtils.applyInverseLogScaleToBounds(bounds, logFlags);
370
371                     GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_BOX__, bounds);
372                     GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_ENABLED__, zoomed);
373                 }
374             }
375         }
376
377         private void doZTranslation(MouseEvent e) {
378             int dy = e.getY() - previousEvent.getY();
379
380             for (Axes axes : currentAxes) {
381                 Double[] bounds = axes.getDisplayedBounds();
382
383                 double zDelta = (bounds[5] - bounds[4]) / 100;
384
385                 bounds[4] += zDelta * dy;
386                 bounds[5] += zDelta * dy;
387
388                 Boolean zoomed = tightZoomBoxToDataBounds(axes, bounds);
389                 boolean[] logFlags = { axes.getXAxisLogFlag(), axes.getYAxisLogFlag(), axes.getZAxisLogFlag()};
390                 ScaleUtils.applyInverseLogScaleToBounds(bounds, logFlags);
391
392                 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_BOX__, bounds);
393                 GraphicController.getController().setProperty(axes.getIdentifier(), GraphicObjectProperties.__GO_ZOOM_ENABLED__, zoomed);
394             }
395         }
396
397         /**
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.
403          */
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;
412                 } else {
413                     zoomed = true;
414                 }
415             }
416
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;
422                 } else {
423                     zoomed = true;
424                 }
425             }
426
427             return zoomed;
428         }
429     }
430 }