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