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