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