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