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