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