Bug #8761 fixed: Xcos masked superblocks had invalid names
[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  * Copyright (C) 2017-2019 - ESI Group - Clement DAVID
8  *
9  * Copyright (C) 2012 - 2016 - Scilab Enterprises
10  *
11  * This file is hereby licensed under the terms of the GNU GPL v2.0,
12  * pursuant to article 5.3.4 of the CeCILL v.2.1.
13  * This file was originally licensed under the terms of the CeCILL v2.1,
14  * and continues to be available under such terms.
15  * For more information, see the COPYING file which you should have received
16  * along with this program.
17  *
18  */
19 package org.scilab.modules.xcos.graph;
20
21 import org.scilab.modules.xcos.graph.model.XcosGraphModel;
22 import java.awt.GraphicsEnvironment;
23 import java.awt.event.ActionEvent;
24 import java.awt.event.ActionListener;
25 import java.io.File;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.HashSet;
33 import java.util.IllegalFormatException;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Queue;
38 import java.util.Set;
39 import java.util.logging.Logger;
40
41 import javax.swing.JFileChooser;
42 import javax.swing.JOptionPane;
43 import javax.swing.SwingWorker;
44 import javax.swing.Timer;
45
46 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement;
47 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.InterpreterException;
48 import org.scilab.modules.graph.ScilabGraph;
49 import org.scilab.modules.graph.utils.ScilabGraphConstants;
50 import org.scilab.modules.gui.bridge.filechooser.SwingScilabFileChooser;
51 import org.scilab.modules.gui.bridge.tab.SwingScilabDockablePanel;
52 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
53 import org.scilab.modules.gui.messagebox.ScilabModalDialog.AnswerOption;
54 import org.scilab.modules.gui.messagebox.ScilabModalDialog.ButtonType;
55 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
56 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
57 import org.scilab.modules.xcos.JavaController;
58 import org.scilab.modules.xcos.Kind;
59 import org.scilab.modules.xcos.ObjectProperties;
60 import org.scilab.modules.xcos.VectorOfDouble;
61 import org.scilab.modules.xcos.VectorOfInt;
62 import org.scilab.modules.xcos.VectorOfString;
63 import org.scilab.modules.xcos.Xcos;
64 import org.scilab.modules.xcos.XcosTab;
65 import org.scilab.modules.xcos.actions.SaveAsAction;
66 import org.scilab.modules.xcos.block.AfficheBlock;
67 import org.scilab.modules.xcos.block.BasicBlock;
68 import org.scilab.modules.xcos.block.SplitBlock;
69 import org.scilab.modules.xcos.block.TextBlock;
70 import org.scilab.modules.xcos.block.io.EventInBlock;
71 import org.scilab.modules.xcos.block.io.EventOutBlock;
72 import org.scilab.modules.xcos.block.io.ExplicitInBlock;
73 import org.scilab.modules.xcos.block.io.ExplicitOutBlock;
74 import org.scilab.modules.xcos.block.io.ImplicitInBlock;
75 import org.scilab.modules.xcos.block.io.ImplicitOutBlock;
76 import org.scilab.modules.xcos.configuration.ConfigurationManager;
77 import org.scilab.modules.xcos.graph.model.BlockInterFunction;
78 import org.scilab.modules.xcos.graph.model.ScicosObjectOwner;
79 import org.scilab.modules.xcos.graph.model.XcosCell;
80 import org.scilab.modules.xcos.graph.model.XcosCellFactory;
81 import org.scilab.modules.xcos.graph.swing.GraphComponent;
82 import org.scilab.modules.xcos.io.XcosFileType;
83 import org.scilab.modules.xcos.io.scicos.ScilabDirectHandler;
84 import org.scilab.modules.xcos.link.BasicLink;
85 import org.scilab.modules.xcos.link.CommandControlLink;
86 import org.scilab.modules.xcos.link.ExplicitLink;
87 import org.scilab.modules.xcos.link.ImplicitLink;
88 import org.scilab.modules.xcos.port.BasicPort;
89 import org.scilab.modules.xcos.port.BasicPort.Type;
90 import org.scilab.modules.xcos.port.Orientation;
91 import org.scilab.modules.xcos.port.PortCheck;
92 import org.scilab.modules.xcos.port.command.CommandPort;
93 import org.scilab.modules.xcos.port.control.ControlPort;
94 import org.scilab.modules.xcos.port.input.ExplicitInputPort;
95 import org.scilab.modules.xcos.port.input.ImplicitInputPort;
96 import org.scilab.modules.xcos.port.output.ExplicitOutputPort;
97 import org.scilab.modules.xcos.port.output.ImplicitOutputPort;
98 import org.scilab.modules.xcos.preferences.XcosOptions;
99 import org.scilab.modules.xcos.utils.BlockPositioning;
100 import org.scilab.modules.xcos.utils.Stack;
101 import org.scilab.modules.xcos.utils.XcosConstants;
102 import org.scilab.modules.xcos.utils.XcosDialogs;
103 import org.scilab.modules.xcos.utils.XcosMessages;
104
105 import com.mxgraph.model.mxCell;
106 import com.mxgraph.model.mxGeometry;
107 import com.mxgraph.model.mxGraphModel;
108 import com.mxgraph.model.mxICell;
109 import com.mxgraph.util.mxEvent;
110 import com.mxgraph.util.mxEventObject;
111 import com.mxgraph.util.mxPoint;
112 import com.mxgraph.view.mxGraphSelectionModel;
113 import com.mxgraph.view.mxMultiplicity;
114 import java.lang.reflect.Constructor;
115 import java.rmi.server.UID;
116 import java.util.HashMap;
117 import java.util.Hashtable;
118 import java.util.Optional;
119 import java.util.stream.Collectors;
120 import org.scilab.modules.types.ScilabType;
121 import org.scilab.modules.xcos.VectorOfBool;
122 import org.scilab.modules.xcos.VectorOfScicosID;
123 import org.scilab.modules.xcos.block.SuperBlock;
124 import org.scilab.modules.xcos.block.io.ContextUpdate;
125 import org.scilab.modules.xcos.io.ScilabTypeCoder;
126 import org.scilab.modules.xcos.utils.XcosEvent;
127
128 /**
129  * The base class for a diagram. This class contains jgraphx + Scicos data.
130  */
131 public class XcosDiagram extends ScilabGraph {
132
133     private static final Logger LOG = Logger.getLogger(XcosDiagram.class.getName());
134
135     private static final String CELLS = "cells";
136     public static final String IN = "in";
137     public static final String OUT = "out";
138     public static final String EIN = "ein";
139     public static final String EOUT = "eout";
140
141     /**
142      * Prefix used to tag text node.
143      */
144     public static final String HASH_IDENTIFIER = "#identifier";
145
146     /**
147      * Constructor
148      *
149      * @param controller the shared controller
150      * @param diagramId the diagram MVC ID
151      * @param kind DIAGRAM or BLOCK for a root diagram or a super-block
152      * @param uid the string UID that will be used on the default parent
153      */
154     public XcosDiagram(final JavaController controller, final long diagramId, final Kind kind, String uid) {
155         super(new XcosGraphModel(controller, diagramId, kind, uid), Xcos.getInstance().getStyleSheet());
156
157         // set the default parent (the JGraphX layer) defined on the model
158         setDefaultParent(getModel().getChildAt(getModel().getRoot(), 0));
159
160         setComponent(new GraphComponent(this));
161         initComponent();
162
163         // Forbid disconnecting cells once it is connected.
164         setCellsDisconnectable(false);
165
166         // Forbid pending edges.
167         setAllowDanglingEdges(false);
168
169         // Cannot connect port to itself.
170         setAllowLoops(false);
171
172         // Override isCellResizable to filter what the user can resize
173         setCellsResizable(true);
174
175         /* Labels use HTML if not equal to interface function name */
176         setHtmlLabels(true);
177         /*
178          * by default every label is movable, see XcosDiagram##isLabelMovable(java.lang.Object) for restrictions
179          */
180         setVertexLabelsMovable(true);
181         setEdgeLabelsMovable(true);
182
183         //
184         setCloneInvalidEdges(true);
185
186         // Override isCellEditable to filter what the user can edit
187         setCellsEditable(true);
188
189         setConnectableEdges(true);
190
191         // Do not clear edge points on connect
192         setResetEdgesOnConnect(false);
193
194         setMultiplicities();
195
196         // auto-position the diagram origin
197         setAutoOrigin(true);
198
199         // do not put loop links inside the common block cell but on the defaultParent
200         ((mxGraphModel) getModel()).setMaintainEdgeParent(false);
201     }
202
203     /*
204      * Static helpers
205      */
206     /**
207      * Only return the instanceof klass
208      *
209      * @param selection the selection to filter out
210      * @param klass the class selector
211      * @return the selection with only klass instance.
212      */
213     public static Object[] filterByClass(final Object[] selection, final Class<BasicBlock> klass) {
214         return mxGraphModel.filterCells(selection, new mxGraphModel.Filter() {
215             @Override
216             public boolean filter(Object cell) {
217                 return klass.isInstance(cell);
218             }
219         });
220     }
221
222     /**
223      * Fill the hierarchy from the first element up to the root diagram
224      * (included)
225      * <p>
226      * Should be used as :
227      * <pre>
228      *  hierarchy = fillHierarchy(new ScicosObjectOwner(getUID(), getKind()))
229      * </pre>
230      *
231      * @param hierarchy the collection to fill
232      * @return the filled collection (the root at the end)
233      */
234     public static Stack<ScicosObjectOwner> lookForHierarchy(ScicosObjectOwner current) {
235         ScicosObjectOwner local = current;
236         Stack<ScicosObjectOwner> hierarchy = new Stack<>();
237         JavaController controller = new JavaController();
238
239         long[] parent = new long[] {local.getUID()};
240         if (local.getKind() == Kind.DIAGRAM) {
241             hierarchy.push(local);
242             return hierarchy;
243         }
244
245         while (parent[0] != 0l) {
246             hierarchy.push(new ScicosObjectOwner(parent[0], Kind.BLOCK));
247             controller.getObjectProperty(parent[0], Kind.BLOCK, ObjectProperties.PARENT_BLOCK, parent);
248         }
249
250         controller.getObjectProperty(local.getUID(), local.getKind(), ObjectProperties.PARENT_DIAGRAM, parent);
251         hierarchy.push(new ScicosObjectOwner(parent[0], Kind.DIAGRAM));
252
253         return hierarchy;
254     }
255
256     /**
257      * Sort the blocks per first integer parameter value
258      *
259      * @param blocks the block list
260      * @param the shared controller
261      * @return the sorted block list (same instance)
262      */
263     public static List<? extends BasicBlock> iparSort(final List<? extends BasicBlock> blocks, final JavaController controller) {
264         Collections.sort(blocks, new Comparator<BasicBlock>() {
265             @Override
266             public int compare(BasicBlock o1, BasicBlock o2) {
267
268
269                 final VectorOfInt data1 = new VectorOfInt();
270                 final VectorOfInt data2 = new VectorOfInt();
271
272                 controller.getObjectProperty(o1.getUID(), Kind.BLOCK, ObjectProperties.IPAR, data1);
273                 controller.getObjectProperty(o2.getUID(), Kind.BLOCK, ObjectProperties.IPAR, data2);
274
275                 final int value1;
276                 if (data1.size() >= 1) {
277                     value1 = data1.get(0);
278                 } else {
279                     value1 = 0;
280                 }
281
282                 final int value2;
283                 if (data2.size() >= 1) {
284                     value2 = data2.get(0);
285                 } else {
286                     value2 = 0;
287                 }
288
289                 return value1 - value2;
290             }
291         });
292         return blocks;
293     }
294
295     /**
296      * @param <T> The type to work on
297      * @param klass the class instance to work on
298      * @return list of typed block
299      */
300     @SuppressWarnings("unchecked")
301     private <T extends BasicBlock> List<T> getAllTypedBlock(Class<T> klass) {
302         final List<T> list = new ArrayList<T>();
303
304         int blockCount = getModel().getChildCount(getDefaultParent());
305
306         for (int i = 0; i < blockCount; i++) {
307             Object cell = getModel().getChildAt(getDefaultParent(), i);
308             if (klass.isInstance(cell)) {
309                 // According to the test we are sure that the cell is an
310                 // instance of T. Thus we can safely cast it.
311                 list.add((T) cell);
312             }
313         }
314         return list;
315     }
316
317     /**
318      * @param <T>
319      * @param <T> The type to work on
320      * @param klasses the class instance list to work on
321      * @return list of typed block
322      */
323     private <T extends BasicBlock> List<T> getAllTypedBlock(Class<T>[] klasses) {
324         final List<T> list = new ArrayList<T>();
325         for (Class<T> klass : klasses) {
326             list.addAll(getAllTypedBlock(klass));
327         }
328         return list;
329     }
330
331     /**
332      * Fill the context with I/O port
333      *
334      * @param context the context to fill
335      * @param controller the shared controller
336      * @return the context
337      */
338     @SuppressWarnings("unchecked")
339     public final Map<Object, Object> fillContext(final Map<Object, Object> context, final JavaController controller) {
340         if (!context.containsKey(IN)) {
341             context.put(IN, iparSort(getAllTypedBlock(new Class[] {ExplicitInBlock.class, ImplicitInBlock.class}), controller));
342         }
343         if (!context.containsKey(OUT)) {
344             context.put(OUT, iparSort(getAllTypedBlock(new Class[] {ExplicitOutBlock.class, ImplicitOutBlock.class}), controller));
345         }
346         if (!context.containsKey(EIN)) {
347             context.put(EIN, iparSort(getAllTypedBlock(new Class[] {EventInBlock.class}), controller));
348         }
349         if (!context.containsKey(EOUT)) {
350             context.put(EOUT, iparSort(getAllTypedBlock(new Class[] {EventOutBlock.class}), controller));
351         }
352
353         return context;
354     }
355
356     @Override
357     public String validateCell(final Object cell, final Hashtable<Object, Object> context) {
358         if (getKind() == Kind.BLOCK) {
359             return validateChildDiagram(cell, context);
360         } else {
361             // does not perform any validation on a root diagram
362             return null;
363         }
364     }
365
366     /**
367      * Validate I/O ports.
368      *
369      * /!\ No model modification should be made in this method, this is only a
370      * validation method.
371      *
372      * @param cell Cell that represents the cell to validate.
373      * @param context Hashtable that represents the global validation state.
374      * @return the error message or null
375      */
376     public String validateChildDiagram(final Object cell, final Map<Object, Object> context) {
377         String err = null;
378
379         /*
380          * Only validate I/O blocks
381          */
382         // get the key
383         final String key;
384         if (cell instanceof ExplicitInBlock || cell instanceof ImplicitInBlock) {
385             key = IN;
386         } else if (cell instanceof ExplicitOutBlock || cell instanceof ImplicitOutBlock) {
387             key = OUT;
388         } else if (cell instanceof EventInBlock) {
389             key = EIN;
390         } else if (cell instanceof EventOutBlock) {
391             key = EOUT;
392         } else {
393             return null;
394         }
395         final BasicBlock block = (BasicBlock) cell;
396         final JavaController controller = new JavaController();
397
398         /*
399          * Prepare validation
400          */
401         // fill the context once
402         fillContext(context, controller);
403
404         /*
405          * Validate with ipar
406          */
407         // get the real index
408         final List<? extends BasicBlock> blocks = (List<? extends BasicBlock>) context.get(key);
409         final int realIndex = blocks.indexOf(block) + 1;
410
411         // get the user index
412         VectorOfInt ipar = new VectorOfInt();
413         controller.getObjectProperty(block.getUID(), Kind.BLOCK, ObjectProperties.IPAR, ipar);
414         if (ipar.size() < 1) {
415             return err;
416         }
417         final int userIndex = ipar.get(0);
418
419         // if the indexes are not equals, alert the user.
420         if (realIndex != userIndex) {
421             final StringBuilder str = new StringBuilder();
422             str.append("<html><body><em>");
423             str.append(XcosMessages.WRONG_PORT_NUMBER);
424             str.append("</em><br/>");
425             str.append(String.format(XcosMessages.EXPECTING_NUMBER, realIndex, userIndex));
426             str.append("</body></html>    ");
427
428             err = str.toString();
429         }
430
431         return err;
432     }
433
434     /**
435      * Set a model property and track undo/redo operation
436      * @param cell the cell
437      * @param values the value to set
438      */
439     public void updateBlock(BasicBlock cell, Map<ObjectProperties, Object> values) {
440         model.beginUpdate();
441         try {
442             blockUpdated(cell, values);
443             fireEvent(new mxEventObject(XcosEvent.UPDATE_BLOCK, XcosEvent.BLOCK, cell));
444         } finally {
445             model.endUpdate();
446         }
447     }
448
449     private void blockUpdated(BasicBlock cell, Map<ObjectProperties, Object> values) {
450         model.beginUpdate();
451         try {
452             for (Map.Entry<ObjectProperties, Object> e : values.entrySet()) {
453                 if (e.getValue() instanceof double[]) {
454                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (double[]) e.getValue());
455                 } else if (e.getValue() instanceof int[]) {
456                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (int[]) e.getValue());
457                 } else if (e.getValue() instanceof boolean[]) {
458                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (boolean[]) e.getValue());
459                 } else if (e.getValue() instanceof String[]) {
460                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (String[]) e.getValue());
461                 } else if (e.getValue() instanceof long[]) {
462                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (long[]) e.getValue());
463                 } else if (e.getValue() instanceof VectorOfDouble) {
464                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfDouble) e.getValue());
465                 } else if (e.getValue() instanceof VectorOfInt) {
466                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfInt) e.getValue());
467                 } else if (e.getValue() instanceof VectorOfBool) {
468                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfBool) e.getValue());
469                 } else if (e.getValue() instanceof VectorOfString) {
470                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfString) e.getValue());
471                 } else if (e.getValue() instanceof VectorOfScicosID) {
472                     ((XcosGraphModel) model).setProperty(cell, e.getKey(), (VectorOfScicosID) e.getValue());
473                 }
474             }
475         } finally {
476             model.endUpdate();
477         }
478
479     }
480
481     /*
482      * Static diagram listeners
483      */
484     /**
485      * CellResizedTracker Called when mxEvents.CELLS_RESIZED is fired.
486      */
487     private static final class RepositionTracker implements mxIEventListener {
488
489         private static RepositionTracker instance;
490
491         /**
492          * Constructor
493          */
494         private RepositionTracker() {
495         }
496
497         /**
498          * @return the instance
499          */
500         public static RepositionTracker getInstance() {
501             if (instance == null) {
502                 instance = new RepositionTracker();
503             }
504             return instance;
505         }
506
507         /**
508          * Update the cell view
509          *
510          * @param source the source instance
511          * @param evt the event data
512          * @see
513          * com.mxgraph.util.mxEventSource.mxIEventListener#invoke(java.lang.Object,
514          * com.mxgraph.util.mxEventObject)
515          */
516         @Override
517         public void invoke(final Object source, final mxEventObject evt) {
518             final XcosDiagram diagram = (XcosDiagram) source;
519             final Object[] cells = (Object[]) evt.getProperty(CELLS);
520
521             diagram.getModel().beginUpdate();
522             try {
523                 for (int i = 0; i < cells.length; ++i) {
524                     if (cells[i] instanceof BasicBlock) {
525                         BlockPositioning.updateBlockView(diagram, (BasicBlock) cells[i]);
526                     }
527                 }
528             } finally {
529                 diagram.getModel().endUpdate();
530             }
531         }
532     }
533
534     /**
535      * Refresh each block on modification (update port position, etc...)
536      */
537     private static final class RefreshBlockTracker implements mxIEventListener {
538         private static RefreshBlockTracker instance = new RefreshBlockTracker();
539
540         /**
541          * Default constructor
542          */
543         private RefreshBlockTracker() {
544         }
545
546         /**
547          * @return the instance
548          */
549         public static RefreshBlockTracker getInstance() {
550             return instance;
551         }
552
553         /**
554          * Refresh the block on port added
555          *
556          * @param sender the diagram
557          * @param evt the event
558          * @see
559          * com.mxgraph.util.mxEventSource.mxIEventListener#invoke(java.lang.Object,
560          * com.mxgraph.util.mxEventObject)
561          */
562         @Override
563         public void invoke(Object sender, mxEventObject evt) {
564             final XcosDiagram diagram = (XcosDiagram) sender;
565
566             if (diagram.isReadonly()) {
567                 return;
568             }
569
570             diagram.getModel().beginUpdate();
571             try {
572                 final BasicBlock updatedBlock = (BasicBlock) evt.getProperty(XcosEvent.BLOCK);
573                 BlockPositioning.updateBlockView(diagram, updatedBlock);
574
575                 diagram.getView().clear(updatedBlock, true, true);
576
577                 // validate display errors
578                 diagram.getAsComponent().clearCellOverlays();
579                 diagram.getAsComponent().validateGraph();
580
581                 diagram.getView().validate();
582             } finally {
583                 diagram.getModel().endUpdate();
584             }
585         }
586     }
587
588     /**
589      * Update the tab title depending on the status
590      */
591     private static final class SavedStatusTracker implements mxIEventListener {
592         private static final Map<XcosDiagram, SavedStatusTracker> instances = new HashMap<>();
593         private ScicosObjectOwner owner;
594
595         private SavedStatusTracker(final ScicosObjectOwner o) {
596             this.owner = o;
597         }
598
599         public static SavedStatusTracker getInstance(XcosDiagram d) {
600             return instances.putIfAbsent(d, new SavedStatusTracker(Xcos.findRoot(d)));
601         }
602
603         @Override
604         public void invoke(Object sender, mxEventObject eo) {
605             if (sender instanceof XcosGraphModel) {
606                 List<XcosDiagram> diagrams = Xcos.getInstance().openedDiagrams(owner);
607                 diagrams.stream().forEach(d -> d.updateTabTitle());
608             }
609         }
610     }
611
612     /**
613      * Update the number and position of ports for a Superblock
614      */
615     public static final class UpdateSuperblockPortsTracker implements mxIEventListener {
616
617         private static UpdateSuperblockPortsTracker instance = new UpdateSuperblockPortsTracker();
618
619         public static UpdateSuperblockPortsTracker getInstance() {
620             return instance;
621         }
622
623         @Override
624         public void invoke(Object sender, mxEventObject evt) {
625             final XcosDiagram diagram = (XcosDiagram) sender;
626
627             if (diagram.isReadonly()) {
628                 return;
629             }
630
631             // common behavior for both block adding/removing and block update
632             List<Object> updatedIOBlocks;
633             if (XcosEvent.UPDATE_BLOCK.equals(evt.getName())) {
634                 updatedIOBlocks = new ArrayList<>();
635                 Object b = evt.getProperty(XcosEvent.BLOCK);
636                 if  (b instanceof ContextUpdate) {
637                     updatedIOBlocks.add(b);
638                 }
639             } else { // ADD_CELLS or REMOVE_CELLS
640                 Object[] cells = (Object[]) evt.getProperty("cells");
641                 updatedIOBlocks = Arrays.stream(cells).filter(b -> b instanceof ContextUpdate).collect(Collectors.toList());
642             }
643             if (updatedIOBlocks.isEmpty()) {
644                 return;
645             }
646
647             final JavaController controller = new JavaController();
648
649             String[] superBlockUID = {""};
650             controller.getObjectProperty(diagram.getUID(), diagram.getKind(), ObjectProperties.UID, superBlockUID);
651
652             List<XcosDiagram> diagrams = Xcos.getInstance().openedDiagrams(Xcos.findRoot(controller, diagram));
653             for (XcosDiagram parent : diagrams) {
654                 final XcosGraphModel model = (XcosGraphModel) parent.getModel();
655                 Object cell = model.getCell(superBlockUID[0]);
656                 if (cell != null) {
657                     // associated parent superblock when visible
658                     SuperBlock superblock = (SuperBlock) cell;
659
660                     // create a full context
661                     Map<Object, Object> context = new HashMap<>();
662                     diagram.fillContext(context, controller);
663
664                     // only update the superblock ports if all of them are valids
665                     diagram.getAsComponent().clearCellOverlays();
666                     String status = diagram.getAsComponent().validateGraph();
667                     if (status != null) {
668                         return;
669                     }
670
671                     model.beginUpdate();
672                     try {
673                         syncPorts(controller, superblock, ObjectProperties.INPUTS, (List<ContextUpdate>) context.get(IN), parent);
674                         syncPorts(controller, superblock, ObjectProperties.OUTPUTS, (List<ContextUpdate>) context.get(OUT), parent);
675                         syncPorts(controller, superblock, ObjectProperties.EVENT_INPUTS, (List<ContextUpdate>) context.get(EIN), parent);
676                         syncPorts(controller, superblock, ObjectProperties.EVENT_OUTPUTS, (List<ContextUpdate>) context.get(EOUT), parent);
677                     } finally {
678                         model.endUpdate();
679                     }
680                     break;
681                 }
682             }
683         }
684
685         /**
686          * Synchronize ports on the parent diagram accordingly to the updated IOBlocks
687          * @param controller shared controller;
688          * @param superblock the superblock to update
689          * @param p port kind
690          * @param updated the updated IOBlocks (ipar sorted)
691          * @param parent diagram containing the superblock
692          */
693         public static void syncPorts(final JavaController controller, SuperBlock superblock, ObjectProperties p, List<ContextUpdate> updated, XcosDiagram parent) {
694             VectorOfScicosID ports = new VectorOfScicosID();
695             controller.getObjectProperty(superblock.getUID(), superblock.getKind(), p, ports);
696
697             int added = updated.size() - ports.size();
698             if (added > 0) {
699                 for (int i = 0; i < added; i++) {
700                     // adding port
701                     BasicPort port = ContextUpdate.IOBlocks.createPort(controller, updated.get(ports.size() + i));
702                     parent.addCell(port, superblock);
703                 }
704             } else if (added < 0) {
705                 for (int i = 0; i < -added; i++) {
706                     // removing port
707                     String portUID[] = {""};
708                     controller.getObjectProperty(ports.get(updated.size() + i), Kind.PORT, ObjectProperties.UID, portUID);
709                     parent.removeCells(new Object[] {((XcosGraphModel) parent.getModel()).getCell(portUID[0])});
710                 }
711             }
712         }
713
714         /**
715          * Append the modified blocks to an existing context
716          * @param context the validation context
717          * @param updatedIOBlocks the blocks to add
718          * @param controller shared controller
719          */
720         public static void updateContext(Map<Object, Object> context, List<XcosCell> updatedIOBlocks, final JavaController controller) {
721             {
722                 List<BasicBlock> l = (List<BasicBlock>) context.getOrDefault(IN, new ArrayList<>());
723                 updatedIOBlocks.stream()
724                 .filter(b -> b instanceof ExplicitInBlock || b instanceof ImplicitInBlock)
725                 .map(b -> (ContextUpdate) b)
726                 .collect(Collectors.toCollection(() -> l));
727                 iparSort(l, controller);
728                 context.putIfAbsent(IN, l);
729             }
730             {
731                 List<BasicBlock> l = (List<BasicBlock>) context.getOrDefault(OUT, new ArrayList<>());
732                 updatedIOBlocks.stream()
733                 .filter(b -> b instanceof ExplicitOutBlock || b instanceof ImplicitOutBlock)
734                 .map(b -> (ContextUpdate) b)
735                 .collect(Collectors.toCollection(() -> l));
736                 iparSort(l, controller);
737                 context.putIfAbsent(OUT, l);
738             }
739             {
740                 List<BasicBlock> l = (List<BasicBlock>) context.getOrDefault(EIN, new ArrayList<>());
741                 updatedIOBlocks.stream()
742                 .filter(b -> b instanceof EventInBlock)
743                 .map(b -> (ContextUpdate) b)
744                 .collect(Collectors.toCollection(() -> l));
745                 iparSort(l, controller);
746                 context.putIfAbsent(EIN, l);
747             }
748             {
749                 List<BasicBlock> l = (List<BasicBlock>) context.getOrDefault(EOUT, new ArrayList<>());
750                 updatedIOBlocks.stream()
751                 .filter(b -> b instanceof EventOutBlock)
752                 .map(b -> (ContextUpdate) b)
753                 .collect(Collectors.toCollection(() -> l));
754                 iparSort(l, controller);
755                 context.putIfAbsent(EOUT, l);
756             }
757         }
758     }
759
760     /**
761      * Hook method that creates the new edge for insertEdge. This implementation
762      * does not set the source and target of the edge, these are set when the
763      * edge is added to the model.
764      *
765      * @param parent Cell that specifies the parent of the new edge.
766      * @param id Optional string that defines the Id of the new edge.
767      * @param value Object to be used as the user object.
768      * @param source Cell that defines the source of the edge.
769      * @param target Cell that defines the target of the edge.
770      * @param style Optional string that defines the cell style.
771      * @return Returns the new edge to be inserted.
772      * @see com.mxgraph.view.mxGraph#createEdge(java.lang.Object,
773      * java.lang.String, java.lang.Object, java.lang.Object, java.lang.Object,
774      * java.lang.String)
775      */
776     @Override
777     public Object createEdge(Object parent, String id, Object value, Object source, Object target, String style) {
778         Object ret = null;
779         JavaController controller = new JavaController();
780
781         if (source instanceof BasicPort) {
782             BasicPort src = (BasicPort) source;
783             BasicLink link = null;
784
785             long uid = controller.createObject(Kind.LINK);
786             if (src.getType() == Type.EXPLICIT) {
787                 link = new ExplicitLink(controller, uid, Kind.LINK, value, null, style, id);
788             } else if (src.getType() == Type.IMPLICIT) {
789                 link = new ImplicitLink(controller, uid, Kind.LINK, value, null, style, id);
790             } else {
791                 link = new CommandControlLink(controller, uid, Kind.LINK, value, null, style, id);
792             }
793
794             // allocate the associated geometry
795             link.setGeometry(new mxGeometry());
796             ret = link;
797         } else if (source instanceof SplitBlock) {
798             SplitBlock src = (SplitBlock) source;
799             return createEdge(parent, id, value, src.getIn(), target, style);
800         } else if (source instanceof BasicLink) {
801             BasicLink src = (BasicLink) source;
802             BasicLink link = null;
803
804             try {
805                 Class<? extends BasicLink> klass = src.getClass();
806
807                 // call XXXXLink(JavaController controller, long uid, Kind kind, Object value, mxGeometry geometry, String style, String id)
808                 Constructor<? extends BasicLink> cstr = klass.getConstructor(JavaController.class, Long.TYPE, Kind.class, Object.class, mxGeometry.class, String.class, String.class);
809                 link = cstr.newInstance(controller, controller.createObject(Kind.LINK), src.getKind(), null, new mxGeometry(), src.getStyle(), new UID().toString());
810             } catch (ReflectiveOperationException e) {
811                 LOG.severe(e.toString());
812             }
813
814             ret = link;
815         }
816
817         if (ret == null) {
818             LOG.warning("Unable to create an edge");
819         }
820
821         return ret;
822     }
823
824     /**
825      * Add an edge from a source to the target.
826      *
827      * @param cell the edge to add (may be null)
828      * @param parent the parent of the source and the target
829      * @param source the source cell
830      * @param target the target cell
831      * @param index the index of the edge
832      * @return the added edge or null.
833      * @see com.mxgraph.view.mxGraph#addEdge(java.lang.Object, java.lang.Object,
834      * java.lang.Object, java.lang.Object, java.lang.Integer)
835      */
836     @Override
837     public Object addCell(Object cell, Object parent, Integer index, Object source, Object target) {
838
839         // already connected edge or normal block
840         if (source == null && target == null) {
841             return super.addCell(cell, parent, index, source, target);
842         }
843
844         // Command -> Control
845         if (source instanceof CommandPort && target instanceof ControlPort && cell instanceof CommandControlLink) {
846             return super.addCell(cell, parent, index, source, target);
847         }
848
849         // Control -> Command
850         // Switch source and target !
851         if (target instanceof CommandPort && source instanceof ControlPort && cell instanceof CommandControlLink) {
852             BasicLink current = (BasicLink) cell;
853             current.invertDirection();
854
855             return super.addCell(cell, parent, index, target, source);
856         }
857
858         // ExplicitOutput -> ExplicitInput
859         if (source instanceof ExplicitOutputPort && target instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
860             return super.addCell(cell, parent, index, source, target);
861         }
862         // ExplicitInput -> ExplicitOutput
863         // Switch source and target !
864         if (target instanceof ExplicitOutputPort && source instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
865             BasicLink current = (BasicLink) cell;
866             current.invertDirection();
867
868             return super.addCell(cell, parent, index, target, source);
869         }
870
871         // ImplicitOutput -> ImplicitInput
872         if (source instanceof ImplicitOutputPort && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
873             return super.addCell(cell, parent, index, source, target);
874         }
875         // ImplicitInput -> ImplicitOutput
876         // Switch source and target !
877         if (target instanceof ImplicitOutputPort && source instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
878             BasicLink current = (BasicLink) cell;
879             current.invertDirection();
880
881             return super.addCell(cell, parent, index, target, source);
882         }
883
884         // ImplicitInput -> ImplicitInput
885         if (source instanceof ImplicitInputPort && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
886             return super.addCell(cell, parent, index, source, target);
887         }
888         // ImplicitOutputPort -> ImplicitOutput
889         // Switch source and target !
890         if (target instanceof ImplicitOutputPort && source instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
891             BasicLink current = (BasicLink) cell;
892             current.invertDirection();
893
894             return super.addCell(cell, parent, index, target, source);
895         }
896
897         /*
898          * Split management
899          */
900         // ExplicitLink -> ExplicitInputPort
901         if (source instanceof ExplicitLink && target instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
902             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
903             return addCell(cell, parent, index, split.getOut2(), target);
904         }
905         // ExplicitOutput -> ExpliciLink
906         // Switch source and target !
907         if (target instanceof ExplicitLink && source instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
908             final BasicLink current = (BasicLink) cell;
909             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
910
911             current.invertDirection();
912
913             return addCell(cell, parent, index, split.getOut2(), source);
914         }
915
916         // ImplicitLink -> ImplicitInputPort
917         if (source instanceof ImplicitLink && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
918             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
919             return addCell(cell, parent, index, split.getOut2(), target);
920         }
921         // ImplicitInputPort -> ImplicitLink
922         // Switch source and target !
923         if (target instanceof ImplicitLink && source instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
924             final BasicLink current = (BasicLink) cell;
925             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
926
927             current.invertDirection();
928
929             return addCell(cell, parent, index, split.getOut2(), source);
930         }
931
932         // ImplicitLink -> ImplicitOutputPort
933         if (source instanceof ImplicitLink && target instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
934             final BasicLink current = (BasicLink) cell;
935             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) source);
936             return addCell(cell, parent, index, split.getOut2(), source);
937         }
938         // ImplicitOutputPort -> ImplicitLink
939         // Switch source and target !
940         if (target instanceof ImplicitLink && source instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
941             final BasicLink current = (BasicLink) cell;
942             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (ImplicitLink) target);
943             return addCell(cell, parent, index, split.getOut2(), source);
944         }
945
946         // CommandControlLink -> ControlPort
947         if (source instanceof CommandControlLink && target instanceof ControlPort && cell instanceof CommandControlLink) {
948             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
949             return addCell(cell, parent, index, split.getOut2(), target);
950         }
951         // ControlPort -> CommandControlLink
952         // Switch source and target !
953         if (target instanceof CommandControlLink && source instanceof ControlPort && cell instanceof CommandControlLink) {
954             final BasicLink current = (BasicLink) cell;
955             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
956
957             current.invertDirection();
958
959             return addCell(cell, parent, index, split.getOut2(), source);
960         }
961
962         if (cell instanceof BasicLink && source != null && target != null) {
963             LOG.severe("Unable to add a typed link");
964             return null;
965         } else {
966             LOG.severe("Adding an untyped edge");
967             return super.addCell(cell, parent, index, source, target);
968         }
969     }
970
971     /**
972      * Add a split on a edge.
973      *
974      * @param splitPoint the split point (center of the split block)
975      * @param link source link
976      * @return split block
977      */
978     public SplitBlock addSplitEdge(final mxPoint splitPoint, final BasicLink link) {
979         final BasicPort linkSource = (BasicPort) link.getSource();
980         final BasicPort linkTarget = (BasicPort) link.getTarget();
981
982         /*
983          * Select the right split accordingly to the link klass
984          */
985         BlockInterFunction f;
986         if (link instanceof CommandControlLink) {
987             f = BlockInterFunction.CLKSPLIT_f;
988         } else if (link instanceof ImplicitLink) {
989             f = BlockInterFunction.IMPSPLIT_f;
990         } else {
991             f = BlockInterFunction.SPLIT_f;
992         }
993
994         final SplitBlock splitBlock;
995         try {
996             splitBlock = (SplitBlock) XcosCellFactory.createBlock(f);
997         } catch (InterpreterException ex) {
998             // something goes wrong
999             throw new RuntimeException(ex);
1000         }
1001
1002         // snap the center of the split block on the grid
1003         try {
1004             mxGeometry geom = splitBlock.getGeometry();
1005             double x = snap(splitPoint.getX()) - (SplitBlock.DEFAULT_SIZE / 2.);
1006             double y = snap(splitPoint.getY()) - (SplitBlock.DEFAULT_SIZE / 2.);
1007             geom.setX(x);
1008             geom.setY(y);
1009             splitBlock.setGeometry(geom);
1010         } catch (NullPointerException e) {
1011             e.printStackTrace();
1012         }
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 XcosCell identifier;
1661         final String cellId = cell.getId() + HASH_IDENTIFIER;
1662
1663         JavaController controller = new JavaController();
1664         long uid = controller.createObject(Kind.ANNOTATION);
1665
1666         final mxGeometry geom;
1667         if (cell.isVertex()) {
1668             // the vertex label position is relative to its parent
1669             geom = new mxGeometry(cell.getGeometry().getWidth() * 0.5, cell.getGeometry().getHeight() * 1.1, 0., 0.);
1670         } else {
1671             // the edge label position is absolute, its initial position should be computed
1672             mxPoint src = cell.getGeometry().getSourcePoint();
1673             mxPoint trgt = cell.getGeometry().getTargetPoint();
1674
1675             geom = new mxGeometry(src.getX(), src.getY(), 0., 0.);
1676             geom.translate((trgt.getX() - src.getX()) / 2, (trgt.getY() - src.getY()) / 2);
1677         }
1678
1679         identifier = new XcosCell(new JavaController(), uid, Kind.ANNOTATION, null, geom, "noLabel=0;opacity=0;", cellId);
1680         identifier.setVertex(true);
1681         identifier.setConnectable(false);
1682
1683         return identifier;
1684     }
1685
1686     /**
1687      * Get the label for the cell according to its style.
1688      *
1689      * @param cell the cell object
1690      * @return a representative the string (block name) or a style specific
1691      * style.
1692      * @see com.mxgraph.view.mxGraph#convertValueToString(java.lang.Object)
1693      */
1694     @Override
1695     public String convertValueToString(Object cell) {
1696         String ret = null;
1697
1698         if (cell != null) {
1699             JavaController controller = new JavaController();
1700             final Map<String, Object> style = getCellStyle(cell);
1701
1702             final String displayedLabel = (String) style.get("displayedLabel");
1703             if (displayedLabel != null) {
1704                 if (cell instanceof BasicBlock) {
1705                     BasicBlock block = (BasicBlock) cell;
1706                     VectorOfDouble v = new VectorOfDouble();
1707                     controller.getObjectProperty(block.getUID(), block.getKind(), ObjectProperties.EXPRS, v);
1708
1709                     try {
1710                         ret = new ScilabTypeCoder().format(displayedLabel, v);
1711                     } catch (IllegalFormatException e) {
1712                         LOG.severe(e.toString());
1713                         ret = displayedLabel;
1714                     }
1715                 } else {
1716                     ret = displayedLabel;
1717                 }
1718             } else {
1719                 final String label = super.convertValueToString(cell);
1720                 if (label.isEmpty() && cell instanceof BasicBlock) {
1721                     BasicBlock block = (BasicBlock) cell;
1722
1723                     String[] interfaceFunction = new String[1];
1724                     controller.getObjectProperty(block.getUID(), block.getKind(), ObjectProperties.INTERFACE_FUNCTION, interfaceFunction);
1725                     ret = interfaceFunction[0];
1726                 } else {
1727                     ret = label;
1728                 }
1729             }
1730         }
1731
1732         return ret;
1733     }
1734
1735     /**
1736      * Return true if auto sized
1737      *
1738      * @param cell the cell
1739      * @return status
1740      * @see com.mxgraph.view.mxGraph#isAutoSizeCell(java.lang.Object)
1741      */
1742     @Override
1743     public boolean isAutoSizeCell(final Object cell) {
1744         boolean status = super.isAutoSizeCell(cell);
1745
1746         if (cell instanceof AfficheBlock) {
1747             status |= true;
1748         }
1749
1750         if (cell instanceof TextBlock) {
1751             status &= false;
1752         }
1753
1754         return status;
1755     }
1756
1757     /**
1758      * {@inheritDoc} Do not extends if the port position is north or south.
1759      */
1760     @Override
1761     public boolean isExtendParent(Object cell) {
1762         final boolean extendsParents;
1763
1764         if (cell instanceof BasicPort) {
1765             final BasicPort p = (BasicPort) cell;
1766             extendsParents = !(p.getOrientation() == Orientation.NORTH || p.getOrientation() == Orientation.SOUTH) && super.isExtendParent(p);
1767         } else {
1768             extendsParents = super.isExtendParent(cell);
1769         }
1770         return extendsParents;
1771     }
1772
1773     /**
1774      * @return the model ID
1775      */
1776     public long getUID() {
1777         return ((XcosCell) getDefaultParent()).getUID();
1778     }
1779
1780     /**
1781      * @return Kind.DIAGRAM or Kind.BLOCK
1782      */
1783     public Kind getKind() {
1784         return ((XcosCell) getDefaultParent()).getKind();
1785     }
1786
1787     /**
1788      * Manage the visibility of the grid and the associated menu
1789      *
1790      * @param status new status
1791      */
1792     public void setGridVisible(final boolean status) {
1793         setGridEnabled(status);
1794         getAsComponent().setGridVisible(status);
1795         getAsComponent().repaint();
1796     }
1797
1798     /**
1799      * @return save status
1800      */
1801     public boolean saveDiagram() {
1802         return saveDiagramAs(getSavedFile());
1803     }
1804
1805     /**
1806      * @param fileName diagram filename
1807      * @return save status
1808      */
1809     public boolean saveDiagramAs(final File fileName) {
1810         boolean isSuccess = false;
1811         File writeFile = fileName;
1812         XcosFileType format = XcosOptions.getPreferences().getFileFormat();
1813
1814         info(XcosMessages.SAVING_DIAGRAM);
1815         if (fileName == null) {
1816             final SwingScilabFileChooser fc = SaveAsAction.createFileChooser();
1817             SaveAsAction.configureFileFilters(fc);
1818             ConfigurationManager.configureCurrentDirectory(fc);
1819
1820             if (getSavedFile() != null) {
1821                 // using save-as, the file chooser should have a filename
1822                 // without extension as default
1823                 String filename = getSavedFile().getName();
1824                 filename = filename.substring(0, filename.lastIndexOf('.'));
1825                 fc.setSelectedFile(new File(filename));
1826             } else {
1827                 final String title = getTitle();
1828                 if (title != null) {
1829                     /*
1830                      * 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
1831                      */
1832                     final char[] regex = "<>:\"/\\|?*".toCharArray();
1833                     String escaped = title;
1834                     for (char c : regex) {
1835                         escaped = escaped.replace(c, '-');
1836                     }
1837
1838                     fc.setSelectedFile(new File(escaped));
1839                 }
1840             }
1841
1842             int status = fc.showSaveDialog(this.getAsComponent());
1843             if (status != JFileChooser.APPROVE_OPTION) {
1844                 info(XcosMessages.EMPTY_INFO);
1845                 return isSuccess;
1846             }
1847
1848             // change the format if the user choose a save-able file format
1849             final XcosFileType selectedFilter = XcosFileType.findFileType(fc.getFileFilter());
1850             if (XcosFileType.getAvailableSaveFormats().contains(selectedFilter)) {
1851                 format = selectedFilter;
1852             }
1853             writeFile = fc.getSelectedFile();
1854         }
1855
1856         /* Extension/format update */
1857         // using a String filename also works on a non-existing file
1858         final String filename = writeFile.getName();
1859
1860         /*
1861          * Look for the user extension if it does not exist, append a default one
1862          *
1863          * if the specified extension is handled, update the save format ; else append a default extension and use the default format
1864          */
1865         XcosFileType userExtension = XcosFileType.findFileType(filename);
1866         if (userExtension == null) {
1867             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
1868             userExtension = format;
1869         }
1870         if (XcosFileType.getAvailableSaveFormats().contains(userExtension)) {
1871             format = userExtension;
1872         } else {
1873             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
1874         }
1875
1876         /*
1877          * If the file exists, ask for confirmation if this is not the previously saved file
1878          */
1879         if (writeFile.exists() && !writeFile.equals(getSavedFile())) {
1880             final boolean overwrite = ScilabModalDialog.show(XcosTab.get(this), XcosMessages.OVERWRITE_EXISTING_FILE, XcosMessages.XCOS, IconType.QUESTION_ICON,
1881                                       ButtonType.YES_NO) == AnswerOption.YES_OPTION;
1882
1883             if (!overwrite) {
1884                 info(XcosMessages.EMPTY_INFO);
1885                 return false;
1886             }
1887         }
1888
1889         /*
1890          * Really save the data
1891          */
1892         try {
1893             format.save(writeFile.getCanonicalPath(), getRootDiagram());
1894             setSavedFile(writeFile);
1895
1896             setTitle(writeFile.getName().substring(0, writeFile.getName().lastIndexOf('.')));
1897             ConfigurationManager.getInstance().addToRecentFiles(writeFile);
1898
1899             ScicosObjectOwner root = Xcos.findRoot(this);
1900             Xcos.getInstance().setModified(root, false);
1901             Xcos.getInstance().openedDiagrams(root).stream()
1902             .forEach(d -> d.updateTabTitle());
1903
1904             isSuccess = true;
1905         } catch (final Exception e) {
1906             LOG.severe(e.toString());
1907
1908             XcosDialogs.couldNotSaveFile(this);
1909         }
1910
1911         updateTabTitle();
1912         info(XcosMessages.EMPTY_INFO);
1913         return isSuccess;
1914     }
1915
1916     /**
1917      * Perform post loading initialization.
1918      *
1919      * @param file the loaded file
1920      */
1921     public void postLoad(final File file) {
1922         final String name = file.getName();
1923
1924         if (XcosFileType.getAvailableSaveFormats().contains(XcosFileType.findFileType(file))) {
1925             setSavedFile(file);
1926         }
1927         setTitle(name.substring(0, name.lastIndexOf('.')));
1928         setModified(false);
1929         updateTabTitle();
1930
1931         fireEvent(new mxEventObject(mxEvent.ROOT));
1932
1933         info(XcosMessages.EMPTY_INFO);
1934     }
1935
1936     @Override
1937     public void setSavedFile(File savedFile) {
1938         super.setSavedFile(savedFile);
1939
1940         if (savedFile != null) {
1941             JavaController controller = new JavaController();
1942             controller.setObjectProperty(getUID(), getKind(), ObjectProperties.PATH, savedFile.getAbsolutePath());
1943         }
1944     }
1945
1946     /**
1947      * @return the title of the current diagram (root diagram or super block)
1948      */
1949     @Override
1950     public String getTitle() {
1951         JavaController controller = new JavaController();
1952         String[] property = {""};
1953
1954         if (getKind() == Kind.DIAGRAM) {
1955             controller.getObjectProperty(getUID(), getKind(), ObjectProperties.TITLE, property);
1956         } else { // Kind.BLOCK
1957             // use the one-line description
1958             controller.getObjectProperty(getUID(), getKind(), ObjectProperties.DESCRIPTION, property);
1959         }
1960
1961         if (property[0].isEmpty()) {
1962             return super.getTitle();
1963         } else {
1964             return property[0];
1965         }
1966     }
1967
1968     /**
1969      * Set the title of the diagram
1970      *
1971      * @param title the title
1972      * @see org.scilab.modules.graph.ScilabGraph#setTitle(java.lang.String)
1973      */
1974     @Override
1975     public void setTitle(final String title) {
1976         super.setTitle(title);
1977
1978         JavaController controller = new JavaController();
1979         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.TITLE, title);
1980     }
1981
1982     /**
1983      * Update the title
1984      */
1985     public void updateTabTitle() {
1986         // get the modifier string
1987         final String modified;
1988         if (Xcos.getInstance().isModified(Xcos.findRoot(this))) {
1989             modified = "*";
1990         } else {
1991             modified = "";
1992         }
1993
1994         // get the title string
1995         final String title = getTitle();
1996
1997         // get the path
1998         CharSequence formattedPath = "";
1999         if (getKind() == Kind.DIAGRAM) {
2000             final File savedFile = getSavedFile();
2001             if (savedFile != null) {
2002                 try {
2003                     final String path = savedFile.getCanonicalPath();
2004                     formattedPath = new StringBuilder().append(" (").append(path).append(')');
2005                 } catch (final IOException e) {
2006                     LOG.warning(e.toString());
2007                 }
2008             }
2009         }
2010
2011         // Product name
2012         final String product = Xcos.TRADENAME;
2013
2014         final String tabTitle = new StringBuilder().append(modified).append(title).append(formattedPath).append(" - ").append(product).toString();
2015
2016         final SwingScilabDockablePanel tab = ScilabTabFactory.getInstance().getFromCache(getGraphTab());
2017         if (tab != null) {
2018             tab.setName(tabTitle);
2019         }
2020     }
2021
2022     /**
2023      * Load a file with different method depending on it extension
2024      *
2025      * @param controller the used controller
2026      * @param file File to load (can be null)
2027      */
2028     public void transformAndLoadFile(final JavaController controller, final String file) {
2029         final File f;
2030         final XcosFileType filetype;
2031         if (file != null) {
2032             f = new File(file);
2033             filetype = XcosFileType.findFileType(f);
2034         } else {
2035             f = null;
2036             filetype = null;
2037         }
2038         new SwingWorker<XcosDiagram, ActionEvent>() {
2039             int counter = 0;
2040             final Timer t = new Timer(1000, new ActionListener() {
2041                 @Override
2042                 public void actionPerformed(ActionEvent e) {
2043                     counter = (counter + 1) % (XcosMessages.DOTS.length() + 1);
2044                     String str = XcosMessages.LOADING_DIAGRAM + XcosMessages.DOTS.substring(0, counter);
2045
2046                     XcosDiagram.this.info(str);
2047                 }
2048             });
2049
2050             @Override
2051             protected XcosDiagram doInBackground() {
2052                 t.start();
2053
2054                 final Xcos instance = Xcos.getInstance();
2055                 XcosDiagram diag = XcosDiagram.this;
2056
2057                 diag.setReadOnly(true);
2058
2059                 /*
2060                  * Load, log errors and notify
2061                  */
2062                 synchronized (instance) {
2063                     try {
2064
2065                         if (f != null && filetype != null) {
2066                             filetype.load(file, XcosDiagram.this);
2067                         } else {
2068                             XcosCellFactory.insertChildren(controller, XcosDiagram.this);
2069                         }
2070
2071                         instance.setLastError("");
2072                     } catch (Exception e) {
2073                         e.printStackTrace();
2074
2075                         Throwable ex = e;
2076                         while (ex instanceof RuntimeException) {
2077                             ex = ex.getCause();
2078                         }
2079                         try {
2080                             instance.setLastError(ex.getMessage());
2081                         } catch (NullPointerException exp) {
2082                             exp.printStackTrace();
2083                         }
2084                     }
2085                     instance.notify();
2086                 }
2087
2088                 return diag;
2089             }
2090
2091             @Override
2092             protected void done() {
2093                 t.stop();
2094                 XcosDiagram.this.setReadOnly(false);
2095                 XcosDiagram.this.getUndoManager().clear();
2096                 XcosDiagram.this.refresh();
2097
2098                 /*
2099                  * Load has finished
2100                  */
2101                 if (f != null && filetype != null) {
2102                     postLoad(f);
2103                 }
2104                 XcosDiagram.this.info(XcosMessages.EMPTY_INFO);
2105             }
2106
2107         } .execute();
2108     }
2109
2110     /**
2111      * Getting the root diagram of a decomposed diagram
2112      *
2113      * @return Root parent of the whole parent
2114      */
2115     public XcosDiagram getRootDiagram() {
2116         if (getKind() == Kind.DIAGRAM) {
2117             return this;
2118         }
2119
2120         JavaController controller = new JavaController();
2121
2122         ScicosObjectOwner root = Xcos.findRoot(controller, this);
2123         Collection<XcosDiagram> diagrams = Xcos.getInstance().getDiagrams(root);
2124         Optional<XcosDiagram> found = diagrams.stream().filter(d -> d.getUID() == root.getUID()).findFirst();
2125         if (found.isPresent()) {
2126             return found.get();
2127         } else {
2128             // create a temporary hidden root diagram
2129             String[] uid = {""};
2130             controller.getObjectProperty(root.getUID(), Kind.DIAGRAM, ObjectProperties.UID, uid);
2131             return new XcosDiagram(controller, root.getUID(), Kind.DIAGRAM, uid[0]);
2132         }
2133
2134
2135     }
2136
2137     /**
2138      * Getting the root diagram UID of a decomposed diagram
2139      *
2140      * @param controller the current JavaController
2141      * @return Root parent of the whole parent
2142      */
2143     public long getRootDiagramUID(JavaController controller) {
2144         if (getKind() == Kind.DIAGRAM) {
2145             return getUID();
2146         }
2147
2148         long[] uid = new long[1];
2149         controller.getObjectProperty(getUID(), getKind(), ObjectProperties.PARENT_DIAGRAM, uid);
2150
2151         return uid[0];
2152     }
2153
2154
2155     /**
2156      * Returns the tooltip to be used for the given cell.
2157      *
2158      * @param cell block
2159      * @return cell tooltip
2160      */
2161     @Override
2162     public String getToolTipForCell(final Object cell) {
2163         if (cell instanceof BasicBlock) {
2164             return getToolTipForCell((BasicBlock) cell);
2165         } else if (cell instanceof BasicPort) {
2166             return getToolTipForCell((BasicPort) cell);
2167         } else if (cell instanceof BasicLink) {
2168             return getToolTipForCell((BasicLink) cell);
2169         }
2170         return "";
2171     }
2172
2173     private String getToolTipForCell(final BasicBlock o) {
2174         JavaController controller = new JavaController();
2175         String[] strValue = {""};
2176         VectorOfDouble vecValue = new VectorOfDouble();
2177         VectorOfInt vecInteger = new VectorOfInt();
2178
2179         StringBuilder result = new StringBuilder();
2180         result.append(ScilabGraphConstants.HTML_BEGIN);
2181
2182         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.INTERFACE_FUNCTION, strValue);
2183         result.append(XcosMessages.TOOLTIP_BLOCK).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2184         .append(strValue[0])
2185         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2186
2187         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.SIM_FUNCTION_NAME, strValue);
2188         result.append(XcosMessages.TOOLTIP_BLOCK_SIMULATION).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2189         .append(strValue[0])
2190         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2191
2192         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.UID, strValue);
2193         result.append(XcosMessages.TOOLTIP_BLOCK_UID).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2194         .append(strValue[0])
2195         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2196
2197         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.STYLE, strValue);
2198         result.append(XcosMessages.TOOLTIP_BLOCK_STYLE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2199         appendReduced(result, strValue[0])
2200         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2201
2202         result.append(ScilabGraphConstants.HTML_NEWLINE);
2203
2204         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.RPAR, vecValue);
2205         result.append(XcosMessages.TOOLTIP_BLOCK_RPAR).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2206         appendReduced(result, ScilabTypeCoder.toString(vecValue))
2207         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2208
2209         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.IPAR, vecInteger);
2210         result.append(XcosMessages.TOOLTIP_BLOCK_IPAR).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2211         appendReduced(result, ScilabTypeCoder.toString(vecInteger))
2212         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2213
2214         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.OPAR, vecValue);
2215         result.append(XcosMessages.TOOLTIP_BLOCK_OPAR).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2216         appendReduced(result, ScilabTypeCoder.toString(vecValue))
2217         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2218
2219         result.append(ScilabGraphConstants.HTML_END);
2220         return result.toString();
2221     }
2222
2223     private String getToolTipForCell(final BasicPort o) {
2224         JavaController controller = new JavaController();
2225         boolean[] boolValue = {false};
2226         String[] strValue = {""};
2227         VectorOfInt intVecValue = new VectorOfInt();
2228
2229         StringBuilder result = new StringBuilder();
2230         result.append(ScilabGraphConstants.HTML_BEGIN);
2231
2232         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.DATATYPE, intVecValue);
2233         result.append(XcosMessages.TOOLTIP_PORT_DATATYPE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2234         formatDatatype(result, intVecValue)
2235         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2236
2237         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.IMPLICIT, boolValue);
2238         result.append(XcosMessages.TOOLTIP_PORT_IMPLICIT).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2239         .append(boolValue[0])
2240         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2241
2242         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.STYLE, strValue);
2243         result.append(XcosMessages.TOOLTIP_PORT_STYLE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2244         appendReduced(result, strValue[0])
2245         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2246
2247         result.append(ScilabGraphConstants.HTML_END);
2248         return result.toString();
2249     }
2250
2251     private String getToolTipForCell(final BasicLink o) {
2252         JavaController controller = new JavaController();
2253         long[] longValue = {0l};
2254         String[] strValue = {""};
2255         VectorOfInt intVecValue = new VectorOfInt();
2256
2257         StringBuilder result = new StringBuilder();
2258         result.append(ScilabGraphConstants.HTML_BEGIN);
2259
2260         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.SOURCE_PORT, longValue);
2261         if (longValue[0] != 0l) {
2262             controller.getObjectProperty(longValue[0], Kind.PORT, ObjectProperties.DATATYPE, intVecValue);
2263             result.append(XcosMessages.TOOLTIP_LINK_SRC_DATATYPE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2264             formatDatatype(result, intVecValue)
2265             .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2266         }
2267         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.DESTINATION_PORT, longValue);
2268         if (longValue[0] != 0l) {
2269             controller.getObjectProperty(longValue[0], Kind.PORT, ObjectProperties.DATATYPE, intVecValue);
2270             result.append(XcosMessages.TOOLTIP_LINK_TRG_DATATYPE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2271             formatDatatype(result, intVecValue)
2272             .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2273         }
2274
2275         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.LABEL, strValue);
2276         result.append(XcosMessages.TOOLTIP_LINK_LABEL).append(ScilabGraphConstants.HTML_BEGIN_CODE)
2277         .append(strValue[0])
2278         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2279
2280         controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.STYLE, strValue);
2281         result.append(XcosMessages.TOOLTIP_LINK_STYLE).append(ScilabGraphConstants.HTML_BEGIN_CODE);
2282         appendReduced(result, strValue[0])
2283         .append(ScilabGraphConstants.HTML_END_CODE).append(ScilabGraphConstants.HTML_NEWLINE);
2284
2285         result.append(ScilabGraphConstants.HTML_END);
2286         return result.toString();
2287     }
2288
2289     private StringBuilder appendReduced(final StringBuilder result, final String msg) {
2290         if (msg.length() > XcosConstants.MAX_CHAR_IN_STYLE) {
2291             result.append(msg.substring(0, XcosConstants.MAX_CHAR_IN_STYLE));
2292             result.append(XcosMessages.DOTS);
2293         } else {
2294             result.append(msg);
2295         }
2296
2297         return result;
2298     }
2299
2300     private StringBuilder formatDatatype(final StringBuilder result, final VectorOfInt intVecValue) {
2301         if (intVecValue.size() != 3) {
2302             result.append(ScilabTypeCoder.toString(intVecValue));
2303         } else {
2304             // this is a known encoding, output representative strings
2305             int rows = intVecValue.get(0);
2306             int cols = intVecValue.get(1);
2307             int type = intVecValue.get(2);
2308
2309             String strType;
2310             // should be similar to the naming used on scicos_model doc
2311             String[] typeTable = {"real", "complex", "int32", "int16", "int8", "uint32", "uint16", "uint8"};
2312             if (0 <= type && type < typeTable.length) {
2313                 strType = typeTable[type - 1];
2314             } else {
2315                 strType = "auto";
2316             }
2317
2318             result.append(String.format("%s [%d %d]", strType, rows, cols));
2319         }
2320
2321         return result;
2322     }
2323
2324     /**
2325      * Display the message in info bar.
2326      *
2327      * @param message Information
2328      */
2329     public void info(final String message) {
2330         final XcosTab tab = XcosTab.get(this);
2331
2332         if (tab != null && tab.getInfoBar() != null) {
2333             tab.getInfoBar().setText(message);
2334         }
2335     }
2336
2337     /**
2338      * Display the message into an error popup
2339      *
2340      * @param message Error of the message
2341      */
2342     public void error(final String message) {
2343         JOptionPane.showMessageDialog(getAsComponent(), message, XcosMessages.XCOS, JOptionPane.ERROR_MESSAGE);
2344     }
2345
2346     /**
2347      * Find the block corresponding to the given uid and display a warning
2348      * message.
2349      *
2350      * @param uid - A String as UID.
2351      * @param message - The message to display.
2352      */
2353     public void warnCellByUID(final String uid, final String message) {
2354         final Object cell = ((mxGraphModel) getModel()).getCell(uid);
2355         if (cell == null) {
2356             return;
2357         }
2358
2359         if (GraphicsEnvironment.isHeadless()) {
2360             System.err.printf("%s at %s%n    %s: %s%n", "warnCell", getRootDiagram().getTitle(), uid, message);
2361             return;
2362         }
2363
2364         // open the tab
2365         if (XcosTab.get(this) == null) {
2366             XcosTab.restore(this);
2367         }
2368
2369         if (message.isEmpty()) {
2370             getAsComponent().clearCellOverlays(cell);
2371         } else {
2372             getAsComponent().setCellWarning(cell, message, null, true);
2373         }
2374     }
2375
2376     @Override
2377     public File getSavedFile() {
2378         if (getKind() == Kind.DIAGRAM) {
2379             return super.getSavedFile();
2380         } else {
2381             return getRootDiagram().getSavedFile();
2382         }
2383     }
2384
2385     /**
2386      * Read the applicable context on this diagram.
2387      * <p>
2388      * This function retrieve the current diagram's context and all its parent
2389      *
2390      * @return the full context
2391      */
2392     public String[] getContext() {
2393         final ArrayList<String> allContext = new ArrayList<>();
2394         final Stack<ScicosObjectOwner> hierarchy = lookForHierarchy(new ScicosObjectOwner(getUID(), getKind()));
2395
2396         final JavaController controller = new JavaController();
2397         final VectorOfString context = new VectorOfString();
2398
2399         hierarchy.stream().forEach(o -> {
2400             controller.getObjectProperty(o.getUID(), o.getKind(), ObjectProperties.DIAGRAM_CONTEXT, context);
2401
2402             final int length = context.size();
2403             for (int i = 0; i < length; i++) {
2404                 allContext.add(context.get(i));
2405             }
2406             allContext.add("");
2407         });
2408
2409         return allContext.toArray(new String[allContext.size()]);
2410     }
2411
2412     /**
2413      * Evaluate the current context
2414      *
2415      * @return The resulting data. Keys are variable names and Values are
2416      * evaluated values.
2417      */
2418     public Map<String, ScilabType> evaluateContext() {
2419         Map<String, ScilabType> result = Collections.emptyMap();
2420         final ScilabDirectHandler handler = ScilabDirectHandler.acquire();
2421         if (handler == null) {
2422             return result;
2423         }
2424
2425         try {
2426             // first write the context strings
2427             handler.writeContext(getContext());
2428
2429             // evaluate using script2var and convert to string keys and list of values
2430             ScilabInterpreterManagement.synchronousScilabExec(ScilabDirectHandler.CONTEXT + " = script2var(" + ScilabDirectHandler.CONTEXT + ", struct()); "
2431                  + ScilabDirectHandler.CONTEXT + "_names = fieldnames("+ScilabDirectHandler.CONTEXT+")'; "
2432                  + ScilabDirectHandler.CONTEXT + "_values = list(); "
2433                  + "for i=1:size(" + ScilabDirectHandler.CONTEXT + "_names, '*') ;"
2434                  + "   " + ScilabDirectHandler.CONTEXT + "_values(i) = " + ScilabDirectHandler.CONTEXT + "(" +  ScilabDirectHandler.CONTEXT + "_names(i));"
2435                  + "end");
2436
2437             // read the structure
2438             result = handler.readContext();
2439         } catch (final InterpreterException e) {
2440             info("Unable to evaluate the context");
2441             e.printStackTrace();
2442         } finally {
2443             handler.release();
2444         }
2445
2446         return result;
2447     }
2448
2449     /**
2450      * Returns true if the given cell is a not a block nor a port.
2451      *
2452      * @param cell the drop target
2453      * @param cells the cells to be dropped
2454      * @return the drop status
2455      * @see com.mxgraph.view.mxGraph#isValidDropTarget(java.lang.Object,
2456      * java.lang.Object[])
2457      */
2458     @Override
2459     public boolean isValidDropTarget(final Object cell, final Object[] cells) {
2460         return !(cell instanceof BasicBlock) && !(cell instanceof BasicBlock) && !(cell instanceof BasicPort) && super.isValidDropTarget(cell, cells);
2461     }
2462
2463     /**
2464      * Construct a new selection model used on this graph.
2465      *
2466      * @return a new selection model instance.
2467      * @see com.mxgraph.view.mxGraph#createSelectionModel()
2468      */
2469     @Override
2470     protected mxGraphSelectionModel createSelectionModel() {
2471         return new mxGraphSelectionModel(this) {
2472             /**
2473              * When we only want to select a cell which is a port, select the
2474              * parent block.
2475              *
2476              * @param cell the cell
2477              * @see
2478              * com.mxgraph.view.mxGraphSelectionModel#setCell(java.lang.Object)
2479              */
2480             @Override
2481             public void setCell(final Object cell) {
2482                 final Object current;
2483                 if (cell instanceof BasicPort) {
2484                     current = getModel().getParent(cell);
2485                 } else {
2486                     current = cell;
2487                 }
2488                 super.setCell(current);
2489             }
2490         };
2491     }
2492
2493     /**
2494      * Counts the number of children in the diagram
2495      *
2496      * @return the number of children in the diagram
2497      */
2498     public int countChildren() {
2499         return getModel().getChildCount(this.getDefaultParent());
2500     }
2501 }