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