2 * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3 * Copyright (C) 2015-2015 - Scilab Enterprises - Clement DAVID
5 * Copyright (C) 2012 - 2016 - Scilab Enterprises
7 * This file is hereby licensed under the terms of the GNU GPL v2.0,
8 * pursuant to article 5.3.4 of the CeCILL v.2.1.
9 * This file was originally licensed under the terms of the CeCILL v2.1,
10 * and continues to be available under such terms.
11 * For more information, see the COPYING file which you should have received
12 * along with this program.
16 package org.scilab.modules.xcos.graph.model;
18 import static org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.buildCall;
19 import static org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.synchronousScilabExec;
21 import java.lang.reflect.InvocationTargetException;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.EnumSet;
25 import java.util.HashMap;
26 import java.util.Optional;
27 import java.util.logging.Logger;
29 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.InterpreterException;
30 import org.scilab.modules.graph.utils.ScilabExported;
31 import org.scilab.modules.xcos.JavaController;
32 import org.scilab.modules.xcos.Kind;
33 import org.scilab.modules.xcos.ObjectProperties;
34 import org.scilab.modules.xcos.VectorOfDouble;
35 import org.scilab.modules.xcos.VectorOfScicosID;
36 import org.scilab.modules.xcos.Xcos;
37 import org.scilab.modules.xcos.XcosView;
38 import org.scilab.modules.xcos.block.BasicBlock;
39 import org.scilab.modules.xcos.graph.XcosDiagram;
40 import org.scilab.modules.xcos.link.BasicLink;
41 import org.scilab.modules.xcos.link.CommandControlLink;
42 import org.scilab.modules.xcos.link.ExplicitLink;
43 import org.scilab.modules.xcos.link.ImplicitLink;
44 import org.scilab.modules.xcos.port.BasicPort;
45 import org.scilab.modules.xcos.port.command.CommandPort;
46 import org.scilab.modules.xcos.port.control.ControlPort;
47 import org.scilab.modules.xcos.port.input.ExplicitInputPort;
48 import org.scilab.modules.xcos.port.input.ImplicitInputPort;
49 import org.scilab.modules.xcos.port.output.ExplicitOutputPort;
50 import org.scilab.modules.xcos.port.output.ImplicitOutputPort;
51 import org.scilab.modules.xcos.utils.BlockPositioning;
53 import com.mxgraph.model.mxCell;
54 import com.mxgraph.model.mxGeometry;
55 import com.mxgraph.util.mxPoint;
56 import java.lang.reflect.Constructor;
57 import java.util.EnumMap;
58 import org.scilab.modules.xcos.block.SplitBlock;
59 import org.scilab.modules.xcos.block.positionning.RoundBlock;
63 * Ease the creation of any {@link Kind} of graphical object
65 public final class XcosCellFactory {
67 /** Size compatibility for user defined blocks */
68 private static final double DEFAULT_SIZE_FACTOR = 20.0;
69 private static final Logger LOG = Logger.getLogger(XcosCellFactory.class.getName());
71 /** Default singleton constructor */
72 private XcosCellFactory() {
73 // This class is a static singleton
77 * This is a notify method mapped as a Scilab gateway used to alert with the loaded UID
82 * the kind of the created object (as an int)
84 @ScilabExported(module = "xcos", filename = "XcosCellFactory.giws.xml")
85 public static synchronized void created(long uid, int kind) {
86 lastCreated = new ScicosObjectOwner(uid, Kind.values()[kind]);
90 private static ScicosObjectOwner lastCreated = null;
93 * Retrieve and clear the last created object (<pre>xcosCellCreated</pre> call)
94 * @return the last created object
96 public static synchronized ScicosObjectOwner getLastCreated() {
97 ScicosObjectOwner last = lastCreated;
107 * Allocate a Java XcosDiagram from a COSF file.
109 * This method execute the file and register a
114 * the file to execute
115 * @return an allocated XcosDiagram
117 public static XcosDiagram createDiagramFromCOSF(final JavaController controller, String filename) {
118 XcosView view = (XcosView) JavaController.lookup_view(Xcos.class.getName());
119 JavaController.unregister_view(view);
123 synchronousScilabExec(
124 "function f(), " + buildCall("exec", filename, -1) + "; " + buildCall("xcosCellCreated", "scs_m".toCharArray()) + "endfunction; f();");
126 ScicosObjectOwner last = getLastCreated();
127 if (last.getKind() == Kind.DIAGRAM) {
128 String[] strUID = new String[1];
129 controller.getObjectProperty(last.getUID(), last.getKind(), ObjectProperties.UID, strUID);
131 diagram = new XcosDiagram(controller, last.getUID(), last.getKind(), strUID[0]);
132 insertChildren(controller, diagram);
136 } catch (InterpreterException e) {
139 JavaController.register_view(Xcos.class.getName(), view);
146 * Insert the diagram MVC children into the JGraphX model
149 * the shared controller
151 * the current diagram instance
153 public static void insertChildren(JavaController controller, XcosDiagram diagram) {
155 * Retrieve the children
157 VectorOfScicosID children = new VectorOfScicosID();
158 controller.getObjectProperty(diagram.getUID(), diagram.getKind(), ObjectProperties.CHILDREN, children);
159 final int childrenLen = children.size();
162 * Allocation some pre-sized stash data
164 final ArrayList<BasicLink> links = new ArrayList<>(childrenLen / 2);
165 final HashMap<Long, BasicPort> ports = new HashMap<>(childrenLen);
168 * Create the XcosCell objects and store some of them for later use
170 XcosCell[] cells = new XcosCell[childrenLen];
171 for (int i = 0; i < childrenLen; i++) {
172 final long uid = children.get(i);
173 final Kind kind = controller.getKind(uid);
178 BasicBlock b = createBlock(controller, uid, kind);
180 BlockPositioning.updatePortsPosition(diagram, b);
181 b.getTypedChildrenIndexes(BasicPort.class).stream()
182 .map(index -> b.getChildAt(index))
183 .filter(c -> c instanceof BasicPort)
184 .map(c -> (BasicPort) c)
185 .forEach(c -> ports.put(c.getUID(), c));
188 BasicLink l = createLink(controller, uid, kind);
198 * Relink the links on the XcosCell part
200 for (BasicLink l : links) {
201 long[] src = new long[1];
202 controller.getObjectProperty(l.getUID(), l.getKind(), ObjectProperties.SOURCE_PORT, src);
204 long[] dest = new long[1];
205 controller.getObjectProperty(l.getUID(), l.getKind(), ObjectProperties.DESTINATION_PORT, dest);
207 BasicPort srcPort = ports.get(src[0]);
208 if (srcPort != null) {
209 l.setSource(srcPort);
211 LOG.severe("Unable to connect link " + l.getId() + " : invalid source " + src[0]);
214 BasicPort destPort = ports.get(dest[0]);
215 if (destPort != null) {
216 l.setTarget(destPort);
218 LOG.severe("Unable to connect link " + l.getId() + " : invalid target " + dest[0]);
222 // re-add the children cells without duplicating them
224 controller.setObjectProperty(diagram.getUID(), diagram.getKind(), ObjectProperties.CHILDREN, children);
226 // add all the children using the diagram modification tracking features
227 diagram.addCells(cells);
229 // each cell has been referenced twice (CHILDREN insert and addCells), derefence them all by one
230 Arrays.stream(cells).forEach(c -> controller.deleteObject(c.getUID()));
234 * Block and Annotation management
238 * Instantiate a new block with the specified interface function.
241 * the interface function
242 * @return A new instance of a block.
244 public static BasicBlock createBlock(BlockInterFunction func) throws InterpreterException {
245 return createBlock(func, func.name());
249 * Instantiate a new block with the specified UID value and interface function
252 * The associated UID value
253 * @param interfaceFunction
254 * the interface function
255 * @return A new instance of a block.
257 public static BasicBlock createBlock(String interfaceFunction) throws InterpreterException {
258 Optional<BlockInterFunction> func = EnumSet.allOf(BlockInterFunction.class).stream().filter(f -> f.name().equals(interfaceFunction)).findFirst();
260 final BasicBlock block;
261 if (func.isPresent()) {
262 block = createBlock(func.get());
264 block = createBlock(BlockInterFunction.BASIC_BLOCK, interfaceFunction);
266 block.setStyle(interfaceFunction);
271 private static BasicBlock createBlock(BlockInterFunction func, String interfaceFunction) throws InterpreterException {
272 return createBlock(new JavaController(), func, interfaceFunction);
275 private static BasicBlock createBlock(final JavaController controller, BlockInterFunction func, String interfaceFunction) throws InterpreterException {
277 ScicosObjectOwner last;
279 if (BlockInterFunction.BASIC_BLOCK.name().equals(interfaceFunction)) {
280 // deliver all the MVC speed for the casual case
281 last = new ScicosObjectOwner(controller.createObject(Kind.BLOCK), Kind.BLOCK);
283 // allocate an empty block that will be filled later
284 synchronousScilabExec("xcosCellCreated(" + interfaceFunction + "(\"define\")); ");
285 last = getLastCreated();
288 // defensive programming
290 throw new InterpreterException("XcosCellFactory#createBlock : unable to allocate " + interfaceFunction);
293 if (EnumSet.of(Kind.BLOCK, Kind.ANNOTATION).contains(last.getKind())) {
294 block = createBlock(controller, func, interfaceFunction, last.getUID(), last.getKind());
302 public static BasicBlock createBlock(final JavaController controller, long uid, Kind kind) {
303 String[] interfaceFunction = new String[1];
304 if (kind == Kind.BLOCK) {
305 controller.getObjectProperty(uid, kind, ObjectProperties.INTERFACE_FUNCTION, interfaceFunction);
306 } else { // ANNOTATION
307 interfaceFunction[0] = "TEXT_f";
310 final BlockInterFunction func = lookForInterfunction(interfaceFunction[0]);
312 return createBlock(controller, func, interfaceFunction[0], uid, kind);
315 public static BlockInterFunction lookForInterfunction(String interfaceFunction) {
316 Optional<BlockInterFunction> optFunc = EnumSet.allOf(BlockInterFunction.class).stream().filter(f -> f.name().equals(interfaceFunction)).findFirst();
318 final BlockInterFunction func;
319 if (optFunc.isPresent()) {
320 func = optFunc.get();
322 func = BlockInterFunction.BASIC_BLOCK;
328 * Instantiate a new block with the specified interface function and uid.
331 * the Java controller to use
333 * the interface function as an enum
334 * @param interfaceFunction
335 * the interface function name
338 * @return A new instance of a block.
340 public static BasicBlock createBlock(final JavaController controller, BlockInterFunction func, String interfaceFunction, long uid, Kind kind) {
342 final EnumMap<ObjectProperties, Integer> properties = new EnumMap<>(ObjectProperties.class);
343 properties.put(ObjectProperties.INPUTS, 0);
344 properties.put(ObjectProperties.OUTPUTS, 0);
345 properties.put(ObjectProperties.EVENT_INPUTS, 0);
346 properties.put(ObjectProperties.EVENT_OUTPUTS, 0);
350 * Retrieve the JGraphX data before cell creation
352 String[] strUID = new String[1];
353 controller.getObjectProperty(uid, kind, ObjectProperties.UID, strUID);
355 String[] style = new String[1];
356 controller.getObjectProperty(uid, kind, ObjectProperties.STYLE, style);
357 if (style[0].isEmpty()) {
358 style[0] = interfaceFunction;
362 String[] description = new String[] { "" };
363 controller.getObjectProperty(uid, kind, ObjectProperties.DESCRIPTION, description);
364 value = description[0];
366 VectorOfDouble geom = new VectorOfDouble(4);
367 controller.getObjectProperty(uid, kind, ObjectProperties.GEOMETRY, geom);
369 double x = geom.get(0);
370 double y = geom.get(1);
371 double w = geom.get(2);
372 double h = geom.get(3);
373 mxGeometry geometry = new mxGeometry(x, y, w, h);
376 * Instanciate the block
378 BasicBlock block = null;
380 Constructor<? extends BasicBlock> cstr = func.getKlass().getConstructor(JavaController.class, Long.TYPE, Kind.class, Object.class, mxGeometry.class, String.class, String.class);
381 block = cstr.newInstance(controller, uid, kind, value, geometry, style[0], strUID[0]);
382 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
383 | SecurityException e) {
384 // Something goes wrong, print it.
390 * Synchronize model information back to the JGraphX data
392 * Annotations have no inputs/outputs
395 if (block.getKind() == Kind.BLOCK) {
396 insertPortChildren(controller, properties, block);
401 * Compatibility to ease user definition :
402 * * split are not updated as they have an hard-coded fixed-size
403 * * round blocks : ports globally layout around the block
404 * * generic case : layout the ports per kind per block-side
406 boolean convertGeometry;
407 if (block instanceof SplitBlock) {
408 convertGeometry = false;
409 } else if (block instanceof RoundBlock) {
410 int numberOfPorts = properties.get(ObjectProperties.INPUTS) +
411 properties.get(ObjectProperties.OUTPUTS) +
412 properties.get(ObjectProperties.EVENT_INPUTS) +
413 properties.get(ObjectProperties.EVENT_OUTPUTS);
414 convertGeometry = (2 * w + 2 * h) < (numberOfPorts * BasicPort.DEFAULT_PORTSIZE);
416 convertGeometry = h < (properties.get(ObjectProperties.INPUTS) * BasicPort.DEFAULT_PORTSIZE) |
417 h < (properties.get(ObjectProperties.OUTPUTS) * BasicPort.DEFAULT_PORTSIZE) |
418 w < (properties.get(ObjectProperties.EVENT_INPUTS) * BasicPort.DEFAULT_PORTSIZE) |
419 w < (properties.get(ObjectProperties.EVENT_OUTPUTS) * BasicPort.DEFAULT_PORTSIZE);
422 if (convertGeometry) {
423 w = w * DEFAULT_SIZE_FACTOR;
424 h = h * DEFAULT_SIZE_FACTOR;
427 * Invert the y-axis value and translate it.
431 block.setGeometry(new mxGeometry(x, y, w, h));
438 * Instantiate a new block for an already created MVC object
440 * @param lastCreated the owned MVC object
441 * @return a block or null
443 public static BasicBlock createBlock(final JavaController controller, ScicosObjectOwner lastCreated) {
445 if (lastCreated.getKind() != Kind.ANNOTATION && lastCreated.getKind() != Kind.BLOCK) {
449 String[] interfaceFunction = new String[1];
450 BlockInterFunction func = lookForInterfunction(interfaceFunction[0]);
452 return createBlock(controller, func, interfaceFunction[0], lastCreated.getUID(), lastCreated.getKind());
460 * Helper used to create port children on a parent block.
462 * This method does not manage the model transaction and should be used to preset the children of a block out of an {@link XcosDiagram}.
465 * is the shared controller instance
467 * specify the kind of port to insert and should be some of :
469 * <LI>{@link ObjectProperties#INPUTS}
470 * <LI>{@link ObjectProperties#OUTPUTS}
471 * <LI>{@link ObjectProperties#EVENT_INPUTS}
472 * <LI>{@link ObjectProperties#EVENT_OUTPUTS}
474 * This method will fill the value with the number of added ports
476 * is the parent {@link mxCell} to modify
478 private static void insertPortChildren(final JavaController controller, final EnumMap<ObjectProperties, Integer> properties, final XcosCell parent) {
479 for (ObjectProperties property : properties.keySet()) {
480 properties.put(property, insertPortChildren(controller, property, parent));
485 * Helper used to create port children on a parent block.
487 * This method does not manage the model transaction and should be used to preset the children of a block out of an {@link XcosDiagram}.
490 * is the shared controller instance
492 * specify the kind of port to insert and should be one of :
494 * <LI>{@link ObjectProperties#INPUTS}
495 * <LI>{@link ObjectProperties#OUTPUTS}
496 * <LI>{@link ObjectProperties#EVENT_INPUTS}
497 * <LI>{@link ObjectProperties#EVENT_OUTPUTS}
499 * is the parent {@link mxCell} to modify
500 * @return the number of inserted children
502 private static int insertPortChildren(final JavaController controller, final ObjectProperties property, final XcosCell parent) {
503 VectorOfScicosID modelChildren = new VectorOfScicosID();
504 controller.getObjectProperty(parent.getUID(), parent.getKind(), property, modelChildren);
506 XcosCell[] children = new XcosCell[modelChildren.size()];
507 for (int i = 0; i < children.length; i++) {
508 XcosCell child = createPort(controller, modelChildren.get(i), property);
512 modelChildren.clear();
513 controller.setObjectProperty(parent.getUID(), parent.getKind(), property, modelChildren);
515 Arrays.stream(children).forEach(c -> {
517 controller.deleteObject(c.getUID());
520 return children.length;
524 * Create a port for a specific uid
527 * is the shared controller instance
529 * represent the allocated UID on the MVC
531 * specify the kind of port to create and should be one of :
533 * <LI>{@link ObjectProperties#INPUTS}
534 * <LI>{@link ObjectProperties#OUTPUTS}
535 * <LI>{@link ObjectProperties#EVENT_INPUTS}
536 * <LI>{@link ObjectProperties#EVENT_OUTPUTS}
537 * @return a newly allocated port
539 private static final BasicPort createPort(final JavaController controller, long uid, final ObjectProperties property) {
541 boolean[] isImplicit = { false };
543 String[] strUID = new String[] { "" };
544 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.UID, strUID);
546 String[] style = new String[] { "" };
547 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.STYLE, style);
549 String[] value = new String[] { "" };
550 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.LABEL, value);
554 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.IMPLICIT, isImplicit);
556 port = new ImplicitInputPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
558 port = new ExplicitInputPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
562 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.IMPLICIT, isImplicit);
564 port = new ImplicitOutputPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
566 port = new ExplicitOutputPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
570 port = new ControlPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
573 port = new CommandPort(controller, uid, Kind.PORT, value[0], style[0], strUID[0]);
580 * Setup JGraphX properties
590 private static BasicLink createLink(JavaController controller, long uid, Kind kind) {
591 int[] type = new int[1];
592 controller.getObjectProperty(uid, kind, ObjectProperties.KIND, type);
595 * Synchronize model information back to the JGraphX data
598 String[] value = new String[] { "" };
599 controller.getObjectProperty(uid, kind, ObjectProperties.LABEL, value);
601 String[] style = new String[] { "" };
602 controller.getObjectProperty(uid, kind, ObjectProperties.STYLE, style);
604 String[] strUID = new String[] { "" };
605 controller.getObjectProperty(uid, kind, ObjectProperties.UID, strUID);
607 VectorOfDouble controlPoints = new VectorOfDouble();
608 controller.getObjectProperty(uid, kind, ObjectProperties.CONTROL_POINTS, controlPoints);
609 final int pointsLen = controlPoints.size() / 2;
611 mxGeometry geom = new mxGeometry();
613 // as the link is supposed to be connected and accordingly to the JGraphX rules : do not add the origin and destination point
614 ArrayList<mxPoint> points = new ArrayList<>();
619 for (; i < pointsLen - 1; i++) {
620 points.add(new mxPoint(controlPoints.get(2 * i), controlPoints.get(2 * i + 1)));
622 // ignore destination
625 geom.setPoints(points);
633 link = new CommandControlLink(controller, uid, kind, value[0], geom, style[0], strUID[0]);
636 link = new ExplicitLink(controller, uid, kind, value[0], geom, style[0], strUID[0]);
639 link = new ImplicitLink(controller, uid, kind, value[0], geom, style[0], strUID[0]);