* Bug #14659 fixed: number of I/O ports of the superblock was not updated when adding...
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / graph / XcosDiagram.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009-2009 - DIGITEO - Bruno JOFRET
4  * Copyright (C) 2009-2010 - DIGITEO - Clement DAVID
5  * Copyright (C) 2011-2017 - Scilab Enterprises - Clement DAVID
6  * Copyright (C) 2015 - Marcos Cardinot
7  *
8  * Copyright (C) 2012 - 2016 - Scilab Enterprises
9  *
10  * This file is hereby licensed under the terms of the GNU GPL v2.0,
11  * pursuant to article 5.3.4 of the CeCILL v.2.1.
12  * This file was originally licensed under the terms of the CeCILL v2.1,
13  * and continues to be available under such terms.
14  * For more information, see the COPYING file which you should have received
15  * along with this program.
16  *
17  */
18 package org.scilab.modules.xcos.graph;
19
20 import org.scilab.modules.xcos.graph.model.XcosGraphModel;
21 import java.awt.GraphicsEnvironment;
22 import java.awt.event.ActionEvent;
23 import java.awt.event.ActionListener;
24 import java.io.File;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.HashSet;
32 import java.util.IllegalFormatException;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Queue;
37 import java.util.Set;
38 import java.util.logging.Logger;
39
40 import javax.swing.JFileChooser;
41 import javax.swing.JOptionPane;
42 import javax.swing.SwingWorker;
43 import javax.swing.Timer;
44
45 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement;
46 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.InterpreterException;
47 import org.scilab.modules.graph.ScilabGraph;
48 import org.scilab.modules.graph.utils.ScilabGraphConstants;
49 import org.scilab.modules.gui.bridge.filechooser.SwingScilabFileChooser;
50 import org.scilab.modules.gui.bridge.tab.SwingScilabDockablePanel;
51 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
52 import org.scilab.modules.gui.messagebox.ScilabModalDialog.AnswerOption;
53 import org.scilab.modules.gui.messagebox.ScilabModalDialog.ButtonType;
54 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
55 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
56 import org.scilab.modules.xcos.JavaController;
57 import org.scilab.modules.xcos.Kind;
58 import org.scilab.modules.xcos.ObjectProperties;
59 import org.scilab.modules.xcos.VectorOfDouble;
60 import org.scilab.modules.xcos.VectorOfInt;
61 import org.scilab.modules.xcos.VectorOfString;
62 import org.scilab.modules.xcos.Xcos;
63 import org.scilab.modules.xcos.XcosTab;
64 import org.scilab.modules.xcos.actions.SaveAsAction;
65 import org.scilab.modules.xcos.block.AfficheBlock;
66 import org.scilab.modules.xcos.block.BasicBlock;
67 import org.scilab.modules.xcos.block.SplitBlock;
68 import org.scilab.modules.xcos.block.TextBlock;
69 import org.scilab.modules.xcos.block.io.EventInBlock;
70 import org.scilab.modules.xcos.block.io.EventOutBlock;
71 import org.scilab.modules.xcos.block.io.ExplicitInBlock;
72 import org.scilab.modules.xcos.block.io.ExplicitOutBlock;
73 import org.scilab.modules.xcos.block.io.ImplicitInBlock;
74 import org.scilab.modules.xcos.block.io.ImplicitOutBlock;
75 import org.scilab.modules.xcos.configuration.ConfigurationManager;
76 import org.scilab.modules.xcos.graph.model.BlockInterFunction;
77 import org.scilab.modules.xcos.graph.model.ScicosObjectOwner;
78 import org.scilab.modules.xcos.graph.model.XcosCell;
79 import org.scilab.modules.xcos.graph.model.XcosCellFactory;
80 import org.scilab.modules.xcos.graph.swing.GraphComponent;
81 import org.scilab.modules.xcos.io.XcosFileType;
82 import org.scilab.modules.xcos.io.scicos.ScilabDirectHandler;
83 import org.scilab.modules.xcos.link.BasicLink;
84 import org.scilab.modules.xcos.link.CommandControlLink;
85 import org.scilab.modules.xcos.link.ExplicitLink;
86 import org.scilab.modules.xcos.link.ImplicitLink;
87 import org.scilab.modules.xcos.port.BasicPort;
88 import org.scilab.modules.xcos.port.BasicPort.Type;
89 import org.scilab.modules.xcos.port.Orientation;
90 import org.scilab.modules.xcos.port.PortCheck;
91 import org.scilab.modules.xcos.port.command.CommandPort;
92 import org.scilab.modules.xcos.port.control.ControlPort;
93 import org.scilab.modules.xcos.port.input.ExplicitInputPort;
94 import org.scilab.modules.xcos.port.input.ImplicitInputPort;
95 import org.scilab.modules.xcos.port.output.ExplicitOutputPort;
96 import org.scilab.modules.xcos.port.output.ImplicitOutputPort;
97 import org.scilab.modules.xcos.preferences.XcosOptions;
98 import org.scilab.modules.xcos.utils.BlockPositioning;
99 import org.scilab.modules.xcos.utils.Stack;
100 import org.scilab.modules.xcos.utils.XcosConstants;
101 import org.scilab.modules.xcos.utils.XcosDialogs;
102 import org.scilab.modules.xcos.utils.XcosMessages;
103
104 import com.mxgraph.model.mxCell;
105 import com.mxgraph.model.mxGeometry;
106 import com.mxgraph.model.mxGraphModel;
107 import com.mxgraph.model.mxICell;
108 import com.mxgraph.util.mxEvent;
109 import com.mxgraph.util.mxEventObject;
110 import com.mxgraph.util.mxPoint;
111 import com.mxgraph.view.mxGraphSelectionModel;
112 import com.mxgraph.view.mxMultiplicity;
113 import java.lang.reflect.Constructor;
114 import java.rmi.server.UID;
115 import java.util.HashMap;
116 import java.util.Hashtable;
117 import java.util.Optional;
118 import java.util.stream.Collectors;
119 import org.scilab.modules.xcos.VectorOfBool;
120 import org.scilab.modules.xcos.VectorOfScicosID;
121 import org.scilab.modules.xcos.block.SuperBlock;
122 import org.scilab.modules.xcos.block.io.ContextUpdate;
123 import org.scilab.modules.xcos.io.ScilabTypeCoder;
124 import org.scilab.modules.xcos.utils.XcosEvent;
125
126 /**
127  * The base class for a diagram. This class contains jgraphx + Scicos data.
128  */
129 public class XcosDiagram extends ScilabGraph {
130
131     private static final Logger LOG = Logger.getLogger(XcosDiagram.class.getName());
132
133     private static final String CELLS = "cells";
134     public static final String IN = "in";
135     public static final String OUT = "out";
136     public static final String EIN = "ein";
137     public static final String EOUT = "eout";
138
139     /**
140      * Prefix used to tag text node.
141      */
142     public static final String HASH_IDENTIFIER = "#identifier";
143
144     /**
145      * Default geometry used while adding a label to a block (on the middle and
146      * below the bottom of the parent block)
147      */
148     private static final mxGeometry DEFAULT_LABEL_GEOMETRY = new mxGeometry(0.5, 1.1, 0.0, 0.0);
149
150     /**
151      * Constructor
152      *
153      * @param controller the shared controller
154      * @param diagramId the diagram MVC ID
155      * @param kind DIAGRAM or BLOCK for a root diagram or a super-block
156      * @param uid the string UID that will be used on the default parent
157      */
158     public XcosDiagram(final JavaController controller, final long diagramId, final Kind kind, String uid) {
159         super(new XcosGraphModel(controller, diagramId, kind, uid), Xcos.getInstance().getStyleSheet());
160
161         // set the default parent (the JGraphX layer) defined on the model
162         setDefaultParent(getModel().getChildAt(getModel().getRoot(), 0));
163
164         setComponent(new GraphComponent(this));
165         initComponent();
166
167         // Forbid disconnecting cells once it is connected.
168         setCellsDisconnectable(false);
169
170         // Forbid pending edges.
171         setAllowDanglingEdges(false);
172
173         // Cannot connect port to itself.
174         setAllowLoops(false);
175
176         // Override isCellResizable to filter what the user can resize
177         setCellsResizable(true);
178
179         /* Labels use HTML if not equal to interface function name */
180         setHtmlLabels(true);
181         /*
182          * by default every label is movable, see XcosDiagram##isLabelMovable(java.lang.Object) for restrictions
183          */
184         setVertexLabelsMovable(true);
185         setEdgeLabelsMovable(true);
186
187         //
188         setCloneInvalidEdges(true);
189
190         // Override isCellEditable to filter what the user can edit
191         setCellsEditable(true);
192
193         setConnectableEdges(true);
194
195         // Do not clear edge points on connect
196         setResetEdgesOnConnect(false);
197
198         setMultiplicities();
199
200         // auto-position the diagram origin
201         setAutoOrigin(true);
202
203         // do not put loop links inside the common block cell but on the defaultParent
204         ((mxGraphModel) getModel()).setMaintainEdgeParent(false);
205     }
206
207     /*
208      * Static helpers
209      */
210     /**
211      * Only return the instanceof klass
212      *
213      * @param selection the selection to filter out
214      * @param klass the class selector
215      * @return the selection with only klass instance.
216      */
217     public static Object[] filterByClass(final Object[] selection, final Class<BasicBlock> klass) {
218         return mxGraphModel.filterCells(selection, new mxGraphModel.Filter() {
219             @Override
220             public boolean filter(Object cell) {
221                 return klass.isInstance(cell);
222             }
223         });
224     }
225
226     /**
227      * Fill the hierarchy from the first element up to the root diagram
228      * (included)
229      * <p>
230      * Should be used as :
231      * <pre>
232      *  hierarchy = fillHierarchy(new ScicosObjectOwner(getUID(), getKind()))
233      * </pre>
234      *
235      * @param hierarchy the collection to fill
236      * @return the filled collection (the root at the end)
237      */
238     public static Stack<ScicosObjectOwner> lookForHierarchy(ScicosObjectOwner current) {
239         ScicosObjectOwner local = current;
240         Stack<ScicosObjectOwner> hierarchy = new Stack<>();
241         JavaController controller = new JavaController();
242
243         long[] parent = new long[] {local.getUID()};
244         if (local.getKind() == Kind.DIAGRAM) {
245             hierarchy.push(local);
246             return hierarchy;
247         }
248
249         while (parent[0] != 0l) {
250             hierarchy.push(new ScicosObjectOwner(parent[0], Kind.BLOCK));
251             controller.getObjectProperty(local.getUID(), local.getKind(), ObjectProperties.PARENT_BLOCK, parent);
252         }
253
254         controller.getObjectProperty(local.getUID(), local.getKind(), ObjectProperties.PARENT_DIAGRAM, parent);
255         hierarchy.push(new ScicosObjectOwner(parent[0], Kind.DIAGRAM));
256
257         return hierarchy;
258     }
259
260     /**
261      * Sort the blocks per first integer parameter value
262      *
263      * @param blocks the block list
264      * @param the shared controller
265      * @return the sorted block list (same instance)
266      */
267     public static List<? extends BasicBlock> iparSort(final List<? extends BasicBlock> blocks, final JavaController controller) {
268         Collections.sort(blocks, new Comparator<BasicBlock>() {
269             @Override
270             public int compare(BasicBlock o1, BasicBlock o2) {
271
272
273                 final VectorOfInt data1 = new VectorOfInt();
274                 final VectorOfInt data2 = new VectorOfInt();
275
276                 controller.getObjectProperty(o1.getUID(), Kind.BLOCK, ObjectProperties.IPAR, data1);
277                 controller.getObjectProperty(o2.getUID(), Kind.BLOCK, ObjectProperties.IPAR, data2);
278
279                 final int value1;
280                 if (data1.size() >= 1) {
281                     value1 = data1.get(0);
282                 } else {
283                     value1 = 0;
284                 }
285
286                 final int value2;
287                 if (data2.size() >= 1) {
288                     value2 = data2.get(0);
289                 } else {
290                     value2 = 0;
291                 }
292
293                 return value1 - value2;
294             }
295         });
296         return blocks;
297     }
298
299     /**
300      * @param <T> The type to work on
301      * @param klass the class instance to work on
302      * @return list of typed block
303      */
304     @SuppressWarnings("unchecked")
305     private <T extends BasicBlock> List<T> getAllTypedBlock(Class<T> klass) {
306         final List<T> list = new ArrayList<T>();
307
308         int blockCount = getModel().getChildCount(getDefaultParent());
309
310         for (int i = 0; i < blockCount; i++) {
311             Object cell = getModel().getChildAt(getDefaultParent(), i);
312             if (klass.isInstance(cell)) {
313                 // According to the test we are sure that the cell is an
314                 // instance of T. Thus we can safely cast it.
315                 list.add((T) cell);
316             }
317         }
318         return list;
319     }
320
321     /**
322      * @param <T>
323      * @param <T> The type to work on
324      * @param klasses the class instance list to work on
325      * @return list of typed block
326      */
327     private <T extends BasicBlock> List<T> getAllTypedBlock(Class<T>[] klasses) {
328         final List<T> list = new ArrayList<T>();
329         for (Class<T> klass : klasses) {
330             list.addAll(getAllTypedBlock(klass));
331         }
332         return list;
333     }
334
335     /**
336      * Fill the context with I/O port
337      *
338      * @param context the context to fill
339      * @param controller the shared controller
340      * @return the context
341      */
342     @SuppressWarnings("unchecked")
343     public final Map<Object, Object> fillContext(final Map<Object, Object> context, final JavaController controller) {
344         if (!context.containsKey(IN)) {
345             context.put(IN, iparSort(getAllTypedBlock(new Class[] {ExplicitInBlock.class, ImplicitInBlock.class}), controller));
346         }
347         if (!context.containsKey(OUT)) {
348             context.put(OUT, iparSort(getAllTypedBlock(new Class[] {ExplicitOutBlock.class, ImplicitOutBlock.class}), controller));
349         }
350         if (!context.containsKey(EIN)) {
351             context.put(EIN, iparSort(getAllTypedBlock(new Class[] {EventInBlock.class}), controller));
352         }
353         if (!context.containsKey(EOUT)) {
354             context.put(EOUT, iparSort(getAllTypedBlock(new Class[] {EventOutBlock.class}), controller));
355         }
356
357         return context;
358     }
359
360     @Override
361     public String validateCell(final Object cell, final Hashtable<Object, Object> context) {
362         if (getKind() == Kind.BLOCK) {
363             return validateChildDiagram(cell, context);
364         } else {
365             // does not perform any validation on a root diagram
366             return null;
367         }
368     }
369
370     /**
371      * Validate I/O ports.
372      *
373      * /!\ No model modification should be made in this method, this is only a
374      * validation method.
375      *
376      * @param cell Cell that represents the cell to validate.
377      * @param context Hashtable that represents the global validation state.
378      * @return the error message or null
379      */
380     public String validateChildDiagram(final Object cell, final Map<Object, Object> context) {
381         String err = null;
382
383         /*
384          * Only validate I/O blocks
385          */
386         // get the key
387         final String key;
388         if (cell instanceof ExplicitInBlock || cell instanceof ImplicitInBlock) {
389             key = IN;
390         } else if (cell instanceof ExplicitOutBlock || cell instanceof ImplicitOutBlock) {
391             key = OUT;
392         } else if (cell instanceof EventInBlock) {
393             key = EIN;
394         } else if (cell instanceof EventOutBlock) {
395             key = EOUT;
396         } else {
397             return null;
398         }
399         final BasicBlock block = (BasicBlock) cell;
400         final JavaController controller = new JavaController();
401
402         /*
403          * Prepare validation
404          */
405         // fill the context once
406         fillContext(context, controller);
407
408         /*
409          * Validate with ipar
410          */
411         // get the real index
412         final List<? extends BasicBlock> blocks = (List<? extends BasicBlock>) context.get(key);
413         final int realIndex = blocks.indexOf(block) + 1;
414
415         // get the user index
416         VectorOfInt ipar = new VectorOfInt();
417         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.IPAR, ipar);
418         if (ipar.size() < 1) {
419             return err;
420         }
421         final int userIndex = ipar.get(0);
422
423         // if the indexes are not equals, alert the user.
424         if (realIndex != userIndex) {
425             final StringBuilder str = new StringBuilder();
426             str.append("<html><body><em>");
427             str.append(XcosMessages.WRONG_PORT_NUMBER);
428             str.append("</em><br/>");
429             str.append(String.format(XcosMessages.EXPECTING_NUMBER, realIndex, userIndex));
430             str.append("</body></html>    ");
431
432             err = str.toString();
433         }
434
435         return err;
436     }
437
438     /**
439      * Set a model property and track undo/redo operation
440      * @param cell the cell
441      * @param values the value to set
442      */
443     public void updateBlock(BasicBlock cell, Map<ObjectProperties, Object> values) {
444         model.beginUpdate();
445         try {
446             blockUpdated(cell, values);
447             fireEvent(new mxEventObject(XcosEvent.UPDATE_BLOCK, XcosEvent.BLOCK, cell));
448         } finally {
449             model.endUpdate();
450         }
451     }
452
453     private void blockUpdated(BasicBlock cell, Map<ObjectProperties, Object> values) {
454         model.beginUpdate();
455         try {
456             for (Map.Entry<ObjectProperties, Object> e : values.entrySet()) {
457                 if (e.getValue() instanceof double[]) {
458                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (double[]) e.getValue());
459                 } else if (e.getValue() instanceof int[]) {
460                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (int[]) e.getValue());
461                 } else if (e.getValue() instanceof boolean[]) {
462                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (boolean[]) e.getValue());
463                 } else if (e.getValue() instanceof String[]) {
464                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (String[]) e.getValue());
465                 } else if (e.getValue() instanceof long[]) {
466                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (long[]) e.getValue());
467                 } else if (e.getValue() instanceof VectorOfDouble) {
468                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfDouble) e.getValue());
469                 } else if (e.getValue() instanceof VectorOfInt) {
470                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfInt) e.getValue());
471                 } else if (e.getValue() instanceof VectorOfBool) {
472                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfBool) e.getValue());
473                 } else if (e.getValue() instanceof VectorOfString) {
474                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfString) e.getValue());
475                 } else if (e.getValue() instanceof VectorOfScicosID) {
476                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfScicosID) e.getValue());
477                 }
478             }
479         } finally {
480             model.endUpdate();
481         }
482
483     }
484
485     /*
486      * Static diagram listeners
487      */
488     /**
489      * CellResizedTracker Called when mxEvents.CELLS_RESIZED is fired.
490      */
491     private static final class RepositionTracker implements mxIEventListener {
492
493         private static RepositionTracker instance;
494
495         /**
496          * Constructor
497          */
498         private RepositionTracker() {
499         }
500
501         /**
502          * @return the instance
503          */
504         public static RepositionTracker getInstance() {
505             if (instance == null) {
506                 instance = new RepositionTracker();
507             }
508             return instance;
509         }
510
511         /**
512          * Update the cell view
513          *
514          * @param source the source instance
515          * @param evt the event data
516          * @see
517          * com.mxgraph.util.mxEventSource.mxIEventListener#invoke(java.lang.Object,
518          * com.mxgraph.util.mxEventObject)
519          */
520         @Override
521         public void invoke(final Object source, final mxEventObject evt) {
522             final XcosDiagram diagram = (XcosDiagram) source;
523             final Object[] cells = (Object[]) evt.getProperty(CELLS);
524
525             diagram.getModel().beginUpdate();
526             try {
527                 for (int i = 0; i < cells.length; ++i) {
528                     if (cells[i] instanceof BasicBlock) {
529                         BlockPositioning.updateBlockView(diagram, (BasicBlock) cells[i]);
530                     }
531                 }
532             } finally {
533                 diagram.getModel().endUpdate();
534             }
535         }
536     }
537
538     /**
539      * Refresh each block on modification (update port position, etc...)
540      */
541     private static final class RefreshBlockTracker implements mxIEventListener {
542         private static RefreshBlockTracker instance = new RefreshBlockTracker();
543
544         /**
545          * Default constructor
546          */
547         private RefreshBlockTracker() {
548         }
549
550         /**
551          * @return the instance
552          */
553         public static RefreshBlockTracker getInstance() {
554             return instance;
555         }
556
557         /**
558          * Refresh the block on port added
559          *
560          * @param sender the diagram
561          * @param evt the event
562          * @see
563          * com.mxgraph.util.mxEventSource.mxIEventListener#invoke(java.lang.Object,
564          * com.mxgraph.util.mxEventObject)
565          */
566         @Override
567         public void invoke(Object sender, mxEventObject evt) {
568             final XcosDiagram diagram = (XcosDiagram) sender;
569
570             if (diagram.isReadonly()) {
571                 return;
572             }
573
574             diagram.getModel().beginUpdate();
575             try {
576                 final BasicBlock updatedBlock = (BasicBlock) evt.getProperty(XcosEvent.BLOCK);
577                 BlockPositioning.updateBlockView(diagram, updatedBlock);
578
579                 diagram.getView().clear(updatedBlock, true, true);
580
581                 // validate display errors
582                 diagram.getAsComponent().clearCellOverlays();
583                 diagram.getAsComponent().validateGraph();
584
585                 diagram.getView().validate();
586             } finally {
587                 diagram.getModel().endUpdate();
588             }
589         }
590     }
591
592     /**
593      * Update the tab title depending on the status
594      */
595     private static final class SavedStatusTracker implements mxIEventListener {
596         private static final Map<XcosDiagram, SavedStatusTracker> instances = new HashMap<>();
597         private ScicosObjectOwner owner;
598
599         private SavedStatusTracker(final ScicosObjectOwner o) {
600             this.owner = o;
601         }
602
603         public static SavedStatusTracker getInstance(XcosDiagram d) {
604             return instances.putIfAbsent(d, new SavedStatusTracker(Xcos.findRoot(d)));
605         }
606
607         @Override
608         public void invoke(Object sender, mxEventObject eo) {
609             if (sender instanceof XcosGraphModel) {
610                 List<XcosDiagram> diagrams = Xcos.getInstance().openedDiagrams(owner);
611                 diagrams.stream().forEach(d -> d.updateTabTitle());
612             }
613         }
614     }
615
616     /**
617      * Update the number and position of ports for a Superblock
618      */
619     public static final class UpdateSuperblockPortsTracker implements mxIEventListener {
620
621         private static UpdateSuperblockPortsTracker instance = new UpdateSuperblockPortsTracker();
622
623         public static UpdateSuperblockPortsTracker getInstance() {
624             return instance;
625         }
626
627         @Override
628         public void invoke(Object sender, mxEventObject evt) {
629             final XcosDiagram diagram = (XcosDiagram) sender;
630
631             if (diagram.isReadonly()) {
632                 return;
633             }
634
635             // common behavior for both block adding/removing and block update
636             List<Object> updatedIOBlocks;
637             if (XcosEvent.UPDATE_BLOCK.equals(evt.getName())) {
638                 updatedIOBlocks = new ArrayList<>();
639                 Object b = evt.getProperty(XcosEvent.BLOCK);
640                 if  (b instanceof ContextUpdate) {
641                     updatedIOBlocks.add(b);
642                 }
643             } else { // ADD_CELLS or REMOVE_CELLS
644                 Object[] cells = (Object[]) evt.getProperty("cells");
645                 updatedIOBlocks = Arrays.stream(cells).filter(b -> b instanceof ContextUpdate).collect(Collectors.toList());
646             }
647             if (updatedIOBlocks.isEmpty()) {
648                 return;
649             }
650
651             final JavaController controller = new JavaController();
652
653             String[] superBlockUID = {""};
654             controller.getObjectProperty(diagram.getUID(), diagram.getKind(), ObjectProperties.UID, superBlockUID);
655
656             List<XcosDiagram> diagrams = Xcos.getInstance().openedDiagrams(Xcos.findRoot(controller, diagram));
657             for (XcosDiagram parent : diagrams) {
658                 final XcosGraphModel model = (XcosGraphModel) parent.getModel();
659                 Object cell = model.getCell(superBlockUID[0]);
660                 if (cell != null) {
661                     // associated parent superblock when visible
662                     SuperBlock superblock = (SuperBlock) cell;
663
664                     // create a full context
665                     Map<Object, Object> context = new HashMap<>();
666                     diagram.fillContext(context, controller);
667
668                     // only update the superblock ports if all of them are valids
669                     diagram.getAsComponent().clearCellOverlays();
670                     String status = diagram.getAsComponent().validateGraph();
671                     if (status != null) {
672                         return;
673                     }
674
675                     model.beginUpdate();
676                     try {
677                         syncPorts(controller, superblock, ObjectProperties.INPUTS, (List<ContextUpdate>) context.get(IN), parent);
678                         syncPorts(controller, superblock, ObjectProperties.OUTPUTS, (List<ContextUpdate>) context.get(OUT), parent);
679                         syncPorts(controller, superblock, ObjectProperties.EVENT_INPUTS, (List<ContextUpdate>) context.get(EIN), parent);
680                         syncPorts(controller, superblock, ObjectProperties.EVENT_OUTPUTS, (List<ContextUpdate>) context.get(EOUT), parent);
681                     } finally {
682                         model.endUpdate();
683                     }
684                     break;
685                 }
686             }
687         }
688
689         /**
690          * Synchronize ports on the parent diagram accordingly to the updated IOBlocks
691          * @param controller shared controller;
692          * @param superblock the superblock to update
693          * @param p port kind
694          * @param updated the updated IOBlocks (ipar sorted)
695          * @param parent diagram containing the superblock
696          */
697         public static void syncPorts(final JavaController controller, SuperBlock superblock, ObjectProperties p, List<ContextUpdate> updated, XcosDiagram parent) {
698             VectorOfScicosID ports = new VectorOfScicosID();
699             controller.getObjectProperty(superblock.getUID(), superblock.getKind(), p, ports);
700
701             int added = updated.size() - ports.size();
702             if (added > 0) {
703                 for (int i = 0; i < added; i++) {
704                     // adding port
705                     BasicPort port = ContextUpdate.IOBlocks.createPort(controller, updated.get(ports.size() + i));
706                     parent.addCell(port, superblock);
707                 }
708             } else if (added < 0) {
709                 for (int i = 0; i < -added; i++) {
710                     // removing port
711                     String portUID[] = {""};
712                     controller.getObjectProperty(ports.get(updated.size() + i), Kind.PORT, ObjectProperties.UID, portUID);
713                     parent.removeCells(new Object[] {((XcosGraphModel) parent.getModel()).getCell(portUID[0])});
714                 }
715             }
716         }
717
718         /**
719          * Append the modified blocks to an existing context
720          * @param context the validation context
721          * @param updatedIOBlocks the blocks to add
722          * @param controller shared controller
723          */
724         public static void updateContext(Map<Object, Object> context, List<XcosCell> updatedIOBlocks, final JavaController controller) {
725             {
726                 List<BasicBlock> l = (List<BasicBlock>) context.getOrDefault(IN, new ArrayList<>());
727                 updatedIOBlocks.stream()
728                 .filter(b -> b instanceof ExplicitInBlock || b instanceof ImplicitInBlock)
729                 .map(b -> (ContextUpdate) b)
730                 .collect(Collectors.toCollection(() -> l));
731                 iparSort(l, controller);
732                 context.putIfAbsent(IN, l);
733             }
734             {
735                 List<BasicBlock> l = (List<BasicBlock>) context.getOrDefault(OUT, new ArrayList<>());
736                 updatedIOBlocks.stream()
737                 .filter(b -> b instanceof ExplicitOutBlock || b instanceof ImplicitOutBlock)
738                 .map(b -> (ContextUpdate) b)
739                 .collect(Collectors.toCollection(() -> l));
740                 iparSort(l, controller);
741                 context.putIfAbsent(OUT, l);
742             }
743             {
744                 List<BasicBlock> l = (List<BasicBlock>) context.getOrDefault(EIN, new ArrayList<>());
745                 updatedIOBlocks.stream()
746                 .filter(b -> b instanceof EventInBlock)
747                 .map(b -> (ContextUpdate) b)
748                 .collect(Collectors.toCollection(() -> l));
749                 iparSort(l, controller);
750                 context.putIfAbsent(EIN, l);
751             }
752             {
753                 List<BasicBlock> l = (List<BasicBlock>) context.getOrDefault(EOUT, new ArrayList<>());
754                 updatedIOBlocks.stream()
755                 .filter(b -> b instanceof EventOutBlock)
756                 .map(b -> (ContextUpdate) b)
757                 .collect(Collectors.toCollection(() -> l));
758                 iparSort(l, controller);
759                 context.putIfAbsent(EOUT, l);
760             }
761         }
762     }
763
764     /**
765      * Hook method that creates the new edge for insertEdge. This implementation
766      * does not set the source and target of the edge, these are set when the
767      * edge is added to the model.
768      *
769      * @param parent Cell that specifies the parent of the new edge.
770      * @param id Optional string that defines the Id of the new edge.
771      * @param value Object to be used as the user object.
772      * @param source Cell that defines the source of the edge.
773      * @param target Cell that defines the target of the edge.
774      * @param style Optional string that defines the cell style.
775      * @return Returns the new edge to be inserted.
776      * @see com.mxgraph.view.mxGraph#createEdge(java.lang.Object,
777      * java.lang.String, java.lang.Object, java.lang.Object, java.lang.Object,
778      * java.lang.String)
779      */
780     @Override
781     public Object createEdge(Object parent, String id, Object value, Object source, Object target, String style) {
782         Object ret = null;
783         JavaController controller = new JavaController();
784
785         if (source instanceof BasicPort) {
786             BasicPort src = (BasicPort) source;
787             BasicLink link = null;
788
789             long uid = controller.createObject(Kind.LINK);
790             if (src.getType() == Type.EXPLICIT) {
791                 link = new ExplicitLink(controller, uid, Kind.LINK, value, null, style, id);
792             } else if (src.getType() == Type.IMPLICIT) {
793                 link = new ImplicitLink(controller, uid, Kind.LINK, value, null, style, id);
794             } else {
795                 link = new CommandControlLink(controller, uid, Kind.LINK, value, null, style, id);
796             }
797
798             // allocate the associated geometry
799             link.setGeometry(new mxGeometry());
800             ret = link;
801         } else if (source instanceof SplitBlock) {
802             SplitBlock src = (SplitBlock) source;
803             return createEdge(parent, id, value, src.getIn(), target, style);
804         } else if (source instanceof BasicLink) {
805             BasicLink src = (BasicLink) source;
806             BasicLink link = null;
807
808             try {
809                 Class<? extends BasicLink> klass = src.getClass();
810
811                 // call XXXXLink(JavaController controller, long uid, Kind kind, Object value, mxGeometry geometry, String style, String id)
812                 Constructor<? extends BasicLink> cstr = klass.getConstructor(JavaController.class, Long.TYPE, Kind.class, Object.class, mxGeometry.class, String.class, String.class);
813                 link = cstr.newInstance(controller, controller.createObject(Kind.LINK), src.getKind(), null, new mxGeometry(), src.getStyle(), new UID().toString());
814             } catch (ReflectiveOperationException e) {
815                 LOG.severe(e.toString());
816             }
817
818             ret = link;
819         }
820
821         if (ret == null) {
822             LOG.warning("Unable to create an edge");
823         }
824
825         return ret;
826     }
827
828     /**
829      * Add an edge from a source to the target.
830      *
831      * @param cell the edge to add (may be null)
832      * @param parent the parent of the source and the target
833      * @param source the source cell
834      * @param target the target cell
835      * @param index the index of the edge
836      * @return the added edge or null.
837      * @see com.mxgraph.view.mxGraph#addEdge(java.lang.Object, java.lang.Object,
838      * java.lang.Object, java.lang.Object, java.lang.Integer)
839      */
840     @Override
841     public Object addCell(Object cell, Object parent, Integer index, Object source, Object target) {
842
843         // already connected edge or normal block
844         if (source == null && target == null) {
845             return super.addCell(cell, parent, index, source, target);
846         }
847
848         // Command -> Control
849         if (source instanceof CommandPort && target instanceof ControlPort && cell instanceof CommandControlLink) {
850             return super.addCell(cell, parent, index, source, target);
851         }
852
853         // Control -> Command
854         // Switch source and target !
855         if (target instanceof CommandPort && source instanceof ControlPort && cell instanceof CommandControlLink) {
856             BasicLink current = (BasicLink) cell;
857             current.invertDirection();
858
859             return super.addCell(cell, parent, index, target, source);
860         }
861
862         // ExplicitOutput -> ExplicitInput
863         if (source instanceof ExplicitOutputPort && target instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
864             return super.addCell(cell, parent, index, source, target);
865         }
866         // ExplicitInput -> ExplicitOutput
867         // Switch source and target !
868         if (target instanceof ExplicitOutputPort && source instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
869             BasicLink current = (BasicLink) cell;
870             current.invertDirection();
871
872             return super.addCell(cell, parent, index, target, source);
873         }
874
875         // ImplicitOutput -> ImplicitInput
876         if (source instanceof ImplicitOutputPort && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
877             return super.addCell(cell, parent, index, source, target);
878         }
879         // ImplicitInput -> ImplicitOutput
880         // Switch source and target !
881         if (target instanceof ImplicitOutputPort && source instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
882             BasicLink current = (BasicLink) cell;
883             current.invertDirection();
884
885             return super.addCell(cell, parent, index, target, source);
886         }
887
888         // ImplicitInput -> ImplicitInput
889         if (source instanceof ImplicitInputPort && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
890             return super.addCell(cell, parent, index, source, target);
891         }
892         // ImplicitOutputPort -> ImplicitOutput
893         // Switch source and target !
894         if (target instanceof ImplicitOutputPort && source instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
895             BasicLink current = (BasicLink) cell;
896             current.invertDirection();
897
898             return super.addCell(cell, parent, index, target, source);
899         }
900
901         /*
902          * Split management
903          */
904         // ExplicitLink -> ExplicitInputPort
905         if (source instanceof ExplicitLink && target instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
906             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
907             return addCell(cell, parent, index, split.getOut2(), target);
908         }
909         // ExplicitOutput -> ExpliciLink
910         // Switch source and target !
911         if (target instanceof ExplicitLink && source instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
912             final BasicLink current = (BasicLink) cell;
913             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
914
915             current.invertDirection();
916
917             return addCell(cell, parent, index, split.getOut2(), source);
918         }
919
920         // ImplicitLink -> ImplicitInputPort
921         if (source instanceof ImplicitLink && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
922             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
923             return addCell(cell, parent, index, split.getOut2(), target);
924         }
925         // ImplicitInputPort -> ImplicitLink
926         // Switch source and target !
927         if (target instanceof ImplicitLink && source instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
928             final BasicLink current = (BasicLink) cell;
929             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
930
931             current.invertDirection();
932
933             return addCell(cell, parent, index, split.getOut2(), source);
934         }
935
936         // ImplicitLink -> ImplicitOutputPort
937         if (source instanceof ImplicitLink && target instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
938             final BasicLink current = (BasicLink) cell;
939             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) source);
940             return addCell(cell, parent, index, split.getOut2(), source);
941         }
942         // ImplicitOutputPort -> ImplicitLink
943         // Switch source and target !
944         if (target instanceof ImplicitLink && source instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
945             final BasicLink current = (BasicLink) cell;
946             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (ImplicitLink) target);
947             return addCell(cell, parent, index, split.getOut2(), source);
948         }
949
950         // CommandControlLink -> ControlPort
951         if (source instanceof CommandControlLink && target instanceof ControlPort && cell instanceof CommandControlLink) {
952             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
953             return addCell(cell, parent, index, split.getOut2(), target);
954         }
955         // ControlPort -> CommandControlLink
956         // Switch source and target !
957         if (target instanceof CommandControlLink && source instanceof ControlPort && cell instanceof CommandControlLink) {
958             final BasicLink current = (BasicLink) cell;
959             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
960
961             current.invertDirection();
962
963             return addCell(cell, parent, index, split.getOut2(), source);
964         }
965
966         if (cell instanceof BasicLink && source != null && target != null) {
967             LOG.severe("Unable to add a typed link");
968             return null;
969         } else {
970             LOG.severe("Adding an untyped edge");
971             return super.addCell(cell, parent, index, source, target);
972         }
973     }
974
975     /**
976      * Add a split on a edge.
977      *
978      * @param splitPoint the split point (center of the split block)
979      * @param link source link
980      * @return split block
981      */
982     public SplitBlock addSplitEdge(final mxPoint splitPoint, final BasicLink link) {
983         final BasicPort linkSource = (BasicPort) link.getSource();
984         final BasicPort linkTarget = (BasicPort) link.getTarget();
985
986         /*
987          * Select the right split accordingly to the link klass
988          */
989         BlockInterFunction f;
990         if (link instanceof CommandControlLink) {
991             f = BlockInterFunction.CLKSPLIT_f;
992         } else if (link instanceof ImplicitLink) {
993             f = BlockInterFunction.IMPSPLIT_f;
994         } else {
995             f = BlockInterFunction.SPLIT_f;
996         }
997
998         final SplitBlock splitBlock;
999         try {
1000             splitBlock = (SplitBlock) XcosCellFactory.createBlock(f);
1001         } catch (InterpreterException ex) {
1002             // something goes wrong
1003             throw new RuntimeException(ex);
1004         }
1005
1006         // snap the center of the split block on the grid
1007         mxGeometry geom = splitBlock.getGeometry();
1008         double x = snap(splitPoint.getX()) - (SplitBlock.DEFAULT_SIZE / 2.);
1009         double y = snap(splitPoint.getY()) - (SplitBlock.DEFAULT_SIZE / 2.);
1010         geom.setX(x);
1011         geom.setY(y);
1012         splitBlock.setGeometry(geom);
1013
1014         getModel().beginUpdate();
1015         try {
1016             // Origin of the parent, (0,0) as default may be different in case
1017             mxPoint orig = link.getParent().getGeometry();
1018             if (orig == null) {
1019                 orig = new mxPoint();
1020             }
1021
1022             addCell(splitBlock);
1023
1024             // Update old link
1025             // get breaking segment and related point
1026             mxPoint splitTr = new mxPoint(splitPoint.getX() - orig.getX(), splitPoint.getY() - orig.getY());
1027             final int pos = link.findNearestSegment(splitTr);
1028
1029             // save points after breaking point
1030             final List<mxPoint> saveStartPoints = link.getPoints(pos, true);
1031             final List<mxPoint> saveEndPoints = link.getPoints(pos, false);
1032
1033             // remove the first end point if the position is near the split
1034             // position
1035             if (saveEndPoints.size() > 0) {
1036                 final mxPoint p = saveEndPoints.get(0);
1037                 final double dx = p.getX() - splitTr.getX();
1038                 final double dy = p.getY() - splitTr.getY();
1039
1040                 if (!getAsComponent().isSignificant(dx, dy)) {
1041                     saveEndPoints.remove(0);
1042                 }
1043             }
1044
1045             getModel().remove(link);
1046             connect(linkSource, splitBlock.getIn(), saveStartPoints, orig);
1047             connect(splitBlock.getOut1(), linkTarget, saveEndPoints, orig);
1048
1049             refresh();
1050         } finally {
1051             getModel().endUpdate();
1052         }
1053
1054         return splitBlock;
1055     }
1056
1057     /**
1058      * Connect two port together with the associated points.
1059      *
1060      * This method perform the connection in two step in order to generate the
1061      * right UndoableChangeEdits.
1062      *
1063      * @param src the source port
1064      * @param trg the target port
1065      * @param points the points
1066      * @param orig the origin point (may be (0,0))
1067      */
1068     public void connect(BasicPort src, BasicPort trg, List<mxPoint> points, mxPoint orig) {
1069         mxGeometry geometry;
1070
1071         /*
1072          * Add the link with a default geometry
1073          */
1074         final Object newLink1 = createEdge(null, null, null, src, trg, null);
1075         addCell(newLink1, null, null, src, trg);
1076         geometry = getModel().getGeometry(newLink1);
1077         if (getModel().getParent(newLink1) instanceof BasicBlock) {
1078             // on a loop link, translate the points as the cell has been moved to the parent
1079             orig.setX(orig.getX() + geometry.getX());
1080             orig.setY(orig.getY() + geometry.getY());
1081         }
1082         geometry.setPoints(points);
1083         getModel().setGeometry(newLink1, geometry);
1084
1085         /*
1086          * Update the geometry
1087          */
1088         // should be cloned to generate an event
1089         geometry = (mxGeometry) getModel().getGeometry(newLink1).clone();
1090         final double dx = orig.getX();
1091         final double dy = orig.getY();
1092
1093         geometry.translate(dx, dy);
1094         getModel().setGeometry(newLink1, geometry);
1095     }
1096
1097     /**
1098      * Initialize component settings for a graph.
1099      *
1100      * This method *must* be used to setup the component after any
1101      * reassociation.
1102      */
1103     public final void initComponent() {
1104         getAsComponent().setToolTips(true);
1105
1106         // This enable stop editing cells when pressing Enter.
1107         getAsComponent().setEnterStopsCellEditing(false);
1108
1109         getAsComponent().setTolerance(1);
1110
1111         getAsComponent().getViewport().setOpaque(false);
1112
1113         getAsComponent().setBackground(XcosOptions.getEdition().getGraphBackground());
1114
1115         final boolean gridEnable = XcosOptions.getEdition().isGraphGridEnable();
1116         setGridVisible(gridEnable);
1117         if (gridEnable) {
1118             setGridSize(XcosOptions.getEdition().getGraphGrid());
1119         }
1120     }
1121
1122     /**
1123      * Install the multiplicities (use for link checking)
1124      */
1125     private void setMultiplicities() {
1126         final List<mxMultiplicity> multiplicities = new ArrayList<mxMultiplicity>();
1127
1128         // Input data port
1129         multiplicities.add(new PortCheck(ExplicitInputPort.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1130             private static final long serialVersionUID = -4987163442006736665L;
1131
1132             {
1133                 add(ExplicitOutputPort.class);
1134                 add(ExplicitLink.class);
1135             }
1136         }), XcosMessages.LINK_ERROR_EXPLICIT_IN));
1137         multiplicities.add(new PortCheck(ImplicitInputPort.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1138             private static final long serialVersionUID = 886376532181210926L;
1139
1140             {
1141                 add(ImplicitOutputPort.class);
1142                 add(ImplicitInputPort.class);
1143                 add(ImplicitLink.class);
1144             }
1145         }), XcosMessages.LINK_ERROR_IMPLICIT_IN));
1146
1147         // Output data port
1148         multiplicities.add(new PortCheck(ExplicitOutputPort.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1149             private static final long serialVersionUID = 4594127972486054821L;
1150
1151             {
1152                 add(ExplicitInputPort.class);
1153             }
1154         }), XcosMessages.LINK_ERROR_EXPLICIT_OUT));
1155         multiplicities.add(new PortCheck(ImplicitOutputPort.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1156             private static final long serialVersionUID = -3719677806532507973L;
1157
1158             {
1159                 add(ImplicitInputPort.class);
1160                 add(ImplicitOutputPort.class);
1161                 add(ImplicitLink.class);
1162             }
1163         }), XcosMessages.LINK_ERROR_IMPLICIT_OUT));
1164
1165         // Control port
1166         multiplicities.add(new PortCheck(ControlPort.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1167             private static final long serialVersionUID = 2941077191386058497L;
1168
1169             {
1170                 add(CommandPort.class);
1171                 add(CommandControlLink.class);
1172             }
1173         }), XcosMessages.LINK_ERROR_EVENT_IN));
1174
1175         // Command port
1176         multiplicities.add(new PortCheck(CommandPort.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1177             private static final long serialVersionUID = -3470370027962480362L;
1178
1179             {
1180                 add(ControlPort.class);
1181             }
1182         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1183
1184         // ExplicitLink connections
1185         multiplicities.add(new PortCheck(ExplicitLink.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1186             private static final long serialVersionUID = 7423543162930147373L;
1187
1188             {
1189                 add(ExplicitInputPort.class);
1190             }
1191         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1192
1193         // ImplicitLink connections
1194         multiplicities.add(new PortCheck(ImplicitLink.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1195             private static final long serialVersionUID = 7775100011122283282L;
1196
1197             {
1198                 add(ImplicitInputPort.class);
1199                 add(ImplicitOutputPort.class);
1200             }
1201         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1202
1203         // CommandControlLink connections
1204         multiplicities.add(new PortCheck(CommandControlLink.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1205             private static final long serialVersionUID = 3260421433507192386L;
1206
1207             {
1208                 add(ControlPort.class);
1209             }
1210         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1211
1212         // Already connected port
1213         multiplicities.add(new PortCheck(BasicPort.class, Collections.unmodifiableList(new ArrayList<Class<? extends mxCell>>() {
1214             private static final long serialVersionUID = 6376349598052836660L;
1215
1216             {
1217                 add(BasicPort.class);
1218             }
1219         }), XcosMessages.LINK_ERROR_ALREADY_CONNECTED));
1220
1221         setMultiplicities(multiplicities.toArray(new mxMultiplicity[multiplicities.size()]));
1222     }
1223
1224     /**
1225      * Install all needed Listeners.
1226      */
1227     public void installListeners() {
1228         /*
1229          * First remove all listeners if present
1230          */
1231         getModel().removeListener(SavedStatusTracker.getInstance(this));
1232         removeListener(UpdateSuperblockPortsTracker.getInstance());
1233         removeListener(RefreshBlockTracker.getInstance());
1234         removeListener(RepositionTracker.getInstance());
1235
1236         // Track when resizing or moving (droping) a cell.
1237         addListener(mxEvent.CELLS_RESIZED, RepositionTracker.getInstance());
1238         addListener(mxEvent.CELLS_MOVED, RepositionTracker.getInstance());
1239
1240         // Refresh block rendering on update
1241         addListener(XcosEvent.UPDATE_BLOCK, RefreshBlockTracker.getInstance());
1242
1243         // on Superblock diagram, refresh port interface on the parent diagram
1244         if (getKind() == Kind.BLOCK) {
1245             addListener(XcosEvent.UPDATE_BLOCK, UpdateSuperblockPortsTracker.getInstance());
1246
1247             addListener(mxEvent.ADD_CELLS, UpdateSuperblockPortsTracker.getInstance());
1248             addListener(mxEvent.REMOVE_CELLS, UpdateSuperblockPortsTracker.getInstance());
1249             addListener(mxEvent.MOVE_CELLS, UpdateSuperblockPortsTracker.getInstance());
1250         }
1251
1252         // Update the saved status rendering on modification
1253         getModel().addListener(mxEvent.CHANGE, SavedStatusTracker.getInstance(this));
1254     }
1255
1256     /**
1257      * Translate the cell and align any split block
1258      *
1259      * @param cell any object
1260      * @param dx the X delta
1261      * @param dy the Y delta
1262      */
1263     @Override
1264     public void translateCell(Object cell, double dx, double dy) {
1265         if (cell instanceof SplitBlock) {
1266             mxGeometry geom = model.getGeometry(cell);
1267
1268             final double posX = snap(geom.getX() + dx) - (SplitBlock.DEFAULT_SIZE / 2.);
1269             final double posY = snap(geom.getY() + dy) - (SplitBlock.DEFAULT_SIZE / 2.);
1270
1271             dx = posX - geom.getX();
1272             dy = posY - geom.getY();
1273         }
1274
1275         super.translateCell(cell, dx, dy);
1276     }
1277
1278     /**
1279      * Removes the given cells from the graph including all connected edges if
1280      * includeEdges is true. The change is carried out using cellsRemoved.
1281      *
1282      * @param cells the cells to be removed
1283      * @param includeEdges true if the edges must be removed, false otherwise.
1284      * @return the deleted cells
1285      * @see com.mxgraph.view.mxGraph#removeCells(java.lang.Object[], boolean)
1286      */
1287     @Override
1288     public Object[] removeCells(final Object[] cells, final boolean includeEdges) {
1289         if (cells == null || cells.length == 0) {
1290             return super.removeCells(cells, includeEdges);
1291         }
1292
1293         /*
1294          * First remove all links connected to a removed Split if applicable
1295          */
1296         final Object[] initialCells;
1297         if (includeEdges) {
1298             initialCells = addAllEdges(cells);
1299         } else {
1300             initialCells = cells;
1301         }
1302
1303         // stash used on the loop
1304         final Queue<Object> loopCells = new LinkedList<Object>(Arrays.asList(initialCells));
1305         // the cells that need to be really
1306         final Set<Object> removedCells = new HashSet<Object>(loopCells);
1307         // couple of cells to reconnect
1308         final List<BasicPort[]> connectedCells = new ArrayList<BasicPort[]>();
1309         final List<List<mxPoint>> connectedPoints = new ArrayList<List<mxPoint>>();
1310
1311         /*
1312          * Then loop on the algorithm to select the right edges
1313          */
1314         // /!\ not bounded algorithm
1315         while (loopCells.size() > 0) {
1316             Object cell = loopCells.remove();
1317
1318             if (cell instanceof BasicLink) {
1319                 /*
1320                  * Continue on non fully connected links
1321                  */
1322                 if (((BasicLink) cell).getSource() == null) {
1323                     continue;
1324                 }
1325                 if (((BasicLink) cell).getTarget() == null) {
1326                     continue;
1327                 }
1328
1329                 /*
1330                  * Add any split to a link
1331                  */
1332                 addTerminalParent(((BasicLink) cell).getSource(), removedCells, loopCells);
1333                 addTerminalParent(((BasicLink) cell).getTarget(), removedCells, loopCells);
1334
1335             } else if (cell instanceof SplitBlock) {
1336                 final SplitBlock splitBlock = (SplitBlock) cell;
1337
1338                 /*
1339                  * Remove related connection or not and reconnect.
1340                  */
1341                 if (splitBlock.getIn().getEdgeCount() == 0 || splitBlock.getOut1().getEdgeCount() == 0 || splitBlock.getOut2().getEdgeCount() == 0) {
1342                     // corner case, all links will be removed
1343                     continue;
1344                 }
1345
1346                 final mxICell inLink = splitBlock.getIn().getEdgeAt(0);
1347                 final mxICell out1Link = splitBlock.getOut1().getEdgeAt(0);
1348                 final mxICell out2Link = splitBlock.getOut2().getEdgeAt(0);
1349
1350                 final boolean inRemoved = removedCells.contains(inLink);
1351                 final boolean out1Removed = removedCells.contains(out1Link);
1352                 final boolean out2Removed = removedCells.contains(out2Link);
1353
1354                 /*
1355                  * Explicit case, if the in link is deleted; all the out links also should.
1356                  */
1357                 if (inLink instanceof ExplicitLink && inRemoved) {
1358                     if (removedCells.add(out1Link)) {
1359                         loopCells.add(out1Link);
1360                     }
1361
1362                     if (removedCells.add(out2Link)) {
1363                         loopCells.add(out2Link);
1364                     }
1365                 }
1366
1367                 /*
1368                  * Global case reconnect if not removed
1369                  */
1370                 final BasicPort[] connection;
1371                 List<mxPoint> points = null;
1372                 if (!inRemoved && !out1Removed && out2Removed) {
1373                     connection = findTerminals(inLink, out1Link, removedCells);
1374                     points = getDirectPoints(splitBlock, inLink, out1Link);
1375                 } else if (!inRemoved && out1Removed && !out2Removed) {
1376                     connection = findTerminals(inLink, out2Link, removedCells);
1377                     points = getDirectPoints(splitBlock, inLink, out2Link);
1378                 } else if (inRemoved && !out1Removed && !out2Removed) {
1379                     // only implicit or event case, log otherwise
1380                     if (out1Link instanceof ExplicitLink || out2Link instanceof ExplicitLink) {
1381                         LOG.severe("Reconnection failed for explicit links");
1382                         connection = null;
1383                     } else {
1384                         connection = findTerminals(out1Link, out2Link, removedCells);
1385                         points = getDirectPoints(splitBlock, out1Link, out2Link);
1386                     }
1387                 } else {
1388                     connection = null;
1389                 }
1390
1391                 if (connection != null) {
1392                     connectedCells.add(connection);
1393                     connectedPoints.add(points);
1394                 }
1395             }
1396         }
1397
1398         final Object[] ret;
1399         getModel().beginUpdate();
1400         try {
1401             ret = super.removeCells(removedCells.toArray(), includeEdges);
1402             for (int i = 0; i < connectedCells.size(); i++) {
1403                 final BasicPort[] connection = connectedCells.get(i);
1404                 final List<mxPoint> points = connectedPoints.get(i);
1405                 if (!removedCells.contains(connection[0].getParent()) && !removedCells.contains(connection[1].getParent())) {
1406                     connect(connection[0], connection[1], points, new mxPoint());
1407                 }
1408             }
1409         } finally {
1410             getModel().endUpdate();
1411         }
1412         return ret;
1413     }
1414
1415     /**
1416      * Add any terminal parent to the removed cells
1417      *
1418      * @param terminal the current terminal (instance of BasicPort)
1419      * @param removedCells the "to be removed" set
1420      * @param loopCells the "while loop" set
1421      */
1422     private void addTerminalParent(mxICell terminal, Collection<Object> removedCells, Collection<Object> loopCells) {
1423         assert (terminal == null || terminal instanceof BasicPort);
1424         assert (removedCells != null);
1425         assert (loopCells != null);
1426
1427         // getting terminal parent
1428         mxICell target = null;
1429         if (terminal != null) {
1430             target = terminal.getParent();
1431         } else {
1432             target = null;
1433         }
1434
1435         // add target if applicable
1436         if (target instanceof SplitBlock) {
1437             if (removedCells.add(target)) {
1438                 loopCells.add(target);
1439             }
1440         }
1441     }
1442
1443     /**
1444      * Find the terminals when relinking the 2 links
1445      *
1446      * This method ensure that {source, target} are not child of removed blocks.
1447      *
1448      * @param linkSource the normal source link
1449      * @param linkTerminal the normal target link
1450      * @param removedCells the set of removed objects
1451      * @return the {source, target} connection
1452      */
1453     private BasicPort[] findTerminals(final mxICell linkSource, final mxICell linkTerminal, final Set<Object> removedCells) {
1454         BasicPort src = (BasicPort) linkSource.getTerminal(true);
1455         BasicPort tgt = (BasicPort) linkTerminal.getTerminal(false);
1456         if (linkSource instanceof ImplicitLink) {
1457             if (removedCells.contains(src.getParent())) {
1458                 src = (BasicPort) linkSource.getTerminal(false);
1459             }
1460             if (removedCells.contains(tgt.getParent())) {
1461                 tgt = (BasicPort) linkTerminal.getTerminal(true);
1462             }
1463         }
1464
1465         return new BasicPort[] {src, tgt};
1466     }
1467
1468     /**
1469      * Get the direct points from inLink.getSource() to outLink.getTarget().
1470      *
1471      * @param splitBlock the current splitblock (added as a mid-point)
1472      * @param inLink the link before the split
1473      * @param outLink the link after the split
1474      * @return the points
1475      */
1476     private List<mxPoint> getDirectPoints(final SplitBlock splitBlock, final mxICell inLink, final mxICell outLink) {
1477         List<mxPoint> points;
1478         // add the points before the split
1479         points = new ArrayList<mxPoint>();
1480         if (inLink.getGeometry().getPoints() != null) {
1481             points.addAll(inLink.getGeometry().getPoints());
1482         }
1483
1484         // add a new point at the split location
1485         points.add(new mxPoint(snap(splitBlock.getGeometry().getCenterX()), snap(splitBlock.getGeometry().getCenterY())));
1486
1487         // add the points after the split
1488         if (outLink.getGeometry().getPoints() != null) {
1489             points.addAll(outLink.getGeometry().getPoints());
1490         }
1491
1492         return points;
1493     }
1494
1495     /**
1496      * Manage Group to be CellFoldable i.e with a (-) to reduce and a (+) to
1497      * expand them. Labels (mxCell instance with value) should not have a
1498      * visible foldable sign.
1499      *
1500      * @param cell the selected cell
1501      * @param collapse the collapse settings
1502      * @return always <code>false</code>
1503      * @see com.mxgraph.view.mxGraph#isCellFoldable(java.lang.Object, boolean)
1504      */
1505     @Override
1506     public boolean isCellFoldable(final Object cell, final boolean collapse) {
1507         return false;
1508     }
1509
1510     /**
1511      * Not BasicBLock cell have a moveable label.
1512      *
1513      * @param cell the cell
1514      * @return true if the corresponding label is moveable
1515      * @see com.mxgraph.view.mxGraph#isLabelMovable(java.lang.Object)
1516      */
1517     @Override
1518     public boolean isLabelMovable(Object cell) {
1519         return !(cell instanceof BasicBlock);
1520     }
1521
1522     /**
1523      * Return true if selectable
1524      *
1525      * @param cell the cell
1526      * @return status
1527      * @see com.mxgraph.view.mxGraph#isCellSelectable(java.lang.Object)
1528      */
1529     @Override
1530     public boolean isCellSelectable(final Object cell) {
1531         if (cell instanceof BasicPort) {
1532             return false;
1533         }
1534         return super.isCellSelectable(cell);
1535     }
1536
1537     /**
1538      * Return true if movable
1539      *
1540      * @param cell the cell
1541      * @return status
1542      * @see com.mxgraph.view.mxGraph#isCellMovable(java.lang.Object)
1543      */
1544     @Override
1545     public boolean isCellMovable(final Object cell) {
1546         if (cell instanceof BasicPort) {
1547             return false;
1548         }
1549
1550         boolean movable = false;
1551         final Object[] cells = getSelectionCells();
1552
1553         // don't move if selection is only links
1554         for (Object c : cells) {
1555             if (!(c instanceof BasicLink)) {
1556                 movable = true;
1557                 break;
1558             }
1559         }
1560
1561         return movable && super.isCellMovable(cell);
1562     }
1563
1564     /**
1565      * Return true if resizable
1566      *
1567      * @param cell the cell
1568      * @return status
1569      * @see com.mxgraph.view.mxGraph#isCellResizable(java.lang.Object)
1570      */
1571     @Override
1572     public boolean isCellResizable(final Object cell) {
1573         if (cell instanceof SplitBlock) {
1574             return false;
1575         }
1576         return (cell instanceof BasicBlock) && super.isCellResizable(cell);
1577     }
1578
1579     /**
1580      * A cell is deletable if it is not a locked block or an identifier cell
1581      *
1582      * @param cell the cell
1583      * @return status
1584      * @see com.mxgraph.view.mxGraph#isCellDeletable(java.lang.Object)
1585      */
1586     @Override
1587     public boolean isCellDeletable(final Object cell) {
1588         final boolean isALockedBLock = cell instanceof BasicBlock && ((BasicBlock) cell).isLocked();
1589         final boolean isAnIdentifier = cell.getClass().equals(mxCell.class);
1590
1591         if (isALockedBLock) {
1592             return false;
1593         }
1594         if (isAnIdentifier) {
1595             return true;
1596         }
1597
1598         return super.isCellDeletable(cell);
1599     }
1600
1601     /**
1602      * Return true if editable
1603      *
1604      * @param cell the cell
1605      * @return status
1606      * @see com.mxgraph.view.mxGraph#isCellEditable(java.lang.Object)
1607      */
1608     @Override
1609     public boolean isCellEditable(final Object cell) {
1610         return (cell instanceof TextBlock) && super.isCellDeletable(cell);
1611     }
1612
1613     /**
1614      * Return or create the identifier for the cell
1615      *
1616      * @param cell the cell to check
1617      * @return the identifier cell
1618      */
1619     public mxCell getOrCreateCellIdentifier(final mxCell cell) {
1620         if (cell.getId().endsWith(HASH_IDENTIFIER)) {
1621             return cell;
1622         }
1623
1624         final mxGraphModel graphModel = (mxGraphModel) getModel();
1625
1626         final mxCell identifier;
1627         final String cellId = cell.getId() + HASH_IDENTIFIER;
1628         if (graphModel.getCell(cellId) == null) {
1629             // create the identifier
1630             identifier = createCellIdentifier(cell);
1631
1632             // add the identifier
1633             graphModel.add(cell, identifier, cell.getChildCount());
1634         } else {
1635             identifier = (mxCell) graphModel.getCell(cellId);
1636         }
1637         return identifier;
1638     }
1639
1640     /**
1641      * Return the identifier for the cell
1642      *
1643      * @param cell the cell to check
1644      * @return the identifier cell
1645      */
1646     public mxCell getCellIdentifier(final mxCell cell) {
1647         final mxGraphModel graphModel = (mxGraphModel) getModel();
1648         final String cellId = cell.getId() + HASH_IDENTIFIER;
1649
1650         return (mxCell) graphModel.getCell(cellId);
1651     }
1652
1653     /**
1654      * Create a cell identifier for a specific cell
1655      *
1656      * @param cell the cell
1657      * @return the cell identifier.
1658      */
1659     public mxCell createCellIdentifier(final mxCell cell) {
1660         final mxCell identifier;
1661         final String cellId = cell.getId() + HASH_IDENTIFIER;
1662
1663         identifier = new mxCell(null, (mxGeometry) DEFAULT_LABEL_GEOMETRY.clone(), "noLabel=0;opacity=0;");
1664         identifier.getGeometry().setRelative(true);
1665         identifier.setVertex(true);
1666         identifier.setConnectable(false);
1667         identifier.setId(cellId);
1668
1669         return identifier;
1670     }
1671
1672     /**
1673      * Get the label for the cell according to its style.
1674      *
1675      * @param cell the cell object
1676      * @return a representative the string (block name) or a style specific
1677      * style.
1678      * @see com.mxgraph.view.mxGraph#convertValueToString(java.lang.Object)
1679      */
1680     @Override
1681     public String convertValueToString(Object cell) {
1682         String ret = null;
1683
1684         if (cell != null) {
1685             JavaController controller = new JavaController();
1686             final Map<String, Object> style = getCellStyle(cell);
1687
1688             final String displayedLabel = (String) style.get("displayedLabel");
1689             if (displayedLabel != null) {
1690                 if (cell instanceof BasicBlock) {
1691                     BasicBlock block = (BasicBlock) cell;
1692                     VectorOfDouble v = new VectorOfDouble();
1693                     controller.getObjectProperty(block.getUID(), block.getKind(), ObjectProperties.EXPRS, v);
1694
1695                     try {
1696                         ret = new ScilabTypeCoder().format(displayedLabel, v);
1697                     } catch (IllegalFormatException e) {
1698                         LOG.severe(e.toString());
1699                         ret = displayedLabel;
1700                     }
1701                 } else {
1702                     ret = displayedLabel;
1703                 }
1704             } else {
1705                 final String label = super.convertValueToString(cell);
1706                 if (label.isEmpty() && cell instanceof BasicBlock) {
1707                     BasicBlock block = (BasicBlock) cell;
1708
1709                     String[] interfaceFunction = new String[1];
1710                     controller.getObjectProperty(block.getUID(), block.getKind(), ObjectProperties.INTERFACE_FUNCTION, interfaceFunction);
1711                     ret = interfaceFunction[0];
1712                 } else {
1713                     ret = label;
1714                 }
1715             }
1716         }
1717
1718         return ret;
1719     }
1720
1721     /**
1722      * Return true if auto sized
1723      *
1724      * @param cell the cell
1725      * @return status
1726      * @see com.mxgraph.view.mxGraph#isAutoSizeCell(java.lang.Object)
1727      */
1728     @Override
1729     public boolean isAutoSizeCell(final Object cell) {
1730         boolean status = super.isAutoSizeCell(cell);
1731
1732         if (cell instanceof AfficheBlock) {
1733             status |= true;
1734         }
1735
1736         if (cell instanceof TextBlock) {
1737             status &= false;
1738         }
1739
1740         return status;
1741     }
1742
1743     /**
1744      * {@inheritDoc} Do not extends if the port position is north or south.
1745      */
1746     @Override
1747     public boolean isExtendParent(Object cell) {
1748         final boolean extendsParents;
1749
1750         if (cell instanceof BasicPort) {
1751             final BasicPort p = (BasicPort) cell;
1752             extendsParents = !(p.getOrientation() == Orientation.NORTH || p.getOrientation() == Orientation.SOUTH) && super.isExtendParent(p);
1753         } else {
1754             extendsParents = super.isExtendParent(cell);
1755         }
1756         return extendsParents;
1757     }
1758
1759     /**
1760      * @return the model ID
1761      */
1762     public long getUID() {
1763         return ((XcosCell) getDefaultParent()).getUID();
1764     }
1765
1766     /**
1767      * @return Kind.DIAGRAM or Kind.BLOCK
1768      */
1769     public Kind getKind() {
1770         return ((XcosCell) getDefaultParent()).getKind();
1771     }
1772
1773     /**
1774      * Manage the visibility of the grid and the associated menu
1775      *
1776      * @param status new status
1777      */
1778     public void setGridVisible(final boolean status) {
1779         setGridEnabled(status);
1780         getAsComponent().setGridVisible(status);
1781         getAsComponent().repaint();
1782     }
1783
1784     /**
1785      * @return save status
1786      */
1787     public boolean saveDiagram() {
1788         return saveDiagramAs(getSavedFile());
1789     }
1790
1791     /**
1792      * @param fileName diagram filename
1793      * @return save status
1794      */
1795     public boolean saveDiagramAs(final File fileName) {
1796         boolean isSuccess = false;
1797         File writeFile = fileName;
1798         XcosFileType format = XcosOptions.getPreferences().getFileFormat();
1799
1800         info(XcosMessages.SAVING_DIAGRAM);
1801         if (fileName == null) {
1802             final SwingScilabFileChooser fc = SaveAsAction.createFileChooser();
1803             SaveAsAction.configureFileFilters(fc);
1804             ConfigurationManager.configureCurrentDirectory(fc);
1805
1806             if (getSavedFile() != null) {
1807                 // using save-as, the file chooser should have a filename
1808                 // without extension as default
1809                 String filename = getSavedFile().getName();
1810                 filename = filename.substring(0, filename.lastIndexOf('.'));
1811                 fc.setSelectedFile(new File(filename));
1812             } else {
1813                 final String title = getTitle();
1814                 if (title != null) {
1815                     /*
1816                      * Escape file to handle not supported character in file name (may be Windows only) see http://msdn.microsoft.com/en -us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
1817                      */
1818                     final char[] regex = "<>:\"/\\|?*".toCharArray();
1819                     String escaped = title;
1820                     for (char c : regex) {
1821                         escaped = escaped.replace(c, '-');
1822                     }
1823
1824                     fc.setSelectedFile(new File(escaped));
1825                 }
1826             }
1827
1828             int status = fc.showSaveDialog(this.getAsComponent());
1829             if (status != JFileChooser.APPROVE_OPTION) {
1830                 info(XcosMessages.EMPTY_INFO);
1831                 return isSuccess;
1832             }
1833
1834             // change the format if the user choose a save-able file format
1835             final XcosFileType selectedFilter = XcosFileType.findFileType(fc.getFileFilter());
1836             if (XcosFileType.getAvailableSaveFormats().contains(selectedFilter)) {
1837                 format = selectedFilter;
1838             }
1839             writeFile = fc.getSelectedFile();
1840         }
1841
1842         /* Extension/format update */
1843         // using a String filename also works on a non-existing file
1844         final String filename = writeFile.getName();
1845
1846         /*
1847          * Look for the user extension if it does not exists, append a default one
1848          *
1849          * if the specified extension is handled, update the save format ; else append a default extension and use the default format
1850          */
1851         XcosFileType userExtension = XcosFileType.findFileType(filename);
1852         if (userExtension == null) {
1853             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
1854             userExtension = format;
1855         }
1856         if (XcosFileType.getAvailableSaveFormats().contains(userExtension)) {
1857             format = userExtension;
1858         } else {
1859             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
1860         }
1861
1862         /*
1863          * If the file exists, ask for confirmation if this is not the previously saved file
1864          */
1865         if (writeFile.exists() && !writeFile.equals(getSavedFile())) {
1866             final boolean overwrite = ScilabModalDialog.show(XcosTab.get(this), XcosMessages.OVERWRITE_EXISTING_FILE, XcosMessages.XCOS, IconType.QUESTION_ICON,
1867                                       ButtonType.YES_NO) == AnswerOption.YES_OPTION;
1868
1869             if (!overwrite) {
1870                 info(XcosMessages.EMPTY_INFO);
1871                 return false;
1872             }
1873         }
1874
1875         /*
1876          * Really save the data
1877          */
1878         try {
1879             format.save(writeFile.getCanonicalPath(), getRootDiagram());
1880             setSavedFile(writeFile);
1881
1882             setTitle(writeFile.getName().substring(0, writeFile.getName().lastIndexOf('.')));
1883             ConfigurationManager.getInstance().addToRecentFiles(writeFile);
1884
1885             ScicosObjectOwner root = Xcos.findRoot(this);
1886             Xcos.getInstance().setModified(root, false);
1887             Xcos.getInstance().openedDiagrams(root).stream()
1888             .forEach(d -> d.updateTabTitle());
1889
1890             isSuccess = true;
1891         } catch (final Exception e) {
1892             LOG.severe(e.toString());
1893
1894             XcosDialogs.couldNotSaveFile(this);
1895         }
1896
1897         updateTabTitle();
1898         info(XcosMessages.EMPTY_INFO);
1899         return isSuccess;
1900     }
1901
1902     /**
1903      * Perform post loading initialization.
1904      *
1905      * @param file the loaded file
1906      */
1907     public void postLoad(final File file) {
1908         final String name = file.getName();
1909
1910         if (XcosFileType.getAvailableSaveFormats().contains(XcosFileType.findFileType(file))) {
1911             setSavedFile(file);
1912         }
1913         setTitle(name.substring(0, name.lastIndexOf('.')));
1914         setModified(false);
1915         updateTabTitle();
1916
1917         fireEvent(new mxEventObject(mxEvent.ROOT));
1918
1919         info(XcosMessages.EMPTY_INFO);
1920     }
1921
1922     @Override
1923     public void setSavedFile(File savedFile) {
1924         super.setSavedFile(savedFile);
1925
1926         if (savedFile != null) {
1927             JavaController controller = new JavaController();
1928             controller.setObjectProperty(getUID(), getKind(), ObjectProperties.PATH, savedFile.getAbsolutePath());
1929         }
1930     }
1931
1932     /**
1933      * Set the title of the diagram
1934      *
1935      * @param title the title
1936      * @see org.scilab.modules.graph.ScilabGraph#setTitle(java.lang.String)
1937      */
1938     @Override
1939     public void setTitle(final String title) {
1940         super.setTitle(title);
1941
1942         JavaController controller = new JavaController();
1943         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.TITLE, title);
1944     }
1945
1946     /**
1947      * Update the title
1948      */
1949     public void updateTabTitle() {
1950         // get the modifier string
1951         final String modified;
1952         if (Xcos.getInstance().isModified(Xcos.findRoot(this))) {
1953             modified = "*";
1954         } else {
1955             modified = "";
1956         }
1957
1958         // get the title string
1959         final String title = getTitle();
1960
1961         // get the path
1962         CharSequence formattedPath = "";
1963         if (getKind() == Kind.DIAGRAM) {
1964             final File savedFile = getSavedFile();
1965             if (savedFile != null) {
1966                 try {
1967                     final String path = savedFile.getCanonicalPath();
1968                     formattedPath = new StringBuilder().append(" (").append(path).append(')');
1969                 } catch (final IOException e) {
1970                     LOG.warning(e.toString());
1971                 }
1972             }
1973         }
1974
1975         // Product name
1976         final String product = Xcos.TRADENAME;
1977
1978         final String tabTitle = new StringBuilder().append(modified).append(title).append(formattedPath).append(" - ").append(product).toString();
1979
1980         final SwingScilabDockablePanel tab = ScilabTabFactory.getInstance().getFromCache(getGraphTab());
1981         if (tab != null) {
1982             tab.setName(tabTitle);
1983         }
1984     }
1985
1986     /**
1987      * Load a file with different method depending on it extension
1988      *
1989      * @param controller the used controller
1990      * @param file File to load (can be null)
1991      */
1992     public void transformAndLoadFile(final JavaController controller, final String file) {
1993         final File f;
1994         final XcosFileType filetype;
1995         if (file != null) {
1996             f = new File(file);
1997             filetype = XcosFileType.findFileType(f);
1998         } else {
1999             f = null;
2000             filetype = null;
2001         }
2002         new SwingWorker<XcosDiagram, ActionEvent>() {
2003             int counter = 0;
2004             final Timer t = new Timer(1000, new ActionListener() {
2005                 @Override
2006                 public void actionPerformed(ActionEvent e) {
2007                     counter = (counter + 1) % (XcosMessages.DOTS.length() + 1);
2008                     String str = XcosMessages.LOADING_DIAGRAM + XcosMessages.DOTS.substring(0, counter);
2009
2010                     XcosDiagram.this.info(str);
2011                 }
2012             });
2013
2014             @Override
2015             protected XcosDiagram doInBackground() {
2016                 t.start();
2017
2018                 final Xcos instance = Xcos.getInstance();
2019                 XcosDiagram diag = XcosDiagram.this;
2020
2021                 diag.setReadOnly(true);
2022
2023                 /*
2024                  * Load, log errors and notify
2025                  */
2026                 synchronized (instance) {
2027                     try {
2028
2029                         if (f != null && filetype != null) {
2030                             filetype.load(file, XcosDiagram.this);
2031                         } else {
2032                             XcosCellFactory.insertChildren(controller, XcosDiagram.this);
2033                         }
2034
2035                         instance.setLastError("");
2036                     } catch (Exception e) {
2037                         e.printStackTrace();
2038
2039                         Throwable ex = e;
2040                         while (ex instanceof RuntimeException) {
2041                             ex = ex.getCause();
2042                         }
2043                         instance.setLastError(ex.getMessage());
2044                     }
2045                     instance.notify();
2046                 }
2047
2048                 return diag;
2049             }
2050
2051             @Override
2052             protected void done() {
2053                 t.stop();
2054                 XcosDiagram.this.setReadOnly(false);
2055                 XcosDiagram.this.getUndoManager().clear();
2056                 XcosDiagram.this.refresh();
2057
2058                 /*
2059                  * Load has finished
2060                  */
2061                 if (f != null && filetype != null) {
2062                     postLoad(f);
2063                 }
2064                 XcosDiagram.this.info(XcosMessages.EMPTY_INFO);
2065             }
2066
2067         } .execute();
2068     }
2069
2070     /**
2071      * Getting the root diagram of a decomposed diagram
2072      *
2073      * @return Root parent of the whole parent
2074      */
2075     public XcosDiagram getRootDiagram() {
2076         if (getKind() == Kind.DIAGRAM) {
2077             return this;
2078         }
2079
2080         JavaController controller = new JavaController();
2081
2082         ScicosObjectOwner root = Xcos.findRoot(controller, this);
2083         Collection<XcosDiagram> diagrams = Xcos.getInstance().getDiagrams(root);
2084         Optional<XcosDiagram> found = diagrams.stream().filter(d -> d.getUID() == root.getUID()).findFirst();
2085         if (found.isPresent()) {
2086             return found.get();
2087         } else {
2088             // create a temporary hidden root diagram
2089             String[] uid = {""};
2090             controller.getObjectProperty(root.getUID(), Kind.DIAGRAM, ObjectProperties.UID, uid);
2091             return new XcosDiagram(controller, root.getUID(), Kind.DIAGRAM, uid[0]);
2092         }
2093
2094
2095     }
2096
2097     /**
2098      * Getting the root diagram UID of a decomposed diagram
2099      *
2100      * @param controller the current JavaController
2101      * @return Root parent of the whole parent
2102      */
2103     public long getRootDiagramUID(JavaController controller) {
2104         if (getKind() == Kind.DIAGRAM) {
2105             return getUID();
2106         }
2107
2108         long[] uid = new long[1];
2109         controller.getObjectProperty(getUID(), getKind(), ObjectProperties.PARENT_DIAGRAM, uid);
2110
2111         return uid[0];
2112     }
2113
2114
2115     /**
2116      * Returns the tooltip to be used for the given cell.
2117      *
2118      * @param cell block
2119      * @return cell tooltip
2120      */
2121     @Override
2122     public String getToolTipForCell(final Object cell) {
2123         if (cell instanceof BasicBlock) {
2124             return getToolTipForCell((BasicBlock) cell);
2125         } else if (cell instanceof BasicPort) {
2126             return getToolTipForCell((BasicPort) cell);
2127         } else if (cell instanceof BasicLink) {
2128             return getToolTipForCell((BasicLink) cell);
2129         }
2130         return "";
2131     }
2132
2133     private String getToolTipForCell(final BasicBlock o) {
2134         JavaController controller = new JavaController();
2135         String[] strValue = {""};
2136         VectorOfDouble vecValue = new VectorOfDouble();
2137         VectorOfInt vecInteger = new VectorOfInt();
2138
2139         StringBuilder result = new StringBuilder();
2140         result.append(ScilabGraphConstants.HTML_BEGIN);
2141
2142         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.INTERFACE_FUNCTION, strValue);
2143         result.append(XcosMessages.TOOLTIP_BLOCK).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2144         .append(strValue[0])
2145         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2146
2147         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.SIM_FUNCTION_NAME, strValue);
2148         result.append(XcosMessages.TOOLTIP_BLOCK_SIMULATION).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2149         .append(strValue[0])
2150         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2151
2152         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.UID, strValue);
2153         result.append(XcosMessages.TOOLTIP_BLOCK_UID).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2154         .append(strValue[0])
2155         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2156
2157         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.STYLE, strValue);
2158         result.append(XcosMessages.TOOLTIP_BLOCK_STYLE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2159         appendReduced(result, strValue[0])
2160         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2161
2162         result.append(ScilabGraphConstants.HTML_NEWLINE);
2163
2164         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.RPAR, vecValue);
2165         result.append(XcosMessages.TOOLTIP_BLOCK_RPAR).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2166         appendReduced(result, ScilabTypeCoder.toString(vecValue))
2167         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2168
2169         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.IPAR, vecInteger);
2170         result.append(XcosMessages.TOOLTIP_BLOCK_IPAR).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2171         appendReduced(result, ScilabTypeCoder.toString(vecInteger))
2172         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2173
2174         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.OPAR, vecValue);
2175         result.append(XcosMessages.TOOLTIP_BLOCK_OPAR).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2176         appendReduced(result, ScilabTypeCoder.toString(vecValue))
2177         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2178
2179         result.append(ScilabGraphConstants.HTML_END);
2180         return result.toString();
2181     }
2182
2183     private String getToolTipForCell(final BasicPort o) {
2184         JavaController controller = new JavaController();
2185         boolean[] boolValue = {false};
2186         String[] strValue = {""};
2187         VectorOfInt intVecValue = new VectorOfInt();
2188
2189         StringBuilder result = new StringBuilder();
2190         result.append(ScilabGraphConstants.HTML_BEGIN);
2191
2192         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.DATATYPE, intVecValue);
2193         result.append(XcosMessages.TOOLTIP_PORT_DATATYPE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2194         formatDatatype(result, intVecValue)
2195         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2196
2197         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.IMPLICIT, boolValue);
2198         result.append(XcosMessages.TOOLTIP_PORT_IMPLICIT).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2199         .append(boolValue[0])
2200         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2201
2202         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.STYLE, strValue);
2203         result.append(XcosMessages.TOOLTIP_PORT_STYLE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2204         appendReduced(result, strValue[0])
2205         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2206
2207         result.append(ScilabGraphConstants.HTML_END);
2208         return result.toString();
2209     }
2210
2211     private String getToolTipForCell(final BasicLink o) {
2212         JavaController controller = new JavaController();
2213         long[] longValue = {0l};
2214         boolean[] boolValue = {false};
2215         String[] strValue = {""};
2216         VectorOfInt intVecValue = new VectorOfInt();
2217
2218         StringBuilder result = new StringBuilder();
2219         result.append(ScilabGraphConstants.HTML_BEGIN);
2220
2221         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.SOURCE_PORT, longValue);
2222         if (longValue[0] != 0l) {
2223             controller.getObjectProperty(longValue[0], Kind.PORT, ObjectProperties.DATATYPE, intVecValue);
2224             result.append(XcosMessages.TOOLTIP_LINK_SRC_DATATYPE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2225             formatDatatype(result, intVecValue)
2226             .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2227         }
2228         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.DESTINATION_PORT, longValue);
2229         if (longValue[0] != 0l) {
2230             controller.getObjectProperty(longValue[0], Kind.PORT, ObjectProperties.DATATYPE, intVecValue);
2231             result.append(XcosMessages.TOOLTIP_LINK_TRG_DATATYPE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2232             formatDatatype(result, intVecValue)
2233             .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2234         }
2235
2236         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.LABEL, strValue);
2237         result.append(XcosMessages.TOOLTIP_LINK_LABEL).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2238         .append(strValue[0])
2239         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2240
2241         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.STYLE, strValue);
2242         result.append(XcosMessages.TOOLTIP_LINK_STYLE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2243         appendReduced(result, strValue[0])
2244         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2245
2246         result.append(ScilabGraphConstants.HTML_END);
2247         return result.toString();
2248     }
2249
2250     private StringBuilder appendReduced(final StringBuilder result, final String msg) {
2251         if (msg.length() > XcosConstants.MAX_CHAR_IN_STYLE) {
2252             result.append(msg.substring(0, XcosConstants.MAX_CHAR_IN_STYLE));
2253             result.append(XcosMessages.DOTS);
2254         } else {
2255             result.append(msg);
2256         }
2257
2258         return result;
2259     }
2260
2261     private StringBuilder formatDatatype(final StringBuilder result, final VectorOfInt intVecValue) {
2262         if (intVecValue.size() != 3) {
2263             result.append(ScilabTypeCoder.toString(intVecValue));
2264         } else {
2265             // this is a known encoding, output representative strings
2266             int rows = intVecValue.get(0);
2267             int cols = intVecValue.get(1);
2268             int type = intVecValue.get(2);
2269
2270             String strType;
2271             // should be similar to the naming used on scicos_model doc
2272             String[] typeTable = {"real", "complex", "int32", "int16", "int8", "uint32", "uint16", "uint8"};
2273             if (0 <= type && type < typeTable.length) {
2274                 strType = typeTable[type - 1];
2275             } else {
2276                 strType = "auto";
2277             }
2278
2279             result.append(String.format("%s [%d %d]", strType, rows, cols));
2280         }
2281
2282         return result;
2283     }
2284
2285     /**
2286      * Display the message in info bar.
2287      *
2288      * @param message Information
2289      */
2290     public void info(final String message) {
2291         final XcosTab tab = XcosTab.get(this);
2292
2293         if (tab != null && tab.getInfoBar() != null) {
2294             tab.getInfoBar().setText(message);
2295         }
2296     }
2297
2298     /**
2299      * Display the message into an error popup
2300      *
2301      * @param message Error of the message
2302      */
2303     public void error(final String message) {
2304         JOptionPane.showMessageDialog(getAsComponent(), message, XcosMessages.XCOS, JOptionPane.ERROR_MESSAGE);
2305     }
2306
2307     /**
2308      * Find the block corresponding to the given uid and display a warning
2309      * message.
2310      *
2311      * @param uid - A String as UID.
2312      * @param message - The message to display.
2313      */
2314     public void warnCellByUID(final String uid, final String message) {
2315         final Object cell = ((mxGraphModel) getModel()).getCell(uid);
2316         if (cell == null) {
2317             return;
2318         }
2319
2320         if (GraphicsEnvironment.isHeadless()) {
2321             System.err.printf("%s at %s\n    %s: %s\n", "warnCell", getRootDiagram().getTitle(), uid, message);
2322             return;
2323         }
2324
2325         // open the tab
2326         if (XcosTab.get(this) == null) {
2327             XcosTab.restore(this);
2328         }
2329
2330         if (message.isEmpty()) {
2331             getAsComponent().clearCellOverlays(cell);
2332         } else {
2333             getAsComponent().setCellWarning(cell, message, null, true);
2334         }
2335     }
2336
2337     @Override
2338     public File getSavedFile() {
2339         if (getKind() == Kind.DIAGRAM) {
2340             return super.getSavedFile();
2341         } else {
2342             return getRootDiagram().getSavedFile();
2343         }
2344     }
2345
2346     /**
2347      * Read the applicable context on this diagram.
2348      * <p>
2349      * This function retrieve the current diagram's context and all its parent
2350      *
2351      * @return the full context
2352      */
2353     public String[] getContext() {
2354         final ArrayList<String> allContext = new ArrayList<>();
2355         final Stack<ScicosObjectOwner> hierarchy = lookForHierarchy(new ScicosObjectOwner(getUID(), getKind()));
2356
2357         final JavaController controller = new JavaController();
2358         final VectorOfString context = new VectorOfString();
2359
2360         hierarchy.stream().forEach(o -> {
2361             controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.DIAGRAM_CONTEXT, context);
2362
2363             final int length = context.size();
2364             for (int i = 0; i < length; i++) {
2365                 allContext.add(context.get(i));
2366             }
2367             allContext.add("");
2368         });
2369
2370         return allContext.toArray(new String[allContext.size()]);
2371     }
2372
2373     /**
2374      * Evaluate the current context
2375      *
2376      * @return The resulting data. Keys are variable names and Values are
2377      * evaluated values.
2378      */
2379     public Map<String, String> evaluateContext() {
2380         Map<String, String> result = Collections.emptyMap();
2381         final ScilabDirectHandler handler = ScilabDirectHandler.acquire();
2382         if (handler == null) {
2383             return result;
2384         }
2385
2386         try {
2387             // first write the context strings
2388             handler.writeContext(getContext());
2389
2390             // evaluate using script2var
2391             ScilabInterpreterManagement.synchronousScilabExec(ScilabDirectHandler.CONTEXT + " = script2var(" + ScilabDirectHandler.CONTEXT + ", struct());");
2392
2393             // read the structure
2394             result = handler.readContext();
2395         } catch (final InterpreterException e) {
2396             info("Unable to evaluate the contexte");
2397             e.printStackTrace();
2398         } finally {
2399             handler.release();
2400         }
2401
2402         return result;
2403     }
2404
2405     /**
2406      * Returns true if the given cell is a not a block nor a port.
2407      *
2408      * @param cell the drop target
2409      * @param cells the cells to be dropped
2410      * @return the drop status
2411      * @see com.mxgraph.view.mxGraph#isValidDropTarget(java.lang.Object,
2412      * java.lang.Object[])
2413      */
2414     @Override
2415     public boolean isValidDropTarget(final Object cell, final Object[] cells) {
2416         return !(cell instanceof BasicBlock) && !(cell instanceof BasicBlock) && !(cell instanceof BasicPort) && super.isValidDropTarget(cell, cells);
2417     }
2418
2419     /**
2420      * Construct a new selection model used on this graph.
2421      *
2422      * @return a new selection model instance.
2423      * @see com.mxgraph.view.mxGraph#createSelectionModel()
2424      */
2425     @Override
2426     protected mxGraphSelectionModel createSelectionModel() {
2427         return new mxGraphSelectionModel(this) {
2428             /**
2429              * When we only want to select a cell which is a port, select the
2430              * parent block.
2431              *
2432              * @param cell the cell
2433              * @see
2434              * com.mxgraph.view.mxGraphSelectionModel#setCell(java.lang.Object)
2435              */
2436             @Override
2437             public void setCell(final Object cell) {
2438                 final Object current;
2439                 if (cell instanceof BasicPort) {
2440                     current = getModel().getParent(cell);
2441                 } else {
2442                     current = cell;
2443                 }
2444                 super.setCell(current);
2445             }
2446         };
2447     }
2448
2449     /**
2450      * Counts the number of children in the diagram
2451      *
2452      * @return the number of children in the diagram
2453      */
2454     public int countChildren() {
2455         return getModel().getChildCount(this.getDefaultParent());
2456     }
2457 }