* Bug #13030 fixed - Selection to Super block did not reset the origin
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / graph / XcosDiagram.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009-2009 - DIGITEO - Bruno JOFRET
4  * Copyright (C) 2009-2010 - DIGITEO - Clement DAVID
5  *
6  * This file must be used under the terms of the CeCILL.
7  * This source file is licensed as described in the file COPYING, which
8  * you should have received as part of this distribution.  The terms
9  * are also available at
10  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
11  *
12  */
13
14 package org.scilab.modules.xcos.graph;
15
16 import java.awt.GraphicsEnvironment;
17 import java.awt.event.ActionEvent;
18 import java.awt.event.ActionListener;
19 import java.beans.PropertyChangeEvent;
20 import java.beans.PropertyChangeListener;
21 import java.beans.PropertyVetoException;
22 import java.io.File;
23 import java.io.IOException;
24 import java.rmi.server.UID;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.Comparator;
30 import java.util.HashSet;
31 import java.util.Hashtable;
32 import java.util.IllegalFormatException;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Queue;
37 import java.util.Set;
38 import java.util.logging.Logger;
39
40 import javax.swing.JFileChooser;
41 import javax.swing.JOptionPane;
42 import javax.swing.SwingWorker;
43 import javax.swing.Timer;
44
45 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement;
46 import org.scilab.modules.action_binding.highlevel.ScilabInterpreterManagement.InterpreterException;
47 import org.scilab.modules.graph.ScilabGraph;
48 import org.scilab.modules.graph.utils.ScilabGraphConstants;
49 import org.scilab.modules.gui.bridge.filechooser.SwingScilabFileChooser;
50 import org.scilab.modules.gui.bridge.tab.SwingScilabTab;
51 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
52 import org.scilab.modules.gui.messagebox.ScilabModalDialog.AnswerOption;
53 import org.scilab.modules.gui.messagebox.ScilabModalDialog.ButtonType;
54 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
55 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
56 import org.scilab.modules.types.ScilabDouble;
57 import org.scilab.modules.types.ScilabMList;
58 import org.scilab.modules.types.ScilabString;
59 import org.scilab.modules.xcos.Xcos;
60 import org.scilab.modules.xcos.XcosTab;
61 import org.scilab.modules.xcos.actions.SaveAsAction;
62 import org.scilab.modules.xcos.block.AfficheBlock;
63 import org.scilab.modules.xcos.block.BasicBlock;
64 import org.scilab.modules.xcos.block.BlockFactory;
65 import org.scilab.modules.xcos.block.BlockFactory.BlockInterFunction;
66 import org.scilab.modules.xcos.block.SplitBlock;
67 import org.scilab.modules.xcos.block.SuperBlock;
68 import org.scilab.modules.xcos.block.TextBlock;
69 import org.scilab.modules.xcos.block.io.ContextUpdate;
70 import org.scilab.modules.xcos.block.io.EventInBlock;
71 import org.scilab.modules.xcos.block.io.EventOutBlock;
72 import org.scilab.modules.xcos.block.io.ExplicitInBlock;
73 import org.scilab.modules.xcos.block.io.ExplicitOutBlock;
74 import org.scilab.modules.xcos.block.io.ImplicitInBlock;
75 import org.scilab.modules.xcos.block.io.ImplicitOutBlock;
76 import org.scilab.modules.xcos.configuration.ConfigurationManager;
77 import org.scilab.modules.xcos.graph.swing.GraphComponent;
78 import org.scilab.modules.xcos.io.XcosFileType;
79 import org.scilab.modules.xcos.io.scicos.ScilabDirectHandler;
80 import org.scilab.modules.xcos.link.BasicLink;
81 import org.scilab.modules.xcos.link.commandcontrol.CommandControlLink;
82 import org.scilab.modules.xcos.link.explicit.ExplicitLink;
83 import org.scilab.modules.xcos.link.implicit.ImplicitLink;
84 import org.scilab.modules.xcos.port.BasicPort;
85 import org.scilab.modules.xcos.port.BasicPort.Type;
86 import org.scilab.modules.xcos.port.Orientation;
87 import org.scilab.modules.xcos.port.PortCheck;
88 import org.scilab.modules.xcos.port.command.CommandPort;
89 import org.scilab.modules.xcos.port.control.ControlPort;
90 import org.scilab.modules.xcos.port.input.ExplicitInputPort;
91 import org.scilab.modules.xcos.port.input.ImplicitInputPort;
92 import org.scilab.modules.xcos.port.output.ExplicitOutputPort;
93 import org.scilab.modules.xcos.port.output.ImplicitOutputPort;
94 import org.scilab.modules.xcos.preferences.XcosOptions;
95 import org.scilab.modules.xcos.utils.BlockPositioning;
96 import org.scilab.modules.xcos.utils.XcosConstants;
97 import org.scilab.modules.xcos.utils.XcosDialogs;
98 import org.scilab.modules.xcos.utils.XcosEvent;
99 import org.scilab.modules.xcos.utils.XcosMessages;
100
101 import com.mxgraph.model.mxCell;
102 import com.mxgraph.model.mxGeometry;
103 import com.mxgraph.model.mxGraphModel;
104 import com.mxgraph.model.mxGraphModel.Filter;
105 import com.mxgraph.model.mxGraphModel.mxChildChange;
106 import com.mxgraph.model.mxGraphModel.mxStyleChange;
107 import com.mxgraph.model.mxICell;
108 import com.mxgraph.model.mxIGraphModel;
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         geometry.setPoints(points);
1148         getModel().setGeometry(newLink1, geometry);
1149
1150         /*
1151          * Update the geometry
1152          */
1153         // should be cloned to generate an event
1154         geometry = (mxGeometry) getModel().getGeometry(newLink1).clone();
1155         final double dx = orig.getX();
1156         final double dy = orig.getY();
1157
1158         geometry.translate(dx, dy);
1159         getModel().setGeometry(newLink1, geometry);
1160     }
1161
1162     /**
1163      * Initialize component settings for a graph.
1164      *
1165      * This method *must* be used to setup the component after any
1166      * reassociation.
1167      */
1168     public void initComponent() {
1169         getAsComponent().setToolTips(true);
1170
1171         // This enable stop editing cells when pressing Enter.
1172         getAsComponent().setEnterStopsCellEditing(false);
1173
1174         getAsComponent().setTolerance(1);
1175
1176         getAsComponent().getViewport().setOpaque(false);
1177
1178         getAsComponent().setBackground(XcosOptions.getEdition().getGraphBackground());
1179
1180         final boolean gridEnable = XcosOptions.getEdition().isGraphGridEnable();
1181         setGridVisible(gridEnable);
1182         if (gridEnable) {
1183             setGridSize(XcosOptions.getEdition().getGraphGrid());
1184         }
1185
1186         /*
1187          * Reinstall related listeners
1188          */
1189
1190         // Property change Listener
1191         // Will say if a diagram has been modified or not.
1192         final PropertyChangeListener p = new PropertyChangeListener() {
1193             @Override
1194             public void propertyChange(final PropertyChangeEvent e) {
1195                 if (e.getPropertyName().compareTo(MODIFIED) == 0) {
1196                     if (!e.getOldValue().equals(e.getNewValue())) {
1197                         updateTabTitle();
1198                     }
1199                 }
1200             }
1201         };
1202         getAsComponent().removePropertyChangeListener(MODIFIED, p);
1203         getAsComponent().addPropertyChangeListener(MODIFIED, p);
1204     }
1205
1206     /**
1207      * Install the default style sheet and the user stylesheet on the diagram.
1208      */
1209     public void installStylesheet() {
1210         final mxStylesheet styleSheet = Xcos.getInstance().getStyleSheet();
1211         setStylesheet(styleSheet);
1212     }
1213
1214     /**
1215      * Install the multiplicities (use for link checking)
1216      */
1217     private void setMultiplicities() {
1218         final List<mxMultiplicity> multiplicities = new ArrayList<mxMultiplicity>();
1219
1220         // Input data port
1221         multiplicities.add(new PortCheck(ExplicitInputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1222             private static final long serialVersionUID = -4987163442006736665L;
1223             {
1224                 add(ExplicitOutputPort.class);
1225                 add(ExplicitLink.class);
1226             }
1227         }), XcosMessages.LINK_ERROR_EXPLICIT_IN));
1228         multiplicities.add(new PortCheck(ImplicitInputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1229             private static final long serialVersionUID = 886376532181210926L;
1230             {
1231                 add(ImplicitOutputPort.class);
1232                 add(ImplicitInputPort.class);
1233                 add(ImplicitLink.class);
1234             }
1235         }), XcosMessages.LINK_ERROR_IMPLICIT_IN));
1236
1237         // Output data port
1238         multiplicities.add(new PortCheck(ExplicitOutputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1239             private static final long serialVersionUID = 4594127972486054821L;
1240             {
1241                 add(ExplicitInputPort.class);
1242             }
1243         }), XcosMessages.LINK_ERROR_EXPLICIT_OUT));
1244         multiplicities.add(new PortCheck(ImplicitOutputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1245             private static final long serialVersionUID = -3719677806532507973L;
1246             {
1247                 add(ImplicitInputPort.class);
1248                 add(ImplicitOutputPort.class);
1249                 add(ImplicitLink.class);
1250             }
1251         }), XcosMessages.LINK_ERROR_IMPLICIT_OUT));
1252
1253         // Control port
1254         multiplicities.add(new PortCheck(ControlPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1255             private static final long serialVersionUID = 2941077191386058497L;
1256             {
1257                 add(CommandPort.class);
1258                 add(CommandControlLink.class);
1259             }
1260         }), XcosMessages.LINK_ERROR_EVENT_IN));
1261
1262         // Command port
1263         multiplicities.add(new PortCheck(CommandPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1264             private static final long serialVersionUID = -3470370027962480362L;
1265             {
1266                 add(ControlPort.class);
1267             }
1268         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1269
1270         // ExplicitLink connections
1271         multiplicities.add(new PortCheck(ExplicitLink.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1272             private static final long serialVersionUID = 7423543162930147373L;
1273
1274             {
1275                 add(ExplicitInputPort.class);
1276             }
1277         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1278
1279         // ImplicitLink connections
1280         multiplicities.add(new PortCheck(ImplicitLink.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1281             private static final long serialVersionUID = 7775100011122283282L;
1282
1283             {
1284                 add(ImplicitInputPort.class);
1285                 add(ImplicitOutputPort.class);
1286             }
1287         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1288
1289         // CommandControlLink connections
1290         multiplicities.add(new PortCheck(CommandControlLink.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1291             private static final long serialVersionUID = 3260421433507192386L;
1292
1293             {
1294                 add(ControlPort.class);
1295             }
1296         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1297
1298         // Already connected port
1299         multiplicities.add(new PortCheck(BasicPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1300             private static final long serialVersionUID = 6376349598052836660L;
1301
1302             {
1303                 add(BasicPort.class);
1304             }
1305         }), XcosMessages.LINK_ERROR_ALREADY_CONNECTED));
1306
1307         setMultiplicities(multiplicities.toArray(new mxMultiplicity[multiplicities.size()]));
1308     }
1309
1310     /**
1311      * Install all needed Listeners.
1312      */
1313     public void installListeners() {
1314         /*
1315          * First remove all listeners if present
1316          */
1317         removeListener(SuperBlockUpdateTracker.getInstance());
1318         removeListener(CellAddedTracker.getInstance());
1319         removeListener(getEngine());
1320         getModel().removeListener(getEngine());
1321         removeListener(CellResizedTracker.getInstance());
1322         getUndoManager().removeListener(UndoUpdateTracker.getInstance());
1323         removeListener(RefreshBlockTracker.getInstance());
1324
1325         // Track when superblock ask a parent refresh.
1326         addListener(XcosEvent.SUPER_BLOCK_UPDATED, SuperBlockUpdateTracker.getInstance());
1327
1328         // Track when cells are added.
1329         addListener(mxEvent.CELLS_ADDED, CellAddedTracker.getInstance());
1330         addListener(mxEvent.CELLS_ADDED, getEngine());
1331
1332         // Track when cells are deleted.
1333         addListener(mxEvent.CELLS_REMOVED, getEngine());
1334
1335         // Track when resizing a cell.
1336         addListener(mxEvent.CELLS_RESIZED, CellResizedTracker.getInstance());
1337
1338         // Track when we have to force a Block value
1339         addListener(XcosEvent.FORCE_CELL_VALUE_UPDATE, getEngine());
1340         getModel().addListener(XcosEvent.FORCE_CELL_VALUE_UPDATE, getEngine());
1341
1342         // Update the blocks view on undo/redo
1343         getUndoManager().addListener(mxEvent.UNDO, UndoUpdateTracker.getInstance());
1344         getUndoManager().addListener(mxEvent.REDO, UndoUpdateTracker.getInstance());
1345
1346         // Refresh port position on update
1347         addListener(XcosEvent.ADD_PORTS, RefreshBlockTracker.getInstance());
1348
1349     }
1350
1351     /**
1352      * Removes the given cells from the graph including all connected edges if
1353      * includeEdges is true. The change is carried out using cellsRemoved.
1354      *
1355      * @param cells
1356      *            the cells to be removed
1357      * @param includeEdges
1358      *            true if the edges must be removed, false otherwise.
1359      * @return the deleted cells
1360      * @see com.mxgraph.view.mxGraph#removeCells(java.lang.Object[], boolean)
1361      */
1362     @Override
1363     public Object[] removeCells(final Object[] cells, final boolean includeEdges) {
1364         if (cells == null || cells.length == 0) {
1365             return super.removeCells(cells, includeEdges);
1366         }
1367
1368         /*
1369          * First remove all links connected to a removed Split if applicable
1370          */
1371         final Object[] initialCells;
1372         if (includeEdges) {
1373             initialCells = addAllEdges(cells);
1374         } else {
1375             initialCells = cells;
1376         }
1377
1378         // stash used on the loop
1379         final Queue<Object> loopCells = new LinkedList<Object>(Arrays.asList(initialCells));
1380         // the cells that need to be really
1381         final Set<Object> removedCells = new HashSet<Object>(loopCells);
1382         // couple of cells to reconnect
1383         final List<BasicPort[]> connectedCells = new ArrayList<BasicPort[]>();
1384         final List<List<mxPoint>> connectedPoints = new ArrayList<List<mxPoint>>();
1385
1386         /*
1387          * Then loop on the algorithm to select the right edges
1388          */
1389         // /!\ not bounded algorithm
1390         while (loopCells.size() > 0) {
1391             Object cell = loopCells.remove();
1392
1393             if (cell instanceof BasicLink) {
1394                 /*
1395                  * Continue on non fully connected links
1396                  */
1397                 if (((BasicLink) cell).getSource() == null) {
1398                     continue;
1399                 }
1400                 if (((BasicLink) cell).getTarget() == null) {
1401                     continue;
1402                 }
1403
1404                 /*
1405                  * Add any split to a link
1406                  */
1407                 addTerminalParent(((BasicLink) cell).getSource(), removedCells, loopCells);
1408                 addTerminalParent(((BasicLink) cell).getTarget(), removedCells, loopCells);
1409
1410             } else if (cell instanceof SplitBlock) {
1411                 final SplitBlock splitBlock = (SplitBlock) cell;
1412
1413                 /*
1414                  * Remove related connection or not and reconnect.
1415                  */
1416
1417                 if (splitBlock.getIn().getEdgeCount() == 0 || splitBlock.getOut1().getEdgeCount() == 0 || splitBlock.getOut2().getEdgeCount() == 0) {
1418                     // corner case, all links will be removed
1419                     continue;
1420                 }
1421
1422                 final mxICell inLink = splitBlock.getIn().getEdgeAt(0);
1423                 final mxICell out1Link = splitBlock.getOut1().getEdgeAt(0);
1424                 final mxICell out2Link = splitBlock.getOut2().getEdgeAt(0);
1425
1426                 final boolean inRemoved = removedCells.contains(inLink);
1427                 final boolean out1Removed = removedCells.contains(out1Link);
1428                 final boolean out2Removed = removedCells.contains(out2Link);
1429
1430                 /*
1431                  * Explicit case, if the in link is deleted; all the out links
1432                  * also should.
1433                  */
1434                 if (inLink instanceof ExplicitLink && inRemoved) {
1435                     if (removedCells.add(out1Link)) {
1436                         loopCells.add(out1Link);
1437                     }
1438
1439                     if (removedCells.add(out2Link)) {
1440                         loopCells.add(out2Link);
1441                     }
1442                 }
1443
1444                 /*
1445                  * Global case reconnect if not removed
1446                  */
1447                 final BasicPort[] connection;
1448                 List<mxPoint> points = null;
1449                 if (!inRemoved && !out1Removed && out2Removed) {
1450                     connection = findTerminals(inLink, out1Link, removedCells);
1451                     points = getDirectPoints(splitBlock, inLink, out1Link);
1452                 } else if (!inRemoved && out1Removed && !out2Removed) {
1453                     connection = findTerminals(inLink, out2Link, removedCells);
1454                     points = getDirectPoints(splitBlock, inLink, out2Link);
1455                 } else if (inRemoved && !out1Removed && !out2Removed) {
1456                     // only implicit or event case, log otherwise
1457                     if (out1Link instanceof ExplicitLink || out2Link instanceof ExplicitLink) {
1458                         LOG.severe("Reconnection failed for explicit links");
1459                         connection = null;
1460                     } else {
1461                         connection = findTerminals(out1Link, out2Link, removedCells);
1462                         points = getDirectPoints(splitBlock, out1Link, out2Link);
1463                     }
1464                 } else {
1465                     connection = null;
1466                 }
1467
1468                 if (connection != null) {
1469                     connectedCells.add(connection);
1470                     connectedPoints.add(points);
1471                 }
1472             }
1473         }
1474
1475         final Object[] ret;
1476         getModel().beginUpdate();
1477         try {
1478             ret = super.removeCells(removedCells.toArray(), includeEdges);
1479             for (int i = 0; i < connectedCells.size(); i++) {
1480                 final BasicPort[] connection = connectedCells.get(i);
1481                 final List<mxPoint> points = connectedPoints.get(i);
1482                 if (!removedCells.contains(connection[0].getParent()) && !removedCells.contains(connection[1].getParent())) {
1483                     connect(connection[0], connection[1], points, new mxPoint());
1484                 }
1485             }
1486         } finally {
1487             getModel().endUpdate();
1488         }
1489         return ret;
1490     }
1491
1492     /**
1493      * Add any terminal parent to the removed cells
1494      *
1495      * @param terminal
1496      *            the current terminal (instance of BasicPort)
1497      * @param removedCells
1498      *            the "to be removed" set
1499      * @param loopCells
1500      *            the "while loop" set
1501      */
1502     private void addTerminalParent(mxICell terminal, Collection<Object> removedCells, Collection<Object> loopCells) {
1503         assert (terminal == null || terminal instanceof BasicPort);
1504         assert (removedCells != null);
1505         assert (loopCells != null);
1506
1507         // getting terminal parent
1508         mxICell target = null;
1509         if (terminal != null) {
1510             target = terminal.getParent();
1511         } else {
1512             target = null;
1513         }
1514
1515         // add target if applicable
1516         if (target instanceof SplitBlock) {
1517             if (removedCells.add(target)) {
1518                 loopCells.add(target);
1519             }
1520         }
1521     }
1522
1523     /**
1524      * Find the terminals when relinking the 2 links
1525      *
1526      * This method ensure that {source, target} are not child of removed blocks.
1527      *
1528      * @param linkSource
1529      *            the normal source link
1530      * @param linkTerminal
1531      *            the normal target link
1532      * @param removedCells
1533      *            the set of removed objects
1534      * @return the {source, target} connection
1535      */
1536     private BasicPort[] findTerminals(final mxICell linkSource, final mxICell linkTerminal, final Set<Object> removedCells) {
1537         BasicPort src = (BasicPort) linkSource.getTerminal(true);
1538         BasicPort tgt = (BasicPort) linkTerminal.getTerminal(false);
1539         if (linkSource instanceof ImplicitLink) {
1540             if (removedCells.contains(src.getParent())) {
1541                 src = (BasicPort) linkSource.getTerminal(false);
1542             }
1543             if (removedCells.contains(tgt.getParent())) {
1544                 tgt = (BasicPort) linkTerminal.getTerminal(true);
1545             }
1546         }
1547
1548         return new BasicPort[] { src, tgt };
1549     }
1550
1551     /**
1552      * Get the direct points from inLink.getSource() to outLink.getTarget().
1553      *
1554      * @param splitBlock
1555      *            the current splitblock (added as a mid-point)
1556      * @param inLink
1557      *            the link before the split
1558      * @param outLink
1559      *            the link after the split
1560      * @return the points
1561      */
1562     private List<mxPoint> getDirectPoints(final SplitBlock splitBlock, final mxICell inLink, final mxICell outLink) {
1563         List<mxPoint> points;
1564         // add the points before the split
1565         points = new ArrayList<mxPoint>();
1566         if (inLink.getGeometry().getPoints() != null) {
1567             points.addAll(inLink.getGeometry().getPoints());
1568         }
1569
1570         // add a new point at the split location
1571         points.add(new mxPoint(snap(splitBlock.getGeometry().getCenterX()), snap(splitBlock.getGeometry().getCenterY())));
1572
1573         // add the points after the split
1574         if (outLink.getGeometry().getPoints() != null) {
1575             points.addAll(outLink.getGeometry().getPoints());
1576         }
1577
1578         return points;
1579     }
1580
1581     /**
1582      * Manage Group to be CellFoldable i.e with a (-) to reduce and a (+) to
1583      * expand them. Labels (mxCell instance with value) should not have a
1584      * visible foldable sign.
1585      *
1586      * @param cell
1587      *            the selected cell
1588      * @param collapse
1589      *            the collapse settings
1590      * @return always <code>false</code>
1591      * @see com.mxgraph.view.mxGraph#isCellFoldable(java.lang.Object, boolean)
1592      */
1593     @Override
1594     public boolean isCellFoldable(final Object cell, final boolean collapse) {
1595         return false;
1596     }
1597
1598     /**
1599      * Not BasicBLock cell have a moveable label.
1600      *
1601      * @param cell
1602      *            the cell
1603      * @return true if the corresponding label is moveable
1604      * @see com.mxgraph.view.mxGraph#isLabelMovable(java.lang.Object)
1605      */
1606     @Override
1607     public boolean isLabelMovable(Object cell) {
1608         return !(cell instanceof BasicBlock);
1609     }
1610
1611     /**
1612      * Return true if selectable
1613      *
1614      * @param cell
1615      *            the cell
1616      * @return status
1617      * @see com.mxgraph.view.mxGraph#isCellSelectable(java.lang.Object)
1618      */
1619     @Override
1620     public boolean isCellSelectable(final Object cell) {
1621         if (cell instanceof BasicPort) {
1622             return false;
1623         }
1624         return super.isCellSelectable(cell);
1625     }
1626
1627     /**
1628      * Return true if movable
1629      *
1630      * @param cell
1631      *            the cell
1632      * @return status
1633      * @see com.mxgraph.view.mxGraph#isCellMovable(java.lang.Object)
1634      */
1635     @Override
1636     public boolean isCellMovable(final Object cell) {
1637         if (cell instanceof BasicPort) {
1638             return false;
1639         }
1640
1641         boolean movable = false;
1642         final Object[] cells = getSelectionCells();
1643
1644         // don't move if selection is only links
1645         for (Object c : cells) {
1646             if (!(c instanceof BasicLink)) {
1647                 movable = true;
1648                 break;
1649             }
1650         }
1651
1652         return movable && super.isCellMovable(cell);
1653     }
1654
1655     /**
1656      * Return true if resizable
1657      *
1658      * @param cell
1659      *            the cell
1660      * @return status
1661      * @see com.mxgraph.view.mxGraph#isCellResizable(java.lang.Object)
1662      */
1663     @Override
1664     public boolean isCellResizable(final Object cell) {
1665         if (cell instanceof SplitBlock) {
1666             return false;
1667         }
1668         return (cell instanceof BasicBlock) && super.isCellResizable(cell);
1669     }
1670
1671     /**
1672      * A cell is deletable if it is not a locked block or an identifier cell
1673      *
1674      * @param cell
1675      *            the cell
1676      * @return status
1677      * @see com.mxgraph.view.mxGraph#isCellDeletable(java.lang.Object)
1678      */
1679     @Override
1680     public boolean isCellDeletable(final Object cell) {
1681         final boolean isALockedBLock = cell instanceof BasicBlock && ((BasicBlock) cell).isLocked();
1682         final boolean isAnIdentifier = cell.getClass().equals(mxCell.class);
1683
1684         if (isALockedBLock) {
1685             return false;
1686         }
1687         if (isAnIdentifier) {
1688             return true;
1689         }
1690
1691         return super.isCellDeletable(cell);
1692     }
1693
1694     /**
1695      * Return true if editable
1696      *
1697      * @param cell
1698      *            the cell
1699      * @return status
1700      * @see com.mxgraph.view.mxGraph#isCellEditable(java.lang.Object)
1701      */
1702     @Override
1703     public boolean isCellEditable(final Object cell) {
1704         return (cell instanceof TextBlock) && super.isCellDeletable(cell);
1705     }
1706
1707     /**
1708      * Return or create the identifier for the cell
1709      *
1710      * @param cell
1711      *            the cell to check
1712      * @return the identifier cell
1713      */
1714     public mxCell getOrCreateCellIdentifier(final mxCell cell) {
1715         final mxGraphModel graphModel = (mxGraphModel) getModel();
1716
1717         final mxCell identifier;
1718         final String cellId = cell.getId() + HASH_IDENTIFIER;
1719         if (graphModel.getCell(cellId) == null) {
1720             // create the identifier
1721             identifier = createCellIdentifier(cell);
1722
1723             // add the identifier
1724             graphModel.add(cell, identifier, cell.getChildCount());
1725         } else {
1726             identifier = (mxCell) graphModel.getCell(cellId);
1727         }
1728         return identifier;
1729     }
1730
1731     /**
1732      * Return the identifier for the cell
1733      *
1734      * @param cell
1735      *            the cell to check
1736      * @return the identifier cell
1737      */
1738     public mxCell getCellIdentifier(final mxCell cell) {
1739         final mxGraphModel graphModel = (mxGraphModel) getModel();
1740         final String cellId = cell.getId() + HASH_IDENTIFIER;
1741
1742         return (mxCell) graphModel.getCell(cellId);
1743     }
1744
1745     /**
1746      * Create a cell identifier for a specific cell
1747      *
1748      * @param cell
1749      *            the cell
1750      * @return the cell identifier.
1751      */
1752     public mxCell createCellIdentifier(final mxCell cell) {
1753         final mxCell identifier;
1754         final String cellId = cell.getId() + HASH_IDENTIFIER;
1755
1756         identifier = new mxCell(null, (mxGeometry) DEFAULT_LABEL_GEOMETRY.clone(), "noLabel=0;opacity=0;");
1757         identifier.getGeometry().setRelative(true);
1758         identifier.setVertex(true);
1759         identifier.setConnectable(false);
1760         identifier.setId(cellId);
1761
1762         return identifier;
1763     }
1764
1765     /**
1766      * Get the label for the cell according to its style.
1767      *
1768      * @param cell
1769      *            the cell object
1770      * @return a representative the string (block name) or a style specific
1771      *         style.
1772      * @see com.mxgraph.view.mxGraph#convertValueToString(java.lang.Object)
1773      */
1774     @Override
1775     public String convertValueToString(Object cell) {
1776         String ret = null;
1777
1778         if (cell != null) {
1779             final Map<String, Object> style = getCellStyle(cell);
1780
1781             final String displayedLabel = (String) style.get("displayedLabel");
1782             if (displayedLabel != null) {
1783                 if (cell instanceof BasicBlock) {
1784                     try {
1785                         ret = String.format(displayedLabel, ((BasicBlock) cell).getExprsFormat());
1786                     } catch (IllegalFormatException e) {
1787                         LOG.severe(e.toString());
1788                         ret = displayedLabel;
1789                     }
1790                 } else {
1791                     ret = displayedLabel;
1792                 }
1793             } else {
1794                 final String label = super.convertValueToString(cell);
1795                 if (label.isEmpty() && cell instanceof BasicBlock) {
1796                     ret = ((BasicBlock) cell).getInterfaceFunctionName();
1797                 } else {
1798                     ret = label;
1799                 }
1800             }
1801         }
1802
1803         return ret;
1804     }
1805
1806     /**
1807      * Return true if auto sized
1808      *
1809      * @param cell
1810      *            the cell
1811      * @return status
1812      * @see com.mxgraph.view.mxGraph#isAutoSizeCell(java.lang.Object)
1813      */
1814     @Override
1815     public boolean isAutoSizeCell(final Object cell) {
1816         boolean status = super.isAutoSizeCell(cell);
1817
1818         if (cell instanceof AfficheBlock) {
1819             status |= true;
1820         }
1821
1822         if (cell instanceof TextBlock) {
1823             status &= false;
1824         }
1825
1826         return status;
1827     }
1828
1829     /**
1830      * {@inheritDoc} Do not extends if the port position is north or south.
1831      */
1832     @Override
1833     public boolean isExtendParent(Object cell) {
1834         final boolean extendsParents;
1835
1836         if (cell instanceof BasicPort) {
1837             final BasicPort p = (BasicPort) cell;
1838             extendsParents = !(p.getOrientation() == Orientation.NORTH || p.getOrientation() == Orientation.SOUTH) && super.isExtendParent(p);
1839         } else {
1840             extendsParents = super.isExtendParent(cell);
1841         }
1842         return extendsParents;
1843     }
1844
1845     /**
1846      * @return the parameters
1847      */
1848     public ScicosParameters getScicosParameters() {
1849         return scicosParameters;
1850     }
1851
1852     /**
1853      * @param scicosParameters
1854      *            the simulation parameters
1855      */
1856     public void setScicosParameters(final ScicosParameters scicosParameters) {
1857         this.scicosParameters = scicosParameters;
1858
1859         scicosParameters.addPropertyChangeListener(getEngine());
1860     }
1861
1862     /**
1863      * @return the engine
1864      */
1865     public CompilationEngineStatus getEngine() {
1866         return engine;
1867     }
1868
1869     /**
1870      * @param finalIntegrationTime
1871      *            set integration time
1872      * @throws PropertyVetoException
1873      *             when the value is invalid
1874      * @category XMLCompatibility
1875      */
1876     public void setFinalIntegrationTime(final double finalIntegrationTime) throws PropertyVetoException {
1877         scicosParameters.setFinalIntegrationTime(finalIntegrationTime);
1878     }
1879
1880     /**
1881      * @param integratorAbsoluteTolerance
1882      *            set integrator absolute tolerance
1883      * @throws PropertyVetoException
1884      *             when the value is invalid
1885      * @category XMLCompatibility
1886      */
1887     public void setIntegratorAbsoluteTolerance(final double integratorAbsoluteTolerance) throws PropertyVetoException {
1888         scicosParameters.setIntegratorAbsoluteTolerance(integratorAbsoluteTolerance);
1889     }
1890
1891     /**
1892      * @param integratorRelativeTolerance
1893      *            integrator relative tolerance
1894      * @throws PropertyVetoException
1895      *             when the value is invalid
1896      * @category XMLCompatibility
1897      */
1898     public void setIntegratorRelativeTolerance(final double integratorRelativeTolerance) throws PropertyVetoException {
1899         scicosParameters.setIntegratorRelativeTolerance(integratorRelativeTolerance);
1900     }
1901
1902     /**
1903      * @param maximumStepSize
1904      *            set max step size
1905      * @throws PropertyVetoException
1906      *             when the value is invalid
1907      * @category XMLCompatibility
1908      */
1909     public void setMaximumStepSize(final double maximumStepSize) throws PropertyVetoException {
1910         scicosParameters.setMaximumStepSize(maximumStepSize);
1911     }
1912
1913     /**
1914      * @param maxIntegrationTimeinterval
1915      *            set max integration time
1916      * @throws PropertyVetoException
1917      *             when the value is invalid
1918      * @category XMLCompatibility
1919      */
1920     public void setMaxIntegrationTimeinterval(final double maxIntegrationTimeinterval) throws PropertyVetoException {
1921         scicosParameters.setMaxIntegrationTimeInterval(maxIntegrationTimeinterval);
1922     }
1923
1924     /**
1925      * @param realTimeScaling
1926      *            set real time scaling
1927      * @throws PropertyVetoException
1928      *             when the value is invalid
1929      * @category XMLCompatibility
1930      */
1931     public void setRealTimeScaling(final double realTimeScaling) throws PropertyVetoException {
1932         scicosParameters.setRealTimeScaling(realTimeScaling);
1933     }
1934
1935     /**
1936      * @param solver
1937      *            set solver
1938      * @throws PropertyVetoException
1939      *             when the value is invalid
1940      * @category XMLCompatibility
1941      */
1942     public void setSolver(final double solver) throws PropertyVetoException {
1943         scicosParameters.setSolver(solver);
1944     }
1945
1946     /**
1947      * @param toleranceOnTime
1948      *            set tolerance time
1949      * @throws PropertyVetoException
1950      *             when the value is invalid
1951      * @category XMLCompatibility
1952      */
1953     public void setToleranceOnTime(final double toleranceOnTime) throws PropertyVetoException {
1954         scicosParameters.setToleranceOnTime(toleranceOnTime);
1955     }
1956
1957     /**
1958      * Get the current diagram context
1959      *
1960      * @return the context at the current node
1961      */
1962     public String[] getContext() {
1963         return scicosParameters.getContext();
1964     }
1965
1966     /**
1967      * Manage the visibility of the grid and the associated menu
1968      *
1969      * @param status
1970      *            new status
1971      */
1972     public void setGridVisible(final boolean status) {
1973         setGridEnabled(status);
1974         getAsComponent().setGridVisible(status);
1975         getAsComponent().repaint();
1976     }
1977
1978     /**
1979      * @return save status
1980      */
1981     public boolean saveDiagram() {
1982         final boolean isSuccess = saveDiagramAs(getSavedFile());
1983
1984         if (isSuccess) {
1985             setModified(false);
1986             Xcos.getInstance().addDiagram(getSavedFile(), this);
1987         }
1988
1989         return isSuccess;
1990     }
1991
1992     /**
1993      * @param fileName
1994      *            diagram filename
1995      * @return save status
1996      */
1997     public boolean saveDiagramAs(final File fileName) {
1998         boolean isSuccess = false;
1999         File writeFile = fileName;
2000         XcosFileType format = XcosOptions.getPreferences().getFileFormat();
2001
2002         info(XcosMessages.SAVING_DIAGRAM);
2003         if (fileName == null) {
2004             final SwingScilabFileChooser fc = SaveAsAction.createFileChooser();
2005             SaveAsAction.configureFileFilters(fc);
2006             ConfigurationManager.configureCurrentDirectory(fc);
2007
2008             if (getSavedFile() != null) {
2009                 // using save-as, the file chooser should have a filename
2010                 // without extension as default
2011                 String filename = getSavedFile().getName();
2012                 filename = filename.substring(0, filename.lastIndexOf('.'));
2013                 fc.setSelectedFile(new File(filename));
2014             } else {
2015                 final String title = getTitle();
2016                 if (title != null) {
2017                     /*
2018                      * Escape file to handle not supported character in file
2019                      * name (may be Windows only) see
2020                      * http://msdn.microsoft.com/en
2021                      * -us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
2022                      */
2023                     final char[] regex = "<>:\"/\\|?*".toCharArray();
2024                     String escaped = title;
2025                     for (char c : regex) {
2026                         escaped = escaped.replace(c, '-');
2027                     }
2028
2029                     fc.setSelectedFile(new File(escaped));
2030                 }
2031             }
2032
2033             int status = fc.showSaveDialog(this.getAsComponent());
2034             if (status != JFileChooser.APPROVE_OPTION) {
2035                 info(XcosMessages.EMPTY_INFO);
2036                 return isSuccess;
2037             }
2038
2039             // change the format if the user choose a save-able file format
2040             final XcosFileType selectedFilter = XcosFileType.findFileType(fc.getFileFilter());
2041             if (XcosFileType.getAvailableSaveFormats().contains(selectedFilter)) {
2042                 format = selectedFilter;
2043             }
2044             writeFile = fc.getSelectedFile();
2045         }
2046
2047         /* Extension/format update */
2048
2049         // using a String filename also works on a non-existing file
2050         final String filename = writeFile.getName();
2051
2052         /*
2053          * Look for the user extension if it does not exists, append a default
2054          * one
2055          *
2056          * if the specified extension is handled, update the save format ; else
2057          * append a default extension and use the default format
2058          */
2059         XcosFileType userExtension = XcosFileType.findFileType(filename);
2060         if (userExtension == null) {
2061             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
2062             userExtension = format;
2063         }
2064         if (XcosFileType.getAvailableSaveFormats().contains(userExtension)) {
2065             format = userExtension;
2066         } else {
2067             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
2068         }
2069
2070         /*
2071          * If the file exists, ask for confirmation if this is not the
2072          * previously saved file
2073          */
2074         if (writeFile.exists() && !writeFile.equals(getSavedFile())) {
2075             final boolean overwrite = ScilabModalDialog.show(XcosTab.get(this), XcosMessages.OVERWRITE_EXISTING_FILE, XcosMessages.XCOS,
2076                                       IconType.QUESTION_ICON, ButtonType.YES_NO) == AnswerOption.YES_OPTION;
2077
2078             if (!overwrite) {
2079                 info(XcosMessages.EMPTY_INFO);
2080                 return false;
2081             }
2082         }
2083
2084         /*
2085          * Really save the data
2086          */
2087         try {
2088             format.save(writeFile.getCanonicalPath(), getRootDiagram());
2089             setSavedFile(writeFile);
2090
2091             setTitle(writeFile.getName().substring(0, writeFile.getName().lastIndexOf('.')));
2092             ConfigurationManager.getInstance().addToRecentFiles(writeFile);
2093             setModified(false);
2094             isSuccess = true;
2095         } catch (final Exception e) {
2096             LOG.severe(e.toString());
2097
2098             XcosDialogs.couldNotSaveFile(this);
2099         }
2100
2101         info(XcosMessages.EMPTY_INFO);
2102         return isSuccess;
2103     }
2104
2105     /**
2106      * Perform post loading initialization.
2107      *
2108      * @param file
2109      *            the loaded file
2110      */
2111     public void postLoad(final File file) {
2112         final String name = file.getName();
2113
2114         if (XcosFileType.getAvailableSaveFormats().contains(XcosFileType.findFileType(file))) {
2115             setSavedFile(file);
2116             Xcos.getInstance().addDiagram(file, this);
2117         }
2118         setTitle(name.substring(0, name.lastIndexOf('.')));
2119         setModified(false);
2120
2121         fireEvent(new mxEventObject(mxEvent.ROOT));
2122
2123         info(XcosMessages.EMPTY_INFO);
2124     }
2125
2126     /**
2127      * Set the title of the diagram
2128      *
2129      * @param title
2130      *            the title
2131      * @see org.scilab.modules.graph.ScilabGraph#setTitle(java.lang.String)
2132      */
2133     @Override
2134     public void setTitle(final String title) {
2135         super.setTitle(title);
2136         updateTabTitle();
2137     }
2138
2139     /**
2140      * Update the title
2141      */
2142     public void updateTabTitle() {
2143         // get the modifier string
2144         final String modified;
2145         if (isModified()) {
2146             modified = "*";
2147         } else {
2148             modified = "";
2149         }
2150
2151         // get the title string
2152         final String title = getTitle();
2153
2154         // get the path
2155         CharSequence formattedPath = "";
2156         final File savedFile = getSavedFile();
2157         if (savedFile != null) {
2158             try {
2159                 final String path = savedFile.getCanonicalPath();
2160                 formattedPath = new StringBuilder().append(" (").append(path).append(')');
2161             } catch (final IOException e) {
2162                 LOG.warning(e.toString());
2163             }
2164         }
2165
2166         // Product name
2167         final String product = Xcos.TRADENAME;
2168
2169         final String tabTitle = new StringBuilder().append(modified).append(title).append(formattedPath).append(" - ").append(product).toString();
2170
2171         final SwingScilabTab tab = ScilabTabFactory.getInstance().getFromCache(getGraphTab());
2172         if (tab != null) {
2173             tab.setName(tabTitle);
2174         }
2175     }
2176
2177     /**
2178      * @param context
2179      *            set context
2180      * @throws PropertyVetoException
2181      *             when the new value is invalid
2182      */
2183     public void setContext(final String[] context) throws PropertyVetoException {
2184         scicosParameters.setContext(context);
2185         fireEvent(new mxEventObject(XcosEvent.DIAGRAM_UPDATED));
2186         updateCellsContext();
2187     }
2188
2189     /**
2190      * Update context value in diagram children
2191      */
2192     public void updateCellsContext() {
2193         final Object rootParent = getDefaultParent();
2194         final int childCount = getModel().getChildCount(rootParent);
2195         for (int i = 0; i < childCount; ++i) {
2196             final Object obj = getModel().getChildAt(rootParent, i);
2197             if (obj instanceof ContextUpdate) {
2198                 final String[] globalContext = getContext();
2199
2200                 /* Determine if the context is not empty */
2201                 int nbOfDetectedChar = 0;
2202                 for (int j = 0; j < globalContext.length; j++) {
2203                     globalContext[j] = globalContext[j].replaceFirst("\\s", "");
2204                     nbOfDetectedChar += globalContext[j].length();
2205                     if (nbOfDetectedChar != 0) {
2206                         break;
2207                     }
2208                 }
2209
2210                 if (nbOfDetectedChar != 0) {
2211                     ((ContextUpdate) obj).onContextChange(globalContext);
2212                 }
2213
2214             } else if (obj instanceof SuperBlock) {
2215                 final SuperBlock superBlock = (SuperBlock) obj;
2216                 if (superBlock.getChild() != null) {
2217                     superBlock.getChild().updateCellsContext();
2218                 }
2219             }
2220         }
2221     }
2222
2223     /**
2224      * @param debugLevel
2225      *            change debug level
2226      * @category XMLCompatibility
2227      * @throws PropertyVetoException
2228      *             when the new value is invalid
2229      */
2230     public void setDebugLevel(final int debugLevel) throws PropertyVetoException {
2231         scicosParameters.setDebugLevel(debugLevel);
2232     }
2233
2234     /**
2235      * Popup a dialog to ask for a file creation
2236      *
2237      * @param f
2238      *            the file to create
2239      * @return true if creation is has been performed
2240      */
2241     public boolean askForFileCreation(final File f) {
2242         AnswerOption answer;
2243         try {
2244             answer = ScilabModalDialog.show(getAsComponent(), new String[] { String.format(XcosMessages.FILE_DOESNT_EXIST, f.getCanonicalFile()) },
2245                                             XcosMessages.XCOS, IconType.QUESTION_ICON, ButtonType.YES_NO);
2246         } catch (final IOException e) {
2247             LOG.severe(e.toString());
2248             answer = AnswerOption.YES_OPTION;
2249         }
2250
2251         if (answer == AnswerOption.YES_OPTION) {
2252             return saveDiagramAs(f);
2253         }
2254
2255         return false;
2256     }
2257
2258     /**
2259      * Load a file with different method depending on it extension
2260      *
2261      * @param file
2262      *            File to load (can be null)
2263      * @param variable
2264      *            the variable to decode (can be null)
2265      */
2266     public void transformAndLoadFile(final String file, final String variable) {
2267         final File f;
2268         final XcosFileType filetype;
2269         if (file != null) {
2270             f = new File(file);
2271             filetype = XcosFileType.findFileType(f);
2272         } else {
2273             f = null;
2274             filetype = null;
2275         }
2276         new SwingWorker<XcosDiagram, ActionEvent>() {
2277             int counter = 0;
2278             final Timer t = new Timer(1000, new ActionListener() {
2279                 @Override
2280                 public void actionPerformed(ActionEvent e) {
2281                     counter = (counter + 1) % (XcosMessages.DOTS.length() + 1);
2282                     String str = XcosMessages.LOADING_DIAGRAM + XcosMessages.DOTS.substring(0, counter);
2283
2284                     XcosDiagram.this.info(str);
2285                 }
2286             });
2287
2288             @Override
2289             protected XcosDiagram doInBackground() {
2290                 t.start();
2291
2292                 final Xcos instance = Xcos.getInstance();
2293                 XcosDiagram diag = XcosDiagram.this;
2294
2295                 diag.setReadOnly(true);
2296
2297                 /*
2298                  * Load, log errors and notify
2299                  */
2300                 synchronized (instance) {
2301                     try {
2302
2303                         if (f != null && filetype != null) {
2304                             filetype.load(file, XcosDiagram.this);
2305                         }
2306
2307                         final ScilabDirectHandler handler = ScilabDirectHandler.acquire();
2308                         try {
2309                             if (variable != null && handler != null) {
2310                                 handler.readDiagram(XcosDiagram.this, variable);
2311                             }
2312                         } finally {
2313                             if (handler != null) {
2314                                 handler.release();
2315                             }
2316                         }
2317
2318                         instance.setLastError("");
2319                     } catch (Exception e) {
2320                         Throwable ex = e;
2321                         while (ex instanceof RuntimeException) {
2322                             ex = ex.getCause();
2323                         }
2324                         instance.setLastError(ex.getMessage());
2325                     }
2326                     instance.notify();
2327                 }
2328
2329                 return diag;
2330             }
2331
2332             @Override
2333             protected void done() {
2334                 t.stop();
2335                 XcosDiagram.this.setReadOnly(false);
2336                 XcosDiagram.this.getUndoManager().clear();
2337                 XcosDiagram.this.refresh();
2338
2339                 /*
2340                  * Load has finished
2341                  */
2342                 if (f != null && filetype != null) {
2343                     postLoad(f);
2344                 }
2345                 XcosDiagram.this.info(XcosMessages.EMPTY_INFO);
2346             }
2347
2348         } .execute();
2349     }
2350
2351     /**
2352      * Update all the children of the current graph.
2353      */
2354     public void setChildrenParentDiagram() {
2355         getModel().beginUpdate();
2356         try {
2357             for (int i = 0; i < getModel().getChildCount(getDefaultParent()); i++) {
2358                 final mxCell cell = (mxCell) getModel().getChildAt(getDefaultParent(), i);
2359                 if (cell instanceof BasicBlock) {
2360                     final BasicBlock block = (BasicBlock) cell;
2361                     block.setParentDiagram(this);
2362                 }
2363             }
2364         } finally {
2365             getModel().endUpdate();
2366         }
2367     }
2368
2369     /**
2370      * Getting the root diagram of a decomposed diagram
2371      *
2372      * @return Root parent of the whole parent
2373      */
2374     public XcosDiagram getRootDiagram() {
2375         XcosDiagram rootGraph = this;
2376         while (rootGraph instanceof SuperBlockDiagram) {
2377             rootGraph = ((SuperBlockDiagram) rootGraph).getContainer().getParentDiagram();
2378         }
2379         return rootGraph;
2380     }
2381
2382     /**
2383      * Returns the tooltip to be used for the given cell.
2384      *
2385      * @param cell
2386      *            block
2387      * @return cell tooltip
2388      */
2389     @Override
2390     public String getToolTipForCell(final Object cell) {
2391         if (cell instanceof BasicBlock) {
2392             return ((BasicBlock) cell).getToolTipText();
2393         } else if (cell instanceof BasicPort) {
2394             return ((BasicPort) cell).getToolTipText();
2395         }
2396         return "";
2397     }
2398
2399     /**
2400      * Display the message in info bar.
2401      *
2402      * @param message
2403      *            Information
2404      */
2405     public void info(final String message) {
2406         final XcosTab tab = XcosTab.get(this);
2407
2408         if (tab != null && tab.getInfoBar() != null) {
2409             tab.getInfoBar().setText(message);
2410         }
2411     }
2412
2413     /**
2414      * Display the message into an error popup
2415      *
2416      * @param message
2417      *            Error of the message
2418      */
2419     public void error(final String message) {
2420         JOptionPane.showMessageDialog(getAsComponent(), message, XcosMessages.XCOS, JOptionPane.ERROR_MESSAGE);
2421     }
2422
2423     /**
2424      * Find the block corresponding to the given uid and display a warning
2425      * message.
2426      *
2427      * @param uid
2428      *            - A String as UID.
2429      * @param message
2430      *            - The message to display.
2431      */
2432     public void warnCellByUID(final String uid, final String message) {
2433         final Object cell = ((mxGraphModel) getModel()).getCell(uid);
2434         if (cell == null) {
2435             return;
2436         }
2437
2438         if (GraphicsEnvironment.isHeadless()) {
2439             System.err.printf("%s at %s\n    %s: %s\n", "warnCell", getRootDiagram().getTitle(), uid, message);
2440             return;
2441         }
2442
2443         // open the tab
2444         if (XcosTab.get(this) == null) {
2445             XcosTab.restore(this);
2446         }
2447
2448         if (message.isEmpty()) {
2449             getAsComponent().clearCellOverlays(cell);
2450         } else {
2451             getAsComponent().setCellWarning(cell, message, null, true);
2452         }
2453     }
2454
2455     /**
2456      * Set the current diagram in a modified state
2457      *
2458      * @param modified
2459      *            True or False whether the current diagram must be saved or
2460      *            not.
2461      */
2462     @Override
2463     public void setModified(final boolean modified) {
2464         super.setModified(modified);
2465         updateTabTitle();
2466     }
2467
2468     /**
2469      * Evaluate the current context
2470      *
2471      * @return The resulting data. Keys are variable names and Values are
2472      *         evaluated values.
2473      */
2474     public Map<String, String> evaluateContext() {
2475         Map<String, String> result = Collections.emptyMap();
2476
2477         final ScilabDirectHandler handler = ScilabDirectHandler.acquire();
2478         if (handler == null) {
2479             return result;
2480         }
2481
2482         try {
2483             // first write the context strings
2484             handler.writeContext(getContext());
2485
2486             // evaluate using script2var
2487             ScilabInterpreterManagement.synchronousScilabExec(ScilabDirectHandler.CONTEXT + " = script2var(" + ScilabDirectHandler.CONTEXT + ", struct());");
2488
2489             // read the structure
2490             result = handler.readContext();
2491         } catch (final InterpreterException e) {
2492             info("Unable to evaluate the contexte");
2493             e.printStackTrace();
2494         } finally {
2495             handler.release();
2496         }
2497
2498         return result;
2499     }
2500
2501     /**
2502      * @param uid
2503      *            block ID
2504      * @return block
2505      */
2506     public BasicBlock getChildById(final String uid) {
2507         BasicBlock returnBlock = null;
2508         for (int i = 0; i < getModel().getChildCount(getDefaultParent()); ++i) {
2509             if (getModel().getChildAt(getDefaultParent(), i) instanceof BasicBlock) {
2510                 final BasicBlock block = (BasicBlock) getModel().getChildAt(getDefaultParent(), i);
2511                 if (block.getId().compareTo(uid) == 0) { // find it
2512                     returnBlock = block;
2513                 } else {
2514                     if (block instanceof SuperBlock) {
2515                         ((SuperBlock) block).createChildDiagram();
2516
2517                         // search in child
2518                         returnBlock = ((SuperBlock) block).getChild().getChildById(uid);
2519
2520                     } else if (block.getRealParameters() instanceof ScilabMList) {
2521                         // we have a hidden SuperBlock, create a real one
2522                         SuperBlock newSP = (SuperBlock) BlockFactory.createBlock(SuperBlock.INTERFUNCTION_NAME);
2523                         newSP.setParentDiagram(block.getParentDiagram());
2524
2525                         newSP.setRealParameters(block.getRealParameters());
2526                         newSP.createChildDiagram(true);
2527
2528                         // search in child
2529                         returnBlock = newSP.getChild().getChildById(uid);
2530                         block.setRealParameters(newSP.getRealParameters());
2531                     }
2532                 }
2533             }
2534
2535             if (returnBlock != null) {
2536                 return returnBlock;
2537             }
2538         }
2539         return returnBlock;
2540     }
2541
2542     /**
2543      * Returns true if the given cell is a not a block nor a port.
2544      *
2545      * @param cell
2546      *            the drop target
2547      * @param cells
2548      *            the cells to be dropped
2549      * @return the drop status
2550      * @see com.mxgraph.view.mxGraph#isValidDropTarget(java.lang.Object,
2551      *      java.lang.Object[])
2552      */
2553     @Override
2554     public boolean isValidDropTarget(final Object cell, final Object[] cells) {
2555         return !(cell instanceof BasicBlock) && !(cell instanceof BasicBlock) && !(cell instanceof BasicPort) && super.isValidDropTarget(cell, cells);
2556     }
2557
2558     /**
2559      * @return child visibility
2560      */
2561     @Deprecated
2562     public boolean isChildVisible() {
2563         // FIXME check
2564
2565         return false;
2566
2567         // for (int i = 0; i < getModel().getChildCount(getDefaultParent());
2568         // i++) {
2569         // final Object child = getModel().getChildAt(getDefaultParent(), i);
2570         // if (child instanceof SuperBlock) {
2571         // final XcosDiagram diag = ((SuperBlock) child).getChild();
2572         // if (diag != null && (diag.isChildVisible() || diag.isVisible())) {
2573         // // if child or sub child is visible
2574         // return true;
2575         // }
2576         // }
2577         // }
2578         // return false;
2579     }
2580
2581     /**
2582      * @return close capability
2583      */
2584     public boolean canClose() {
2585         if (!isChildVisible()) {
2586             return true;
2587         }
2588         return false;
2589     }
2590
2591     /**
2592      * Construct a new selection model used on this graph.
2593      *
2594      * @return a new selection model instance.
2595      * @see com.mxgraph.view.mxGraph#createSelectionModel()
2596      */
2597     @Override
2598     protected mxGraphSelectionModel createSelectionModel() {
2599         return new mxGraphSelectionModel(this) {
2600             /**
2601              * When we only want to select a cell which is a port, select the
2602              * parent block.
2603              *
2604              * @param cell
2605              *            the cell
2606              * @see com.mxgraph.view.mxGraphSelectionModel#setCell(java.lang.Object)
2607              */
2608             @Override
2609             public void setCell(final Object cell) {
2610                 final Object current;
2611                 if (cell instanceof BasicPort) {
2612                     current = getModel().getParent(cell);
2613                 } else {
2614                     current = cell;
2615                 }
2616                 super.setCell(current);
2617             }
2618         };
2619     }
2620 }