Xcos GUI: handle XCOS / ZCOS import
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / graph / model / XcosCellFactory.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2015-2015 - Scilab Enterprises - Clement DAVID
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.graph.model;
14
15 import static org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.buildCall;
16 import static org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.synchronousScilabExec;
17
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.EnumSet;
24 import java.util.Optional;
25
26 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.InterpreterException;
27 import org.scilab.modules.graph.utils.ScilabExported;
28 import org.scilab.modules.xcos.JavaController;
29 import org.scilab.modules.xcos.Kind;
30 import org.scilab.modules.xcos.ObjectProperties;
31 import org.scilab.modules.xcos.VectorOfDouble;
32 import org.scilab.modules.xcos.VectorOfScicosID;
33 import org.scilab.modules.xcos.Xcos;
34 import org.scilab.modules.xcos.XcosView;
35 import org.scilab.modules.xcos.block.BasicBlock;
36 import org.scilab.modules.xcos.graph.XcosDiagram;
37 import org.scilab.modules.xcos.link.BasicLink;
38 import org.scilab.modules.xcos.link.commandcontrol.CommandControlLink;
39 import org.scilab.modules.xcos.link.explicit.ExplicitLink;
40 import org.scilab.modules.xcos.link.implicit.ImplicitLink;
41 import org.scilab.modules.xcos.port.BasicPort;
42 import org.scilab.modules.xcos.port.command.CommandPort;
43 import org.scilab.modules.xcos.port.control.ControlPort;
44 import org.scilab.modules.xcos.port.input.ExplicitInputPort;
45 import org.scilab.modules.xcos.port.input.ImplicitInputPort;
46 import org.scilab.modules.xcos.port.output.ExplicitOutputPort;
47 import org.scilab.modules.xcos.port.output.ImplicitOutputPort;
48 import org.scilab.modules.xcos.utils.BlockPositioning;
49
50 import com.mxgraph.model.mxCell;
51 import com.mxgraph.model.mxGeometry;
52 import com.mxgraph.model.mxICell;
53 import com.mxgraph.util.mxPoint;
54
55 /**
56  * Ease the creation of any {@link Kind} of graphical object
57  */
58 public final class XcosCellFactory {
59
60     /** Default singleton constructor */
61     private XcosCellFactory() {
62         // This class is a static singleton
63     }
64
65     /**
66      * This is a notify method mapped as a Scilab gateway used to alert with the loaded UID
67      * @param uid the loaded UID
68      * @param kind the kind of the created object (as an int)
69      */
70     @ScilabExported(module = "xcos", filename = "XcosCellFactory.giws.xml")
71     public static void created(long uid, int kind) {
72         lastCreated = new ScicosObjectOwner(uid, Kind.values()[kind]);
73
74     }
75     private static ScicosObjectOwner lastCreated = null;
76
77     /*
78      * Diagram management
79      */
80
81     /**
82      * Allocate a Java XcosDiagram from a COSF file.
83      *
84      * This method execute the file and register a
85      *
86      * @param controller the controller
87      * @param filename the file to execute
88      * @return an allocated XcosDiagram
89      */
90     public static XcosDiagram createDiagramFromCOSF(final JavaController controller, String filename) {
91         XcosView view = (XcosView) JavaController.lookup_view(Xcos.class.getName());
92         JavaController.unregister_view(view);
93
94         XcosDiagram diagram;
95         try {
96             synchronousScilabExec(
97                 "function f(), " +
98                 buildCall("exec", filename, -1) +
99                 buildCall("xcosCellCreated", "scs_m".toCharArray()) +
100                 "endfunction; f();");
101
102             if (lastCreated.getKind() == Kind.DIAGRAM) {
103                 diagram = new XcosDiagram(lastCreated.getUID(), lastCreated.getKind());
104                 insertChildren(controller, diagram);
105                 lastCreated = null;
106             } else {
107                 diagram = null;
108             }
109         } catch (InterpreterException e) {
110             diagram = null;
111         } finally {
112             JavaController.register_view(Xcos.class.getName(), view);
113         }
114
115         return diagram;
116     }
117
118     /**
119      * Insert the diagram MVC children into the JGraphX model
120      * @param controller the shared controller
121      * @param diagram the current diagram instance
122      */
123     public static void insertChildren(JavaController controller, XcosDiagram diagram) {
124         VectorOfScicosID children = new VectorOfScicosID();
125         controller.getObjectProperty(diagram.getUID(), diagram.getKind(), ObjectProperties.CHILDREN, children);
126         final int childrenLen = children.size();
127
128         /*
129          * Allocation some pre-sized stash data
130          */
131         final ArrayList<BasicLink> links = new ArrayList<>(childrenLen / 2);
132         final ArrayList<BasicPort> ports = new ArrayList<>(childrenLen);
133
134         /*
135          * Create the XcosCell objects and store some of them for later use
136          */
137         XcosCell[] cells = new XcosCell[childrenLen];
138         for (int i = 0; i < childrenLen; i++) {
139             final long uid = children.get(i);
140             final Kind kind = controller.getKind(uid);
141
142             switch (kind) {
143                 case ANNOTATION:
144                 case BLOCK:
145                     BasicBlock b = createBlock(controller, uid, kind);
146                     cells[i] = b;
147                     BlockPositioning.updatePortsPosition(diagram, b);
148                     b.getTypedChildrenIndexes(BasicPort.class).stream()
149                     .map(index -> b.getChildAt(index))
150                     .filter(c -> c instanceof BasicPort)
151                     .forEach( c -> ports.add((BasicPort) c));
152                     break;
153                 case LINK:
154                     BasicLink l = createLink(controller, uid, kind);
155                     cells[i] = l;
156                     links.add(l);
157                     break;
158                 default:
159                     break;
160             }
161         }
162
163         /*
164          * Relink the links on the XcosCell part
165          */
166         Comparator<XcosCell> compare = (c1, c2) -> (int) (c1.getUID() - c2.getUID());
167         Collections.sort(ports, compare);
168         for (BasicLink l : links) {
169             long[] src = new long[1];
170             controller.getObjectProperty(l.getUID(), l.getKind(), ObjectProperties.SOURCE_PORT, src);
171
172             long[] dest = new long[1];
173             controller.getObjectProperty(l.getUID(), l.getKind(), ObjectProperties.DESTINATION_PORT, dest);
174
175             int srcIndex = Collections.binarySearch(ports, new XcosCell(src[0], Kind.PORT), compare);
176             if (srcIndex >= 0) {
177                 l.setSource(ports.get(srcIndex));
178             } else {
179                 throw new IllegalStateException();
180             }
181
182             int destIndex = Collections.binarySearch(ports, new XcosCell(dest[0], Kind.PORT), compare);
183             if (destIndex > 0) {
184                 l.setTarget(ports.get(destIndex));
185             } else {
186                 throw new IllegalStateException();
187             }
188         }
189
190         diagram.addCells(cells);
191     }
192
193
194
195     /*
196      * Block and Annotation management
197      */
198
199     /**
200      * Instantiate a new block with the specified interface function.
201      *
202      * @param func
203      *            the interface function
204      * @return A new instance of a block.
205      */
206     public static BasicBlock createBlock(BlockInterFunction func) {
207         return createBlock(func, func.name());
208     }
209
210     /**
211      * Instantiate a new block with the specified UID value and interface function
212      *
213      * @param uid
214      *            The associated UID value
215      * @param interfaceFunction the interface function
216      * @return A new instance of a block.
217      */
218     public static BasicBlock createBlock(String interfaceFunction) {
219         Optional<BlockInterFunction> func = EnumSet.allOf(BlockInterFunction.class).stream()
220                                             .filter(f -> f.name().equals(interfaceFunction))
221                                             .findFirst();
222
223         final BasicBlock block;
224         if (func.isPresent()) {
225             block = createBlock(func.get());
226         } else {
227             block = createBlock(BlockInterFunction.BASIC_BLOCK, interfaceFunction);
228         }
229         block.setStyle(interfaceFunction);
230
231         return block;
232     }
233
234     private static BasicBlock createBlock(BlockInterFunction func, String interfaceFunction) {
235         return createBlock(new JavaController(), func, interfaceFunction);
236     }
237
238     private static BasicBlock createBlock(final JavaController controller, BlockInterFunction func, String interfaceFunction) {
239         BasicBlock block;
240         try {
241             if (BlockInterFunction.BASIC_BLOCK.name().equals(interfaceFunction)) {
242                 // deliver all the MVC speed for the casual case
243                 lastCreated = new ScicosObjectOwner(controller.createObject(Kind.BLOCK), Kind.BLOCK);
244             } else {
245                 // allocate an empty block that will be filled later
246                 synchronousScilabExec("xcosCellCreated(" + interfaceFunction + "(\"define\")); ");
247             }
248
249             // defensive programming
250             if (lastCreated == null) {
251                 System.err.println("XcosCellFactory#createBlock : unable to allocate " + interfaceFunction);
252                 return null;
253             }
254
255             if (EnumSet.of(Kind.BLOCK, Kind.ANNOTATION).contains(lastCreated.getKind())) {
256                 block = createBlock(controller, func, interfaceFunction, lastCreated.getUID());
257                 lastCreated = null;
258             } else {
259                 block = null;
260             }
261         } catch (InterpreterException e) {
262             block = null;
263         }
264
265         return block;
266     }
267
268     private static BasicBlock createBlock(final JavaController controller, long uid, Kind kind) {
269         String[] interfaceFunction = new String[1];
270         controller.getObjectProperty(uid, kind, ObjectProperties.INTERFACE_FUNCTION, interfaceFunction);
271
272         return createBlock(controller, interfaceFunction[0], uid);
273     }
274
275     private static BasicBlock createBlock(final JavaController controller, String interfaceFunction, long uid) {
276         Optional<BlockInterFunction> optFunc = EnumSet.allOf(BlockInterFunction.class).stream()
277                                                .filter(f -> f.name().equals(interfaceFunction))
278                                                .findFirst();
279
280         final BlockInterFunction func;
281         if (optFunc.isPresent()) {
282             func = optFunc.get();
283         } else {
284             func = BlockInterFunction.BASIC_BLOCK;
285         }
286
287         return createBlock(controller, func, interfaceFunction, uid);
288     }
289
290     /**
291      * Instantiate a new block with the specified interface function and uid.
292      *
293      * @param controller the Java controller to use
294      * @param func the interface function as an enum
295      * @param interfaceFunction the interface function name
296      * @param uid the allocated uid
297      * @return A new instance of a block.
298      */
299     private static BasicBlock createBlock(final JavaController controller, BlockInterFunction func, String interfaceFunction, long uid) {
300         BasicBlock block = null;
301         try {
302             block = func.getKlass().getConstructor(Long.TYPE).newInstance(uid);
303         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
304             // Something goes wrong, print it.
305             e.printStackTrace();
306             return block;
307         }
308
309         /*
310          * Synchronize model information back to the JGraphX data
311          */
312         insertPortChildren(controller, block);
313
314         String[] style = new String[1];
315         controller.getObjectProperty(block.getUID(), block.getKind(), ObjectProperties.STYLE, style);
316         if (style[0].isEmpty()) {
317             block.setStyle(interfaceFunction);
318         } else {
319             block.setStyle(style[0]);
320         }
321
322         VectorOfDouble geom = new VectorOfDouble(4);
323         controller.getObjectProperty(block.getUID(), block.getKind(), ObjectProperties.GEOMETRY, geom);
324         block.setGeometry(new mxGeometry(geom.get(0), geom.get(1), geom.get(2), geom.get(3)));
325
326         // FIXME find a way to reuse the Scicos compat handler from org.scilab.modules.xcos.io.scicos
327
328
329
330
331         return block;
332     }
333
334     /*
335      * Port management
336      */
337
338     /**
339      * Helper used to create port children on a parent block.
340      *
341      * This method does not manage the model transaction and should be used to preset the children of a block out of an {@link XcosDiagram}.
342      *
343      * @param controller is the shared controller instance
344      * @param parent is the parent {@link mxCell} to modify
345      */
346     private static void insertPortChildren(final JavaController controller, final XcosCell parent) {
347         final EnumSet<ObjectProperties> properties = EnumSet.of(ObjectProperties.INPUTS, ObjectProperties.OUTPUTS, ObjectProperties.EVENT_INPUTS, ObjectProperties.EVENT_OUTPUTS);
348         insertPortChildren(controller, properties, parent);
349     }
350
351
352     /**
353      * Helper used to create port children on a parent block.
354      *
355      * This method does not manage the model transaction and should be used to preset the children of a block out of an {@link XcosDiagram}.
356      *
357      * @param controller is the shared controller instance
358      * @param properties specify the kind of port to insert and should be some of : <UL>
359      *        <LI>{@link ObjectProperties#INPUTS}
360      *        <LI>{@link ObjectProperties#OUTPUTS}
361      *        <LI>{@link ObjectProperties#EVENT_INPUTS}
362      *        <LI>{@link ObjectProperties#EVENT_OUTPUTS}
363      * @param parent is the parent {@link mxCell} to modify
364      */
365     private static void insertPortChildren(final JavaController controller, final EnumSet<ObjectProperties> properties, final XcosCell parent) {
366         for (ObjectProperties property : properties) {
367             insertPortChildren(controller, property, parent);
368         }
369     }
370
371     /**
372      * Helper used to create port children on a parent block.
373      *
374      * This method does not manage the model transaction and should be used to preset the children of a block out of an {@link XcosDiagram}.
375      *
376      * @param controller is the shared controller instance
377      * @param property specify the kind of port to insert and should be one of : <UL>
378      *        <LI>{@link ObjectProperties#INPUTS}
379      *        <LI>{@link ObjectProperties#OUTPUTS}
380      *        <LI>{@link ObjectProperties#EVENT_INPUTS}
381      *        <LI>{@link ObjectProperties#EVENT_OUTPUTS}
382      * @param parent is the parent {@link mxCell} to modify
383      */
384     private static void insertPortChildren(final JavaController controller, final ObjectProperties property, final XcosCell parent) {
385         if (parent.getKind() != Kind.BLOCK) {
386             return;
387         }
388
389         VectorOfScicosID modelChildren = new VectorOfScicosID();
390         controller.getObjectProperty(parent.getUID(), parent.getKind(), property, modelChildren);
391
392         mxICell[] children = new mxICell[modelChildren.size()];
393         for (int i = 0; i < children.length; i++) {
394             children[i] = createPort(controller, modelChildren.get(i), property);
395         }
396         Arrays.stream(children).forEach(c -> parent.insert(c));
397     }
398
399     /**
400      * Create a port for a specific uid
401      *
402      * @param controller is the shared controller instance
403      * @param uid represent the allocated UID on the MVC
404      * @param property specify the kind of port to create and should be one of : <UL>
405      *        <LI>{@link ObjectProperties#INPUTS}
406      *        <LI>{@link ObjectProperties#OUTPUTS}
407      *        <LI>{@link ObjectProperties#EVENT_INPUTS}
408      *        <LI>{@link ObjectProperties#EVENT_OUTPUTS}
409      * @return a newly allocated port
410      */
411     private static final BasicPort createPort(final JavaController controller, long uid, final ObjectProperties property) {
412         boolean[] isImplicit = {false};
413
414         switch (property) {
415             case INPUTS:
416                 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.IMPLICIT, isImplicit);
417                 if (isImplicit[0]) {
418                     return new ImplicitInputPort(uid);
419                 } else {
420                     return new ExplicitInputPort(uid);
421                 }
422             case OUTPUTS:
423                 controller.getObjectProperty(uid, Kind.PORT, ObjectProperties.IMPLICIT, isImplicit);
424                 if (isImplicit[0]) {
425                     return new ImplicitOutputPort(uid);
426                 } else {
427                     return new ExplicitOutputPort(uid);
428                 }
429             case EVENT_INPUTS:
430                 return new ControlPort(uid);
431             case EVENT_OUTPUTS:
432                 return new CommandPort(uid);
433             default:
434                 return null;
435         }
436     }
437
438     /*
439      * Link management
440      */
441
442     private static BasicLink createLink(JavaController controller, long uid, Kind kind) {
443         int[] type = new int[1];
444         controller.getObjectProperty(uid, kind, ObjectProperties.KIND, type);
445
446         BasicLink link;
447         switch (type[0]) {
448             case -1:
449                 link = new CommandControlLink(uid);
450                 break;
451             case 1:
452                 link = new ExplicitLink(uid);
453                 break;
454             case 2:
455                 link = new ImplicitLink(uid);
456                 break;
457             default:
458                 return null;
459         }
460
461         /*
462          * Synchronize model information back to the JGraphX data
463          */
464         VectorOfDouble controlPoints = new VectorOfDouble();
465         controller.getObjectProperty(uid, kind, ObjectProperties.CONTROL_POINTS, controlPoints);
466         final int pointsLen = controlPoints.size() / 2;
467
468         mxGeometry geom = new mxGeometry();
469
470         // as the link is supposed to be connected and accordingly to the JGraphX rules : do not add the origin and destination point
471         ArrayList<mxPoint> points = new ArrayList<>();
472         int i = 0;
473         // ignore origin
474         i++;
475         // loop for points
476         for (; i < pointsLen - 1; i++) {
477             points.add(new mxPoint(controlPoints.get(2 * i), controlPoints.get(2 * i + 1)));
478         }
479         // ignore destination
480         i++;
481
482         geom.setPoints(points);
483         link.setGeometry(geom);
484         return link;
485     }
486
487 }