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
6 * Copyright (C) 2012 - 2016 - Scilab Enterprises
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.
17 package org.scilab.modules.xcos.utils;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
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;
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;
41 * Helpers to place port on a block.
43 public final class BlockPositioning {
46 * The default grid size. This value is used when the grid size isn't accessible (on the palette).
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;
54 /** This class is a static singleton, thus it must not be instantiated */
55 private BlockPositioning() {
59 * Dispatch ports on Block's _WEST_ side.
62 * The block we have to work on.
64 * The ports we have to move on the side.
66 public static void updateWestPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
68 double gridSize = diag.getGridSize();
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);
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();
81 double nonVariantPosition = -portGeom.getWidth();
83 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
85 portGeom.setX(nonVariantPosition);
86 portGeom.setY(alignedPosition);
88 port.setLabelPosition(Orientation.WEST);
90 diag.getModel().endUpdate();
94 * Calculate an aligned port position.
101 * the current working index
102 * @return the aligned position on the grid.
104 private static double calculateAlignedPosition(final double gridSize, final double segLength, int i) {
106 * The base position is the origin of the port geometry. It is the upper-left corner position.
108 final double basePosition = (i + 1) * segLength;
111 * The aligned base position is the base position aligned on the grid.
113 final double alignedBasePosition = basePosition - Math.IEEEremainder(basePosition, gridSize);
116 * The aligned position is the base position translated from origin to the middle of the port.
118 final double alignedPosition = alignedBasePosition - (BasicPort.DEFAULT_PORTSIZE / 2.0);
120 return alignedPosition;
124 * Dispatch ports on Block's _NORTH_ side.
127 * The block we have to work on.
129 * The ports we have to move on the side.
131 public static void updateNorthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
132 double gridSize = diag.getGridSize();
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);
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();
145 double nonVariantPosition = -portGeom.getHeight();
147 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
149 portGeom.setX(alignedPosition);
150 portGeom.setY(nonVariantPosition);
152 port.setLabelPosition(Orientation.NORTH);
154 diag.getModel().endUpdate();
158 * Dispatch ports on Block's _EAST_ side.
161 * The block we have to work on.
163 * The ports we have to move on the side.
165 public static void updateEastPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
166 double gridSize = diag.getGridSize();
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);
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();
179 double nonVariantPosition = blockGeom.getWidth();
181 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
183 portGeom.setX(nonVariantPosition);
184 portGeom.setY(alignedPosition);
186 port.setLabelPosition(Orientation.EAST);
188 diag.getModel().endUpdate();
192 * Dispatch ports on Block's _SOUTH_ side.
195 * The block we have to work on.
197 * The ports we have to move on the side.
199 public static void updateSouthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
200 double gridSize = diag.getGridSize();
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);
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();
213 double nonVariantPosition = blockGeom.getHeight();
215 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
217 portGeom.setX(alignedPosition);
218 portGeom.setY(nonVariantPosition);
220 port.setLabelPosition(Orientation.SOUTH);
222 diag.getModel().endUpdate();
226 * Update all the port position of the block.
229 * The block we have to work on.
231 public static void updatePortsPosition(final XcosDiagram diag, BasicBlock block) {
232 final Map<Orientation, List<BasicPort>> ports = BasicBlockInfo.getAllOrientedPorts(block);
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);
241 diag.getModel().endUpdate();
245 * Update the port position for the specified orientation. This function manage the flip and mirror properties.
248 * The block we are working on
250 * The ports at with the specified orientation
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) {
258 Collections.reverse(this);
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]);
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;
272 List<BasicPort> working = ports;
274 /* List order modification with the flip flag */
276 if (iter == Orientation.NORTH || iter == Orientation.SOUTH) {
277 working = invertedPorts;
281 /* List order modification with the mirror flag */
283 if (iter == Orientation.EAST || iter == Orientation.WEST) {
284 working = invertedPorts;
289 * Ugly modification of the iter to update at the right position Works only for 0 - 90 - 180 - 270 angles.
291 Orientation rotated = rotateOrientation(iter, mirrored, flipped);
293 updatePortsPosition(diag, block, rotated, angle, working);
297 * Ugly modification of the iter to update at the right position. Works only for 0 - 90 - 180 - 270 angles.
300 * the real orientation
302 * is the block mirrored
304 * is the block flipped
305 * @return The modified orientation
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;
311 /* Flip & Mirror management */
313 if (rotated == Orientation.EAST || rotated == Orientation.WEST) {
314 rotated = Orientation.values()[(rotated.ordinal() + 2) % nbOfOrientations];
318 if (rotated == Orientation.NORTH || rotated == Orientation.SOUTH) {
319 rotated = Orientation.values()[(rotated.ordinal() + 2) % nbOfOrientations];
326 * Update the ports positions according to the angle. This function doesn't handle order inversion.
329 * The block we are working on
331 * The current port orientation
333 * The angle we have to rotate
335 * The ordered ports we are working on.
337 private static void updatePortsPosition(final XcosDiagram diag, BasicBlock block, Orientation iter, final double angle, List<BasicPort> working) {
339 * Ugly modification of the iter to update at the right position Works only for 0 - 90 - 180 - 270 angles.
341 final int nbOfOrientations = Orientation.values().length; // 4
342 Orientation rotated = iter;
344 /* Angle management */
345 double rotationIndex = angle / ROTATION_STEP;
346 rotated = Orientation.values()[(rotated.ordinal() + (int) rotationIndex) % nbOfOrientations];
348 /* Call the associated function */
351 updateNorthPortsPosition(diag, block, working);
354 updateSouthPortsPosition(diag, block, working);
357 updateEastPortsPosition(diag, block, working);
360 updateWestPortsPosition(diag, block, working);
369 * Rotate all the port of the block.
372 * The block to work on.
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]);
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;
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();
391 diag.getModel().beginUpdate();
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);
398 diag.getModel().endUpdate();
404 * Update the geometry of the block's ports.
407 * The block to work on
409 public static void updateBlockView(final XcosDiagram diag, BasicBlock block) {
410 if (block.getKind() != Kind.BLOCK) {
414 diag.getModel().beginUpdate();
415 updatePortsPosition(diag, block);
416 rotateAllPorts(diag, block);
417 diag.getModel().endUpdate();
420 * TODO: bug #6705; This placement trick doesn't work on the first block Dnd as the view is not revalidated.
422 * On block loading, parentDiagram is null thus placement is not performed.
427 * Flip a block (horizontal inversion).
430 * The block to work on
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);
437 StyleMap styleMap = new StyleMap(style[0]);
438 final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
440 styleMap.put(XcosConstants.STYLE_FLIP, Boolean.toString(invertedFlip));
442 controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
444 updateBlockView(diag, block);
448 * Mirror a block (vertical inversion).
451 * The block to work on
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);
458 StyleMap styleMap = new StyleMap(style[0]);
459 final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
461 styleMap.put(XcosConstants.STYLE_MIRROR, Boolean.toString(invertedFlip));
463 controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
465 updateBlockView(diag, block);
469 * Rotate a block with an anti-clockwise next value
472 * The block to work on
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);
479 StyleMap styleMap = new StyleMap(style[0]);
480 styleMap.put(XcosConstants.STYLE_ROTATION, Double.toString(getNextAntiClockwiseAngle(styleMap)));
482 controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
483 updateBlockView(diag, block);
487 * Get the next anti-clockwise rotation value
491 * @return The angle value
493 public static double getNextAntiClockwiseAngle(StyleMap styleMap) {
494 final double doubleRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0"));
496 double angle = (doubleRotation - ROTATION_STEP + MAX_ROTATION) % MAX_ROTATION;
501 * Get the next clockwise rotation value
505 * @return The angle value
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;
514 * Convert any angle value to a valid block value
517 * the non valid value
518 * @return the nearest graph valid value
520 public static double roundAngle(double angle) {
522 if (angle < 0 || angle > MAX_ROTATION) {
523 ret = (angle + MAX_ROTATION) % MAX_ROTATION;
526 for (int i = 0; i < (MAX_ROTATION / ROTATION_STEP); i++) {
527 double min = i * ROTATION_STEP;
528 double max = (i + 1) * ROTATION_STEP;
530 if (ret < (min + max) / 2) {