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