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.mxStyleUtils;
36 import org.scilab.modules.graph.utils.StyleMap;
39 * Helpers to place port on a block.
41 public final class BlockPositioning {
44 * The default grid size. This value is used when the grid size isn't accessible (on the palette).
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;
52 /** This class is a static singleton, thus it must not be instantiated */
53 private BlockPositioning() {
57 * Dispatch ports on Block's _WEST_ side.
60 * The block we have to work on.
62 * The ports we have to move on the side.
64 public static void updateWestPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
66 double gridSize = diag.getGridSize();
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);
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();
79 double nonVariantPosition = -portGeom.getWidth();
81 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
83 portGeom.setX(nonVariantPosition);
84 portGeom.setY(alignedPosition);
86 port.setLabelPosition(Orientation.WEST);
88 diag.getModel().endUpdate();
92 * Calculate an aligned port position.
99 * the current working index
100 * @return the aligned position on the grid.
102 private static double calculateAlignedPosition(final double gridSize, final double segLength, int i) {
104 * The base position is the origin of the port geometry. It is the upper-left corner position.
106 final double basePosition = (i + 1) * segLength;
109 * The aligned base position is the base position aligned on the grid.
111 final double alignedBasePosition = basePosition - Math.IEEEremainder(basePosition, gridSize);
114 * The aligned position is the base position translated from origin to the middle of the port.
116 final double alignedPosition = alignedBasePosition - (BasicPort.DEFAULT_PORTSIZE / 2.0);
118 return alignedPosition;
122 * Dispatch ports on Block's _NORTH_ side.
125 * The block we have to work on.
127 * The ports we have to move on the side.
129 public static void updateNorthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
130 double gridSize = diag.getGridSize();
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);
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();
143 double nonVariantPosition = -portGeom.getHeight();
145 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
147 portGeom.setX(alignedPosition);
148 portGeom.setY(nonVariantPosition);
150 port.setLabelPosition(Orientation.NORTH);
152 diag.getModel().endUpdate();
156 * Dispatch ports on Block's _EAST_ side.
159 * The block we have to work on.
161 * The ports we have to move on the side.
163 public static void updateEastPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
164 double gridSize = diag.getGridSize();
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);
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();
177 double nonVariantPosition = blockGeom.getWidth();
179 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
181 portGeom.setX(nonVariantPosition);
182 portGeom.setY(alignedPosition);
184 port.setLabelPosition(Orientation.EAST);
186 diag.getModel().endUpdate();
190 * Dispatch ports on Block's _SOUTH_ side.
193 * The block we have to work on.
195 * The ports we have to move on the side.
197 public static void updateSouthPortsPosition(final XcosDiagram diag, BasicBlock block, List<? extends BasicPort> ports) {
198 double gridSize = diag.getGridSize();
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);
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();
211 double nonVariantPosition = blockGeom.getHeight();
213 double alignedPosition = calculateAlignedPosition(gridSize, segLength, order);
215 portGeom.setX(alignedPosition);
216 portGeom.setY(nonVariantPosition);
218 port.setLabelPosition(Orientation.SOUTH);
220 diag.getModel().endUpdate();
224 * Update all the port position of the block.
227 * The block we have to work on.
229 public static void updatePortsPosition(final XcosDiagram diag, BasicBlock block) {
230 final Map<Orientation, List<BasicPort>> ports = BasicBlockInfo.getAllOrientedPorts(block);
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);
239 diag.getModel().endUpdate();
243 * Update the port position for the specified orientation. This function manage the flip and mirror properties.
246 * The block we are working on
248 * The ports at with the specified orientation
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) {
256 Collections.reverse(this);
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]);
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;
270 List<BasicPort> working = ports;
272 /* List order modification with the flip flag */
274 if (iter == Orientation.NORTH || iter == Orientation.SOUTH) {
275 working = invertedPorts;
279 /* List order modification with the mirror flag */
281 if (iter == Orientation.EAST || iter == Orientation.WEST) {
282 working = invertedPorts;
287 * Ugly modification of the iter to update at the right position Works only for 0 - 90 - 180 - 270 angles.
289 Orientation rotated = rotateOrientation(iter, mirrored, flipped);
291 updatePortsPosition(diag, block, rotated, angle, working);
295 * Ugly modification of the iter to update at the right position. Works only for 0 - 90 - 180 - 270 angles.
298 * the real orientation
300 * is the block mirrored
302 * is the block flipped
303 * @return The modified orientation
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;
309 /* Flip & Mirror management */
311 if (rotated == Orientation.EAST || rotated == Orientation.WEST) {
312 rotated = Orientation.values()[(rotated.ordinal() + 2) % nbOfOrientations];
316 if (rotated == Orientation.NORTH || rotated == Orientation.SOUTH) {
317 rotated = Orientation.values()[(rotated.ordinal() + 2) % nbOfOrientations];
324 * Update the ports positions according to the angle. This function doesn't handle order inversion.
327 * The block we are working on
329 * The current port orientation
331 * The angle we have to rotate
333 * The ordered ports we are working on.
335 private static void updatePortsPosition(final XcosDiagram diag, BasicBlock block, Orientation iter, final double angle, List<BasicPort> working) {
337 * Ugly modification of the iter to update at the right position Works only for 0 - 90 - 180 - 270 angles.
339 final int nbOfOrientations = Orientation.values().length; // 4
340 Orientation rotated = iter;
342 /* Angle management */
343 double rotationIndex = angle / ROTATION_STEP;
344 rotated = Orientation.values()[(rotated.ordinal() + (int) rotationIndex) % nbOfOrientations];
346 /* Call the associated function */
349 updateNorthPortsPosition(diag, block, working);
352 updateSouthPortsPosition(diag, block, working);
355 updateEastPortsPosition(diag, block, working);
358 updateWestPortsPosition(diag, block, working);
367 * Rotate all the port of the block.
370 * The block to work on.
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]);
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;
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();
389 diag.getModel().beginUpdate();
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);
396 diag.getModel().endUpdate();
402 * Update the geometry of the block's ports.
405 * The block to work on
407 public static void updateBlockView(final XcosDiagram diag, BasicBlock block) {
408 if (block.getKind() != Kind.BLOCK) {
412 diag.getModel().beginUpdate();
413 updatePortsPosition(diag, block);
414 rotateAllPorts(diag, block);
415 diag.getModel().endUpdate();
418 * TODO: bug #6705; This placement trick doesn't work on the first block Dnd as the view is not revalidated.
420 * On block loading, parentDiagram is null thus placement is not performed.
425 * Flip a block (horizontal inversion).
428 * The block to work on
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);
435 StyleMap styleMap = new StyleMap(style[0]);
436 final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_FLIP));
438 styleMap.put(XcosConstants.STYLE_FLIP, Boolean.toString(invertedFlip));
440 controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
442 updateBlockView(diag, block);
446 * Mirror a block (vertical inversion).
449 * The block to work on
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);
456 StyleMap styleMap = new StyleMap(style[0]);
457 final boolean invertedFlip = ! Boolean.TRUE.toString().equals(styleMap.get(XcosConstants.STYLE_MIRROR));
459 styleMap.put(XcosConstants.STYLE_MIRROR, Boolean.toString(invertedFlip));
461 controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
463 updateBlockView(diag, block);
467 * Rotate a block with an anti-clockwise next value
470 * The block to work on
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);
477 StyleMap styleMap = new StyleMap(style[0]);
478 styleMap.put(XcosConstants.STYLE_ROTATION, Double.toString(getNextAntiClockwiseAngle(styleMap)));
480 controller.setObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.STYLE, styleMap.toString());
481 updateBlockView(diag, block);
485 * Get the next anti-clockwise rotation value
489 * @return The angle value
491 public static double getNextAntiClockwiseAngle(StyleMap styleMap) {
492 final double doubleRotation = Double.valueOf(styleMap.getOrDefault(XcosConstants.STYLE_ROTATION, "0"));
494 double angle = (doubleRotation - ROTATION_STEP + MAX_ROTATION) % MAX_ROTATION;
499 * Get the next clockwise rotation value
503 * @return The angle value
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;
512 * Convert any angle value to a valid block value
515 * the non valid value
516 * @return the nearest graph valid value
518 public static double roundAngle(double angle) {
520 if (angle < 0 || angle > MAX_ROTATION) {
521 ret = (angle + MAX_ROTATION) % MAX_ROTATION;
524 for (int i = 0; i < (MAX_ROTATION / ROTATION_STEP); i++) {
525 double min = i * ROTATION_STEP;
526 double max = (i + 1) * ROTATION_STEP;
528 if (ret < (min + max) / 2) {