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