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