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