GPL + CeCILL Header change
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / utils / BlockPositioning.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009 - DIGITEO - Antoine ELIAS
4  * Copyright (C) 2011-2015 - Scilab Enterprises - Clement DAVID
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  * === LICENSE_END ===
15  *
16  */
17
18 package org.scilab.modules.xcos.utils;
19
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.Map;
24
25 import org.scilab.modules.xcos.JavaController;
26 import org.scilab.modules.xcos.Kind;
27 import org.scilab.modules.xcos.ObjectProperties;
28 import org.scilab.modules.xcos.VectorOfDouble;
29 import org.scilab.modules.xcos.block.BasicBlock;
30 import org.scilab.modules.xcos.graph.XcosDiagram;
31 import org.scilab.modules.xcos.io.scicos.BasicBlockInfo;
32 import org.scilab.modules.xcos.port.BasicPort;
33 import org.scilab.modules.xcos.port.Orientation;
34
35 import com.mxgraph.model.mxGeometry;
36 import com.mxgraph.model.mxIGraphModel;
37 import com.mxgraph.util.mxStyleUtils;
38
39 /**
40  * Helpers to place port on a block.
41  */
42 public final class BlockPositioning {
43
44     /**
45      * The default grid size. This value is used when the grid size isn't accessible (on the palette).
46      */
47     public static final double DEFAULT_GRIDSIZE = Double.MIN_NORMAL;
48     /** The rotation step of the clockwise and anticlockwise rotation */
49     public static final double ROTATION_STEP = 90;
50     /** The max valid rotation value (always 360 degres) */
51     public static final double MAX_ROTATION = 360;
52
53     /** This class is a static singleton, thus it must not be instantiated */
54     private BlockPositioning() {
55     }
56
57     /**
58      * Dispatch ports on Block's _WEST_ side.
59      *
60      * @param block
61      *            The block we have to work on.
62      * @param ports
63      *            The ports we have to move on the side.
64      */
65     public static void updateWestPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
66
67         double gridSize = diag.getGridSize();
68
69         final mxGeometry blockGeom = block.getGeometry();
70         assert blockGeom != null;
71         final int portsSize = ports.size();
72         final double blockLength = blockGeom.getHeight();
73         final double segLength = blockLength / (portsSize + 1);
74
75         diag.getModel().beginUpdate();
76         for (int i = 0; i < portsSize; ++i) {
77             final BasicPort port = (ports.get(i));
78             final mxGeometry portGeom = port.getGeometry();
79
80             double nonVariantPosition = -portGeom.getWidth();
81             final int order = i;
82             double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
83
84             portGeom.setX(nonVariantPosition);
85             portGeom.setY(alignedPosition);
86
87             port.setLabelPosition(Orientation.WEST);
88         }
89         diag.getModel().endUpdate();
90     }
91
92     /**
93      * Calculate an aligned port position.
94      *
95      * @param gridSize
96      *            the grid size
97      * @param segLength
98      *            the side length
99      * @param i
100      *            the current working index
101      * @return the aligned position on the grid.
102      */
103     private static double calculateAlignedPosition(final double gridSize, final double segLength, int i) {
104         /*
105          * The base position is the origin of the port geometry. It is the upper-left corner position.
106          */
107         final double basePosition = (i + 1) * segLength;
108
109         /*
110          * The aligned base position is the base position aligned on the grid.
111          */
112         final double alignedBasePosition = basePosition - Math.IEEEremainder(basePosition, gridSize);
113
114         /*
115          * The aligned position is the base position translated from origin to the middle of the port.
116          */
117         final double alignedPosition = alignedBasePosition - (BasicPort.DEFAULT_PORTSIZE / 2.0);
118
119         return alignedPosition;
120     }
121
122     /**
123      * Dispatch ports on Block's _NORTH_ side.
124      *
125      * @param block
126      *            The block we have to work on.
127      * @param ports
128      *            The ports we have to move on the side.
129      */
130     public static void updateNorthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
131         double gridSize = diag.getGridSize();
132
133         final mxGeometry blockGeom = block.getGeometry();
134         assert blockGeom != null;
135         final int portsSize = ports.size();
136         final double blockLength = blockGeom.getWidth();
137         final double segLength = blockLength / (portsSize + 1);
138
139         diag.getModel().beginUpdate();
140         for (int i = 0; i < portsSize; ++i) {
141             final BasicPort port = (ports.get(i));
142             final mxGeometry portGeom = port.getGeometry();
143
144             double nonVariantPosition = -portGeom.getHeight();
145             final int order = i;
146             double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
147
148             portGeom.setX(alignedPosition);
149             portGeom.setY(nonVariantPosition);
150
151             port.setLabelPosition(Orientation.NORTH);
152         }
153         diag.getModel().endUpdate();
154     }
155
156     /**
157      * Dispatch ports on Block's _EAST_ side.
158      *
159      * @param block
160      *            The block we have to work on.
161      * @param ports
162      *            The ports we have to move on the side.
163      */
164     public static void updateEastPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
165         double gridSize = diag.getGridSize();
166
167         final mxGeometry blockGeom = block.getGeometry();
168         assert blockGeom != null;
169         final int portsSize = ports.size();
170         final double blockLength = blockGeom.getHeight();
171         final double segLength = blockLength / (portsSize + 1);
172
173         diag.getModel().beginUpdate();
174         for (int i = 0; i < portsSize; ++i) {
175             final BasicPort port = (ports.get(i));
176             final mxGeometry portGeom = port.getGeometry();
177
178             double nonVariantPosition = blockGeom.getWidth();
179             final int order = i;
180             double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
181
182             portGeom.setX(nonVariantPosition);
183             portGeom.setY(alignedPosition);
184
185             port.setLabelPosition(Orientation.EAST);
186         }
187         diag.getModel().endUpdate();
188     }
189
190     /**
191      * Dispatch ports on Block's _SOUTH_ side.
192      *
193      * @param block
194      *            The block we have to work on.
195      * @param ports
196      *            The ports we have to move on the side.
197      */
198     public static void updateSouthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
199         double gridSize = diag.getGridSize();
200
201         final mxGeometry blockGeom = block.getGeometry();
202         assert blockGeom != null;
203         final int portsSize = ports.size();
204         final double blockLength = blockGeom.getWidth();
205         final double segLength = blockLength / (portsSize + 1);
206
207         diag.getModel().beginUpdate();
208         for (int i = 0; i < portsSize; ++i) {
209             final BasicPort port = (ports.get(i));
210             final mxGeometry portGeom = port.getGeometry();
211
212             double nonVariantPosition = blockGeom.getHeight();
213             final int order = i;
214             double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
215
216             portGeom.setX(alignedPosition);
217             portGeom.setY(nonVariantPosition);
218
219             port.setLabelPosition(Orientation.SOUTH);
220         }
221         diag.getModel().endUpdate();
222     }
223
224     /**
225      * Update all the port position of the block.
226      *
227      * @param block
228      *            The block we have to work on.
229      */
230     public static void updatePortsPosition(final XcosDiagram diag, BasicBlock block) {
231         final Map<Orientation, List<BasicPort>> ports = BasicBlockInfo.getAllOrientedPorts(block);
232
233         diag.getModel().beginUpdate();
234         for (Orientation iter : Orientation.values()) {
235             List<BasicPort> orientedPorts = ports.get(iter);
236             if (orientedPorts != null && !orientedPorts.isEmpty()) {
237                 updatePortsPositions(diag, block, orientedPorts, iter);
238             }
239         }
240         diag.getModel().endUpdate();
241     }
242
243     /**
244      * Update the port position for the specified orientation. This function manage the flip and mirror properties.
245      *
246      * @param block
247      *            The block we are working on
248      * @param ports
249      *            The ports at with the specified orientation
250      * @param iter
251      *            The orientation.
252      */
253     private static void updatePortsPositions(final XcosDiagram diag, BasicBlock block, List<BasicPort> ports, Orientation iter) {
254         @SuppressWarnings("serial")
255         final List<BasicPort> invertedPorts = new ArrayList<BasicPort>(ports) {
256             {
257                 Collections.reverse(this);
258             }
259         };
260
261         JavaController controller = new JavaController();
262         VectorOfDouble mvcAngle = new VectorOfDouble();
263         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.ANGLE, mvcAngle);
264
265         int flags = (int) mvcAngle.get(0);
266         final boolean mirrored = (flags & 0x0002) != 0;
267         final boolean flipped = (flags & 0x0001) != 0;
268         final int angle = (((int) Math.round(mvcAngle.get(1))) % 360 + 360) % 360;
269
270         List<BasicPort> working = ports;
271
272         /* List order modification with the flip flag */
273         if (flipped) {
274             if (iter == Orientation.NORTH || iter == Orientation.SOUTH) {
275                 working = invertedPorts;
276             }
277         }
278
279         /* List order modification with the mirror flag */
280         if (mirrored) {
281             if (iter == Orientation.EAST || iter == Orientation.WEST) {
282                 working = invertedPorts;
283             }
284         }
285
286         /*
287          * Ugly modification of the iter to update at the right position Works only for 0 - 90 - 180 - 270 angles.
288          */
289         Orientation rotated = rotateOrientation(iter, mirrored, flipped);
290
291         updatePortsPosition(diag, block, rotated, angle, working);
292     }
293
294     /**
295      * Ugly modification of the iter to update at the right position. Works only for 0 - 90 - 180 - 270 angles.
296      *
297      * @param iter
298      *            the real orientation
299      * @param mirrored
300      *            is the block mirrored
301      * @param flipped
302      *            is the block flipped
303      * @return The modified orientation
304      */
305     private static Orientation rotateOrientation(final Orientation iter, final boolean mirrored, final boolean flipped) {
306         final int nbOfOrientations = Orientation.values().length; // 4
307         Orientation rotated = iter;
308
309         /* Flip & Mirror management */
310         if (mirrored) {
311             if (rotated == Orientation.EAST || rotated == Orientation.WEST) {
312                 rotated = Orientation.values()[(rotated.ordinal() + 2) % nbOfOrientations];
313             }
314         }
315         if (flipped) {
316             if (rotated == Orientation.NORTH || rotated == Orientation.SOUTH) {
317                 rotated = Orientation.values()[(rotated.ordinal() + 2) % nbOfOrientations];
318             }
319         }
320         return rotated;
321     }
322
323     /**
324      * Update the ports positions according to the angle. This function doesn't handle order inversion.
325      *
326      * @param block
327      *            The block we are working on
328      * @param iter
329      *            The current port orientation
330      * @param angle
331      *            The angle we have to rotate
332      * @param working
333      *            The ordered ports we are working on.
334      */
335     private static void updatePortsPosition(final XcosDiagram diag, BasicBlock block, Orientation iter, final double angle, List<BasicPort> working) {
336         /*
337          * Ugly modification of the iter to update at the right position Works only for 0 - 90 - 180 - 270 angles.
338          */
339         final int nbOfOrientations = Orientation.values().length; // 4
340         Orientation rotated = iter;
341
342         /* Angle management */
343         double rotationIndex = angle / ROTATION_STEP;
344         rotated = Orientation.values()[(rotated.ordinal() + (int) rotationIndex) % nbOfOrientations];
345
346         /* Call the associated function */
347         switch (rotated) {
348             case NORTH:
349                 updateNorthPortsPosition(diag, block, working);
350                 break;
351             case SOUTH:
352                 updateSouthPortsPosition(diag, block, working);
353                 break;
354             case EAST:
355                 updateEastPortsPosition(diag, block, working);
356                 break;
357             case WEST:
358                 updateWestPortsPosition(diag, block, working);
359                 break;
360
361             default:
362                 break;
363         }
364     }
365
366     /**
367      * Rotate all the port of the block.
368      *
369      * @param block
370      *            The block to work on.
371      */
372     public static void rotateAllPorts(final XcosDiagram diag, BasicBlock block) {
373         JavaController controller = new JavaController();
374         VectorOfDouble mvcAngle = new VectorOfDouble();
375         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.ANGLE, mvcAngle);
376
377         final int mirrorAndFlip = (int) mvcAngle.get(0);
378         final boolean flipped = (mirrorAndFlip & 0x0001) != 0;
379         final boolean mirrored = (mirrorAndFlip & 0x0002) != 0;
380         final double angle = mvcAngle.get(1);
381
382         final int childrenCount = block.getChildCount();
383         for (int i = 0; i < childrenCount; ++i) {
384             if (block.getChildAt(i) instanceof BasicPort) {
385                 final BasicPort port = (BasicPort) block.getChildAt(i);
386                 final Orientation orientation = port.getOrientation();
387
388                 diag.getModel().beginUpdate();
389
390                 /* Apply angle */
391                 final mxIGraphModel model = diag.getModel();
392                 final String rot = Double.toString(orientation.getRelativeAngle(angle, port.getClass(), flipped, mirrored));
393                 mxStyleUtils.setCellStyles(model, new Object[] { port }, XcosConstants.STYLE_ROTATION, rot);
394
395                 diag.getModel().endUpdate();
396             }
397         }
398     }
399
400     /**
401      * Update the geometry of the block's ports.
402      *
403      * @param block
404      *            The block to work on
405      */
406     public static void updateBlockView(final XcosDiagram diag, BasicBlock block) {
407         if (block.getKind() != Kind.BLOCK) {
408             return;
409         }
410
411         diag.getModel().beginUpdate();
412         updatePortsPosition(diag, block);
413         rotateAllPorts(diag, block);
414         diag.getModel().endUpdate();
415
416         /*
417          * TODO: bug #6705; This placement trick doesn't work on the first block Dnd as the view is not revalidated.
418          *
419          * On block loading, parentDiagram is null thus placement is not performed.
420          */
421     }
422
423     /**
424      * Flip a block (horizontal inversion).
425      *
426      * @param block
427      *            The block to work on
428      */
429     public static void toggleFlip(final XcosDiagram diag, BasicBlock block) {
430         JavaController controller = new JavaController();
431         VectorOfDouble mvcAngle = new VectorOfDouble();
432         controller.getObjectProperty(block.getUID(), block.getKind(), ObjectProperties.ANGLE, mvcAngle);
433
434         // retrieve then mask the value
435         int mirrorAndFlip = (int) mvcAngle.get(0);
436         mirrorAndFlip ^= 0x0001;
437
438         mvcAngle.set(0, mirrorAndFlip);
439         controller.setObjectProperty(block.getUID(), block.getKind(), ObjectProperties.ANGLE, mvcAngle);
440
441         updateBlockView(diag, block);
442     }
443
444     /**
445      * Mirror a block (vertical inversion).
446      *
447      * @param block
448      *            The block to work on
449      */
450     public static void toggleMirror(final XcosDiagram diag, BasicBlock block) {
451         JavaController controller = new JavaController();
452         VectorOfDouble mvcAngle = new VectorOfDouble();
453         controller.getObjectProperty(block.getUID(), block.getKind(), ObjectProperties.ANGLE, mvcAngle);
454
455         // retrieve then mask the value
456         int mirrorAndFlip = (int) mvcAngle.get(0);
457         mirrorAndFlip ^= 0x0002;
458
459         mvcAngle.set(0, mirrorAndFlip);
460         controller.setObjectProperty(block.getUID(), block.getKind(), ObjectProperties.ANGLE, mvcAngle);
461
462         updateBlockView(diag, block);
463     }
464
465     /**
466      * Rotate a block with an anti-clockwise next value
467      *
468      * @param block
469      *            The block to work on
470      */
471     public static void toggleAntiClockwiseRotation(final XcosDiagram diag, BasicBlock block) {
472         JavaController controller = new JavaController();
473
474         VectorOfDouble mvcAngle = new VectorOfDouble();
475         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.ANGLE, mvcAngle);
476
477         mvcAngle.set(1, getNextAntiClockwiseAngle(block));
478         controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.ANGLE, mvcAngle);
479         updateBlockView(diag, block);
480     }
481
482     /**
483      * Get the next anti-clockwise rotation value
484      *
485      * @param block
486      *            The block to work on
487      * @return The angle value
488      */
489     public static double getNextAntiClockwiseAngle(BasicBlock block) {
490         JavaController controller = new JavaController();
491
492         VectorOfDouble mvcAngle = new VectorOfDouble();
493         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.ANGLE, mvcAngle);
494
495         double angle = (mvcAngle.get(1) - ROTATION_STEP + MAX_ROTATION) % MAX_ROTATION;
496         return angle;
497     }
498
499     /**
500      * Get the next clockwise rotation value
501      *
502      * @param block
503      *            The block to work on
504      * @return The angle value
505      */
506     public static double getNextClockwiseAngle(BasicBlock block) {
507         JavaController controller = new JavaController();
508
509         VectorOfDouble mvcAngle = new VectorOfDouble();
510         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.ANGLE, mvcAngle);
511
512         double angle = (mvcAngle.get(1) + ROTATION_STEP) % MAX_ROTATION;
513         return angle;
514     }
515
516     /**
517      * Convert any angle value to a valid block value
518      *
519      * @param angle
520      *            the non valid value
521      * @return the nearest graph valid value
522      */
523     public static double roundAngle(double angle) {
524         double ret = angle;
525         if (angle < 0 || angle > MAX_ROTATION) {
526             ret = (angle + MAX_ROTATION) % MAX_ROTATION;
527         }
528
529         for (int i = 0; i < (MAX_ROTATION / ROTATION_STEP); i++) {
530             double min = i * ROTATION_STEP;
531             double max = (i + 1) * ROTATION_STEP;
532
533             if (ret < (min + max) / 2) {
534                 ret = min;
535                 break;
536             }
537         }
538         return ret;
539     }
540 }