Xcos: be compatible with Scilab 5.5.2 zcos files
[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 int ROTATION_STEP = 90;
49     /** The max valid rotation value (always 360 degres) */
50     public static final int 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 int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
268         final int angle = ((Math.round(intRotation)) % 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 int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
381         final int angle = ((Math.round(intRotation)) % 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 = Integer.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         block.updateBlockView();
414         updatePortsPosition(diag, block);
415         rotateAllPorts(diag, block);
416         diag.getModel().endUpdate();
417
418         /*
419          * TODO: bug #6705; This placement trick doesn't work on the first block Dnd as the view is not revalidated.
420          *
421          * On block loading, parentDiagram is null thus placement is not performed.
422          */
423     }
424
425     /**
426      * Flip a block (horizontal inversion).
427      *
428      * @param block
429      *            The block to work on
430      */
431     public static void toggleFlip(final XcosDiagram diag, BasicBlock block) {
432         JavaController controller = new JavaController();
433         String[] style = new String[1];
434         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
435
436         StyleMap styleMap = new StyleMap(style[0]);
437         final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
438
439         styleMap.put(XcosConstants.STYLE_FLIP, Boolean.toString(invertedFlip));
440
441         controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
442
443         updateBlockView(diag, block);
444     }
445
446     /**
447      * Mirror a block (vertical inversion).
448      *
449      * @param block
450      *            The block to work on
451      */
452     public static void toggleMirror(final XcosDiagram diag, BasicBlock block) {
453         JavaController controller = new JavaController();
454         String[] style = new String[1];
455         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
456
457         StyleMap styleMap = new StyleMap(style[0]);
458         final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
459
460         styleMap.put(XcosConstants.STYLE_MIRROR, Boolean.toString(invertedFlip));
461
462         controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
463
464         updateBlockView(diag, block);
465     }
466
467     /**
468      * Rotate a block with an anti-clockwise next value
469      *
470      * @param block
471      *            The block to work on
472      */
473     public static void toggleAntiClockwiseRotation(final XcosDiagram diag, BasicBlock block) {
474         JavaController controller = new JavaController();
475         String[] style = new String[1];
476         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, style);
477
478         StyleMap styleMap = new StyleMap(style[0]);
479         styleMap.put(XcosConstants.STYLE_ROTATION, Integer.toString(getNextAntiClockwiseAngle(styleMap)));
480
481         controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
482         updateBlockView(diag, block);
483     }
484
485     /**
486      * Get the next anti-clockwise rotation value
487      *
488      * @param styleMap
489      *            the data to parse
490      * @return The angle value
491      */
492     public static int getNextAntiClockwiseAngle(StyleMap styleMap) {
493         final int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
494
495         int angle = (intRotation - ROTATION_STEP + MAX_ROTATION) % MAX_ROTATION;
496         return angle;
497     }
498
499     /**
500      * Get the next clockwise rotation value
501      *
502      * @param styleMap
503      *            the data to parse
504      * @return The angle value
505      */
506     public static int getNextClockwiseAngle(StyleMap styleMap) {
507         final int intRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0")).intValue();
508         int angle = (intRotation + ROTATION_STEP) % MAX_ROTATION;
509         return angle;
510     }
511
512     /**
513      * Convert any angle value to a valid block value
514      *
515      * @param angle
516      *            the non valid value
517      * @return the nearest graph valid value
518      */
519     public static int roundAngle(int angle) {
520         int ret = angle;
521         if (angle < 0 || angle > MAX_ROTATION) {
522             ret = (angle + MAX_ROTATION) % MAX_ROTATION;
523         }
524
525         for (int i = 0; i < (MAX_ROTATION / ROTATION_STEP); i++) {
526             int min = i * ROTATION_STEP;
527             int max = (i + 1) * ROTATION_STEP;
528
529             if (ret < (min + max) / 2) {
530                 ret = min;
531                 break;
532             }
533         }
534         return ret;
535     }
536 }