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