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