Xcos java: remove unnecessary imports
[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  *
15  */
16
17 package org.scilab.modules.xcos.utils;
18
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.Map;
23
24 import org.scilab.modules.xcos.JavaController;
25 import org.scilab.modules.xcos.Kind;
26 import org.scilab.modules.xcos.ObjectProperties;
27 import org.scilab.modules.xcos.block.BasicBlock;
28 import org.scilab.modules.xcos.graph.XcosDiagram;
29 import org.scilab.modules.xcos.io.scicos.BasicBlockInfo;
30 import org.scilab.modules.xcos.port.BasicPort;
31 import org.scilab.modules.xcos.port.Orientation;
32
33 import com.mxgraph.model.mxGeometry;
34 import com.mxgraph.model.mxIGraphModel;
35 import com.mxgraph.util.mxStyleUtils;
36 import org.scilab.modules.graph.utils.StyleMap;
37
38 /**
39  * Helpers to place port on a block.
40  */
41 public final class BlockPositioning {
42
43     /**
44      * The default grid size. This value is used when the grid size isn't accessible (on the palette).
45      */
46     public static final double DEFAULT_GRIDSIZE = Double.MIN_NORMAL;
47     /** The rotation step of the clockwise and anticlockwise rotation */
48     public static final double ROTATION_STEP = 90;
49     /** The max valid rotation value (always 360 degres) */
50     public static final double MAX_ROTATION = 360;
51
52     /** This class is a static singleton, thus it must not be instantiated */
53     private BlockPositioning() {
54     }
55
56     /**
57      * Dispatch ports on Block's _WEST_ side.
58      *
59      * @param block
60      *            The block we have to work on.
61      * @param ports
62      *            The ports we have to move on the side.
63      */
64     public static void updateWestPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
65
66         double gridSize = diag.getGridSize();
67
68         final mxGeometry blockGeom = block.getGeometry();
69         assert blockGeom != null;
70         final int portsSize = ports.size();
71         final double blockLength = blockGeom.getHeight();
72         final double segLength = blockLength / (portsSize + 1);
73
74         diag.getModel().beginUpdate();
75         for (int i = 0; i < portsSize; ++i) {
76             final BasicPort port = (ports.get(i));
77             final mxGeometry portGeom = port.getGeometry();
78
79             double nonVariantPosition = -portGeom.getWidth();
80             final int order = i;
81             double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
82
83             portGeom.setX(nonVariantPosition);
84             portGeom.setY(alignedPosition);
85
86             port.setLabelPosition(Orientation.WEST);
87         }
88         diag.getModel().endUpdate();
89     }
90
91     /**
92      * Calculate an aligned port position.
93      *
94      * @param gridSize
95      *            the grid size
96      * @param segLength
97      *            the side length
98      * @param i
99      *            the current working index
100      * @return the aligned position on the grid.
101      */
102     private static double calculateAlignedPosition(final double gridSize, final double segLength, int i) {
103         /*
104          * The base position is the origin of the port geometry. It is the upper-left corner position.
105          */
106         final double basePosition = (i + 1) * segLength;
107
108         /*
109          * The aligned base position is the base position aligned on the grid.
110          */
111         final double alignedBasePosition = basePosition - Math.IEEEremainder(basePosition, gridSize);
112
113         /*
114          * The aligned position is the base position translated from origin to the middle of the port.
115          */
116         final double alignedPosition = alignedBasePosition - (BasicPort.DEFAULT_PORTSIZE / 2.0);
117
118         return alignedPosition;
119     }
120
121     /**
122      * Dispatch ports on Block's _NORTH_ side.
123      *
124      * @param block
125      *            The block we have to work on.
126      * @param ports
127      *            The ports we have to move on the side.
128      */
129     public static void updateNorthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
130         double gridSize = diag.getGridSize();
131
132         final mxGeometry blockGeom = block.getGeometry();
133         assert blockGeom != null;
134         final int portsSize = ports.size();
135         final double blockLength = blockGeom.getWidth();
136         final double segLength = blockLength / (portsSize + 1);
137
138         diag.getModel().beginUpdate();
139         for (int i = 0; i < portsSize; ++i) {
140             final BasicPort port = (ports.get(i));
141             final mxGeometry portGeom = port.getGeometry();
142
143             double nonVariantPosition = -portGeom.getHeight();
144             final int order = i;
145             double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
146
147             portGeom.setX(alignedPosition);
148             portGeom.setY(nonVariantPosition);
149
150             port.setLabelPosition(Orientation.NORTH);
151         }
152         diag.getModel().endUpdate();
153     }
154
155     /**
156      * Dispatch ports on Block's _EAST_ side.
157      *
158      * @param block
159      *            The block we have to work on.
160      * @param ports
161      *            The ports we have to move on the side.
162      */
163     public static void updateEastPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
164         double gridSize = diag.getGridSize();
165
166         final mxGeometry blockGeom = block.getGeometry();
167         assert blockGeom != null;
168         final int portsSize = ports.size();
169         final double blockLength = blockGeom.getHeight();
170         final double segLength = blockLength / (portsSize + 1);
171
172         diag.getModel().beginUpdate();
173         for (int i = 0; i < portsSize; ++i) {
174             final BasicPort port = (ports.get(i));
175             final mxGeometry portGeom = port.getGeometry();
176
177             double nonVariantPosition = blockGeom.getWidth();
178             final int order = i;
179             double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
180
181             portGeom.setX(nonVariantPosition);
182             portGeom.setY(alignedPosition);
183
184             port.setLabelPosition(Orientation.EAST);
185         }
186         diag.getModel().endUpdate();
187     }
188
189     /**
190      * Dispatch ports on Block's _SOUTH_ side.
191      *
192      * @param block
193      *            The block we have to work on.
194      * @param ports
195      *            The ports we have to move on the side.
196      */
197     public static void updateSouthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
198         double gridSize = diag.getGridSize();
199
200         final mxGeometry blockGeom = block.getGeometry();
201         assert blockGeom != null;
202         final int portsSize = ports.size();
203         final double blockLength = blockGeom.getWidth();
204         final double segLength = blockLength / (portsSize + 1);
205
206         diag.getModel().beginUpdate();
207         for (int i = 0; i < portsSize; ++i) {
208             final BasicPort port = (ports.get(i));
209             final mxGeometry portGeom = port.getGeometry();
210
211             double nonVariantPosition = blockGeom.getHeight();
212             final int order = i;
213             double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
214
215             portGeom.setX(alignedPosition);
216             portGeom.setY(nonVariantPosition);
217
218             port.setLabelPosition(Orientation.SOUTH);
219         }
220         diag.getModel().endUpdate();
221     }
222
223     /**
224      * Update all the port position of the block.
225      *
226      * @param block
227      *            The block we have to work on.
228      */
229     public static void updatePortsPosition(final XcosDiagram diag, BasicBlock block) {
230         final Map<Orientation, List<BasicPort>> ports = BasicBlockInfo.getAllOrientedPorts(block);
231
232         diag.getModel().beginUpdate();
233         for (Orientation iter : Orientation.values()) {
234             List<BasicPort> orientedPorts = ports.get(iter);
235             if (orientedPorts != null && !orientedPorts.isEmpty()) {
236                 updatePortsPositions(diag, block, orientedPorts, iter);
237             }
238         }
239         diag.getModel().endUpdate();
240     }
241
242     /**
243      * Update the port position for the specified orientation. This function manage the flip and mirror properties.
244      *
245      * @param block
246      *            The block we are working on
247      * @param ports
248      *            The ports at with the specified orientation
249      * @param iter
250      *            The orientation.
251      */
252     private static void updatePortsPositions(final XcosDiagram diag, BasicBlock block, List<BasicPort> ports, Orientation iter) {
253         @SuppressWarnings("serial")
254         final List<BasicPort> invertedPorts = new ArrayList<BasicPort>(ports) {
255             {
256                 Collections.reverse(this);
257             }
258         };
259
260         JavaController controller = new JavaController();
261         String[] style = new String[1];
262         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
263         StyleMap styleMap = new StyleMap(style[0]);
264
265         final boolean mirrored = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
266         final boolean flipped = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
267         final double doubleRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0"));
268         final int angle = (((int) Math.round(doubleRotation)) % 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         String[] style = new String[1];
375         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
376         StyleMap styleMap = new StyleMap(style[0]);
377
378         final boolean mirrored = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
379         final boolean flipped = Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
380         final double doubleRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0"));
381         final int angle = (((int) Math.round(doubleRotation)) % 360 + 360) % 360;
382
383         final int childrenCount = block.getChildCount();
384         for (int i = 0; i < childrenCount; ++i) {
385             if (block.getChildAt(i) instanceof BasicPort) {
386                 final BasicPort port = (BasicPort) block.getChildAt(i);
387                 final Orientation orientation = port.getOrientation();
388
389                 diag.getModel().beginUpdate();
390
391                 /* Apply angle */
392                 final mxIGraphModel model = diag.getModel();
393                 final String rot = Double.toString(orientation.getRelativeAngle(angle, port.getClass(), flipped, mirrored));
394                 mxStyleUtils.setCellStyles(model, new Object[] { port }, XcosConstants.STYLE_ROTATION, rot);
395
396                 diag.getModel().endUpdate();
397             }
398         }
399     }
400
401     /**
402      * Update the geometry of the block's ports.
403      *
404      * @param block
405      *            The block to work on
406      */
407     public static void updateBlockView(final XcosDiagram diag, BasicBlock block) {
408         if (block.getKind() != Kind.BLOCK) {
409             return;
410         }
411
412         diag.getModel().beginUpdate();
413         updatePortsPosition(diag, block);
414         rotateAllPorts(diag, block);
415         diag.getModel().endUpdate();
416
417         /*
418          * TODO: bug #6705; This placement trick doesn't work on the first block Dnd as the view is not revalidated.
419          *
420          * On block loading, parentDiagram is null thus placement is not performed.
421          */
422     }
423
424     /**
425      * Flip a block (horizontal inversion).
426      *
427      * @param block
428      *            The block to work on
429      */
430     public static void toggleFlip(final XcosDiagram diag, BasicBlock block) {
431         JavaController controller = new JavaController();
432         String[] style = new String[1];
433         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
434
435         StyleMap styleMap = new StyleMap(style[0]);
436         final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
437
438         styleMap.put(XcosConstants.STYLE_FLIP, Boolean.toString(invertedFlip));
439
440         controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
441
442         updateBlockView(diag, block);
443     }
444
445     /**
446      * Mirror a block (vertical inversion).
447      *
448      * @param block
449      *            The block to work on
450      */
451     public static void toggleMirror(final XcosDiagram diag, BasicBlock block) {
452         JavaController controller = new JavaController();
453         String[] style = new String[1];
454         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
455
456         StyleMap styleMap = new StyleMap(style[0]);
457         final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
458
459         styleMap.put(XcosConstants.STYLE_MIRROR, Boolean.toString(invertedFlip));
460
461         controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
462
463         updateBlockView(diag, block);
464     }
465
466     /**
467      * Rotate a block with an anti-clockwise next value
468      *
469      * @param block
470      *            The block to work on
471      */
472     public static void toggleAntiClockwiseRotation(final XcosDiagram diag, BasicBlock block) {
473         JavaController controller = new JavaController();
474         String[] style = new String[1];
475         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
476
477         StyleMap styleMap = new StyleMap(style[0]);
478         styleMap.put(XcosConstants.STYLE_ROTATION, Double.toString(getNextAntiClockwiseAngle(styleMap)));
479
480         controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
481         updateBlockView(diag, block);
482     }
483
484     /**
485      * Get the next anti-clockwise rotation value
486      *
487      * @param styleMap
488      *            the data to parse
489      * @return The angle value
490      */
491     public static double getNextAntiClockwiseAngle(StyleMap styleMap) {
492         final double doubleRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0"));
493
494         double angle = (doubleRotation - ROTATION_STEP + MAX_ROTATION) % MAX_ROTATION;
495         return angle;
496     }
497
498     /**
499      * Get the next clockwise rotation value
500      *
501      * @param styleMap
502      *            the data to parse
503      * @return The angle value
504      */
505     public static double getNextClockwiseAngle(StyleMap styleMap) {
506         final double doubleRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0"));
507         double angle = (doubleRotation + ROTATION_STEP) % MAX_ROTATION;
508         return angle;
509     }
510
511     /**
512      * Convert any angle value to a valid block value
513      *
514      * @param angle
515      *            the non valid value
516      * @return the nearest graph valid value
517      */
518     public static double roundAngle(double angle) {
519         double ret = angle;
520         if (angle < 0 || angle > MAX_ROTATION) {
521             ret = (angle + MAX_ROTATION) % MAX_ROTATION;
522         }
523
524         for (int i = 0; i < (MAX_ROTATION / ROTATION_STEP); i++) {
525             double min = i * ROTATION_STEP;
526             double max = (i + 1) * ROTATION_STEP;
527
528             if (ret < (min + max) / 2) {
529                 ret = min;
530                 break;
531             }
532         }
533         return ret;
534     }
535 }