32f62abe90855ee0a45f333187423c6f36533941
[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.Xcos;
58 import org.scilab.modules.xcos.XcosTab;
59 import org.scilab.modules.xcos.actions.SaveAsAction;
60 import org.scilab.modules.xcos.block.AfficheBlock;
61 import org.scilab.modules.xcos.block.BasicBlock;
62 import org.scilab.modules.xcos.block.SplitBlock;
63 import org.scilab.modules.xcos.block.TextBlock;
64 import org.scilab.modules.xcos.block.io.EventInBlock;
65 import org.scilab.modules.xcos.block.io.EventOutBlock;
66 import org.scilab.modules.xcos.block.io.ExplicitInBlock;
67 import org.scilab.modules.xcos.block.io.ExplicitOutBlock;
68 import org.scilab.modules.xcos.block.io.ImplicitInBlock;
69 import org.scilab.modules.xcos.block.io.ImplicitOutBlock;
70 import org.scilab.modules.xcos.configuration.ConfigurationManager;
71 import org.scilab.modules.xcos.graph.model.XcosCell;
72 import org.scilab.modules.xcos.graph.model.XcosCellFactory;
73 import org.scilab.modules.xcos.graph.model.XcosCellFactory.BlockInterFunction;
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
109 /**
110  * The base class for a diagram. This class contains jgraphx + Scicos data.
111  */
112 public class XcosDiagram extends ScilabGraph {
113     private static final Logger LOG = Logger.getLogger(XcosDiagram.class.getName());
114
115     private static final String MODIFIED = "modified";
116     private static final String CELLS = "cells";
117     public static final String IN = "in";
118     public static final String OUT = "out";
119     public static final String EIN = "ein";
120     public static final String EOUT = "eout";
121
122     /**
123      * Prefix used to tag text node.
124      */
125     public static final String HASH_IDENTIFIER = "#identifier";
126
127     /**
128      * Default geometry used while adding a label to a block (on the middle and
129      * below the bottom of the parent block)
130      */
131     private static final mxGeometry DEFAULT_LABEL_GEOMETRY = new mxGeometry(0.5, 1.1, 0.0, 0.0);
132
133     /*
134      * diagram data
135      */
136
137     // the associated parameters
138     private final ScicosParameters scicosParameters;
139
140     // the associated object uid
141     private final long uid;
142     // the kind of the diagram : can be either a DIAGRAM or a BLOCK (eg. for superblocks)
143     private final Kind kind;
144
145     /**
146      * Constructor
147      *
148      * @param withVisibleFeatures true if the visible features should be activated, false otherwise. Disable it on encode/decode leads to a huge performance gain.
149      */
150     public XcosDiagram(final long diagramId, final Kind kind) {
151         super(new mxGraphModel(), Xcos.getInstance().getStyleSheet());
152
153         this.uid = diagramId;
154         this.kind = kind;
155         new JavaController().referenceObject(uid);
156
157         // Scicos related setup
158         if (kind == Kind.DIAGRAM) {
159             scicosParameters = new ScicosParameters(diagramId);
160         } else {
161             scicosParameters = null;
162         }
163
164         // add the default parent (the JGraphX layer)
165         XcosCell parent = new XcosCell(this.uid, this.kind);
166         ((mxICell) getModel().getRoot()).insert(parent);
167         setDefaultParent(parent);
168
169         setComponent(new GraphComponent(this));
170         initComponent();
171
172         // Forbid disconnecting cells once it is connected.
173         setCellsDisconnectable(false);
174
175         // Forbid pending edges.
176         setAllowDanglingEdges(false);
177
178         // Cannot connect port to itself.
179         setAllowLoops(false);
180
181         // Override isCellResizable to filter what the user can resize
182         setCellsResizable(true);
183
184         /* Labels use HTML if not equal to interface function name */
185         setHtmlLabels(true);
186         /*
187          * by default every label is movable, see
188          * XcosDiagram##isLabelMovable(java.lang.Object) for restrictions
189          */
190         setVertexLabelsMovable(true);
191         setEdgeLabelsMovable(true);
192
193         //
194         setCloneInvalidEdges(true);
195
196         // Override isCellEditable to filter what the user can edit
197         setCellsEditable(true);
198
199         setConnectableEdges(true);
200
201         // Do not clear edge points on connect
202         setResetEdgesOnConnect(false);
203
204         setMultiplicities();
205
206         setAutoOrigin(true);
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 = data1.get(0);
254                 } else {
255                     value1 = 0;
256                 }
257
258                 final int value2;
259                 if (data2.size() >= 1) {
260                     value2 = 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(diagram, (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(diagram, 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             JavaController controller = new JavaController();
647             long uid = controller.createObject(Kind.LINK);
648             if (src.getType() == Type.EXPLICIT) {
649                 link = new ExplicitLink(uid);
650             } else if (src.getType() == Type.IMPLICIT) {
651                 link = new ImplicitLink(uid);
652             } else {
653                 link = new CommandControlLink(uid);
654             }
655
656             // allocate the associated geometry
657             link.setGeometry(new mxGeometry());
658             ret = link;
659         } else if (source instanceof SplitBlock) {
660             SplitBlock src = (SplitBlock) source;
661             return createEdge(parent, id, value, src.getIn(), target, style);
662         } else if (source instanceof BasicLink) {
663             BasicLink src = (BasicLink) source;
664             BasicLink link = null;
665
666             try {
667                 link = src.getClass().newInstance();
668
669                 // allocate the associated geometry
670                 link.setGeometry(new mxGeometry());
671
672             } catch (InstantiationException e) {
673                 LOG.severe(e.toString());
674             } catch (IllegalAccessException e) {
675                 LOG.severe(e.toString());
676             }
677
678             ret = link;
679         }
680
681         if (ret == null) {
682             ret = super.createEdge(parent, id, value, source, target, style);
683             LOG.warning("Creating a non typed edge");
684         }
685
686         return ret;
687     }
688
689     /**
690      * Add an edge from a source to the target.
691      *
692      * @param cell
693      *            the edge to add (may be null)
694      * @param parent
695      *            the parent of the source and the target
696      * @param source
697      *            the source cell
698      * @param target
699      *            the target cell
700      * @param index
701      *            the index of the edge
702      * @return the added edge or null.
703      * @see com.mxgraph.view.mxGraph#addEdge(java.lang.Object, java.lang.Object,
704      *      java.lang.Object, java.lang.Object, java.lang.Integer)
705      */
706     @Override
707     public Object addCell(Object cell, Object parent, Integer index, Object source, Object target) {
708
709         // already connected edge or normal block
710         if (source == null && target == null) {
711             return super.addCell(cell, parent, index, source, target);
712         }
713
714         // Command -> Control
715         if (source instanceof CommandPort && target instanceof ControlPort && cell instanceof CommandControlLink) {
716             return super.addCell(cell, parent, index, source, target);
717         }
718
719         // Control -> Command
720         // Switch source and target !
721         if (target instanceof CommandPort && source instanceof ControlPort && cell instanceof CommandControlLink) {
722             BasicLink current = (BasicLink) cell;
723             current.invertDirection();
724
725             return super.addCell(cell, parent, index, target, source);
726         }
727
728         // ExplicitOutput -> ExplicitInput
729         if (source instanceof ExplicitOutputPort && target instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
730             return super.addCell(cell, parent, index, source, target);
731         }
732         // ExplicitInput -> ExplicitOutput
733         // Switch source and target !
734         if (target instanceof ExplicitOutputPort && source instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
735             BasicLink current = (BasicLink) cell;
736             current.invertDirection();
737
738             return super.addCell(cell, parent, index, target, source);
739         }
740
741         // ImplicitOutput -> ImplicitInput
742         if (source instanceof ImplicitOutputPort && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
743             return super.addCell(cell, parent, index, source, target);
744         }
745         // ImplicitInput -> ImplicitOutput
746         // Switch source and target !
747         if (target instanceof ImplicitOutputPort && source instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
748             BasicLink current = (BasicLink) cell;
749             current.invertDirection();
750
751             return super.addCell(cell, parent, index, target, source);
752         }
753
754         // ImplicitInput -> ImplicitInput
755         if (source instanceof ImplicitInputPort && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
756             return super.addCell(cell, parent, index, source, target);
757         }
758         // ImplicitOutputPort -> ImplicitOutput
759         // Switch source and target !
760         if (target instanceof ImplicitOutputPort && source instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
761             BasicLink current = (BasicLink) cell;
762             current.invertDirection();
763
764             return super.addCell(cell, parent, index, target, source);
765         }
766
767         /*
768          * Split management
769          */
770
771         // ExplicitLink -> ExplicitInputPort
772         if (source instanceof ExplicitLink && target instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
773             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
774             return addCell(cell, parent, index, split.getOut2(), target);
775         }
776         // ExplicitOutput -> ExpliciLink
777         // Switch source and target !
778         if (target instanceof ExplicitLink && source instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
779             final BasicLink current = (BasicLink) cell;
780             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
781
782             current.invertDirection();
783
784             return addCell(cell, parent, index, split.getOut2(), source);
785         }
786
787         // ImplicitLink -> ImplicitInputPort
788         if (source instanceof ImplicitLink && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
789             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
790             return addCell(cell, parent, index, split.getOut2(), target);
791         }
792         // ImplicitInputPort -> ImplicitLink
793         // Switch source and target !
794         if (target instanceof ImplicitLink && source instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
795             final BasicLink current = (BasicLink) cell;
796             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
797
798             current.invertDirection();
799
800             return addCell(cell, parent, index, split.getOut2(), source);
801         }
802
803         // ImplicitLink -> ImplicitOutputPort
804         if (source instanceof ImplicitLink && target instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
805             final BasicLink current = (BasicLink) cell;
806             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) source);
807             return addCell(cell, parent, index, split.getOut2(), source);
808         }
809         // ImplicitOutputPort -> ImplicitLink
810         // Switch source and target !
811         if (target instanceof ImplicitLink && source instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
812             final BasicLink current = (BasicLink) cell;
813             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (ImplicitLink) target);
814             return addCell(cell, parent, index, split.getOut2(), source);
815         }
816
817         // CommandControlLink -> ControlPort
818         if (source instanceof CommandControlLink && target instanceof ControlPort && cell instanceof CommandControlLink) {
819             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
820             return addCell(cell, parent, index, split.getOut2(), target);
821         }
822         // ControlPort -> CommandControlLink
823         // Switch source and target !
824         if (target instanceof CommandControlLink && source instanceof ControlPort && cell instanceof CommandControlLink) {
825             final BasicLink current = (BasicLink) cell;
826             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
827
828             current.invertDirection();
829
830             return addCell(cell, parent, index, split.getOut2(), source);
831         }
832
833         if (cell instanceof BasicLink && source != null && target != null) {
834             LOG.severe("Unable to add a typed link");
835             return null;
836         } else {
837             LOG.severe("Adding an untyped edge");
838             return super.addCell(cell, parent, index, source, target);
839         }
840     }
841
842     /**
843      * Add a split on a edge.
844      *
845      * @param splitPoint
846      *            the split point (center of the split block)
847      * @param link
848      *            source link
849      * @return split block
850      */
851     public SplitBlock addSplitEdge(final mxPoint splitPoint, final BasicLink link) {
852         final BasicPort linkSource = (BasicPort) link.getSource();
853         final BasicPort linkTarget = (BasicPort) link.getTarget();
854
855         final SplitBlock splitBlock = (SplitBlock) XcosCellFactory.createBlock(BlockInterFunction.SPLIT_f);
856
857         getModel().beginUpdate();
858         try {
859             // Origin of the parent, (0,0) as default may be different in case
860             mxPoint orig = link.getParent().getGeometry();
861             if (orig == null) {
862                 orig = new mxPoint();
863             }
864
865             // FIXME create the port is needed
866             // splitBlock.addConnection(linkSource);
867
868             addCell(splitBlock);
869             // force resize and align on the grid
870             resizeCell(splitBlock, new mxRectangle(splitPoint.getX(), splitPoint.getY(), 0, 0));
871
872             // Update old link
873
874             // get breaking segment and related point
875             mxPoint splitTr = new mxPoint(splitPoint.getX() - orig.getX(), splitPoint.getY() - orig.getY());
876             final int pos = link.findNearestSegment(splitTr);
877
878             // save points after breaking point
879             final List<mxPoint> saveStartPoints = link.getPoints(pos, true);
880             final List<mxPoint> saveEndPoints = link.getPoints(pos, false);
881
882             // remove the first end point if the position is near the split
883             // position
884             if (saveEndPoints.size() > 0) {
885                 final mxPoint p = saveEndPoints.get(0);
886                 final double dx = p.getX() - splitTr.getX();
887                 final double dy = p.getY() - splitTr.getY();
888
889                 if (!getAsComponent().isSignificant(dx, dy)) {
890                     saveEndPoints.remove(0);
891                 }
892             }
893
894             // disable events
895             getModel().beginUpdate();
896             getModel().remove(link);
897             getModel().endUpdate();
898
899             connect(linkSource, splitBlock.getIn(), saveStartPoints, orig);
900             connect(splitBlock.getOut1(), linkTarget, saveEndPoints, orig);
901
902             refresh();
903         } finally {
904             getModel().endUpdate();
905         }
906
907         return splitBlock;
908     }
909
910     /**
911      * Connect two port together with the associated points.
912      *
913      * This method perform the connection in two step in order to generate the
914      * right UndoableChangeEdits.
915      *
916      * @param src
917      *            the source port
918      * @param trg
919      *            the target port
920      * @param points
921      *            the points
922      * @param orig
923      *            the origin point (may be (0,0))
924      */
925     public void connect(BasicPort src, BasicPort trg, List<mxPoint> points, mxPoint orig) {
926         mxGeometry geometry;
927
928         /*
929          * Add the link with a default geometry
930          */
931         final Object newLink1 = createEdge(null, null, null, src, trg, null);
932         addCell(newLink1, null, null, src, trg);
933         geometry = getModel().getGeometry(newLink1);
934         if (getModel().getParent(newLink1) instanceof BasicBlock) {
935             // on a loop link, translate the points as the cell has been moved to the parent
936             orig.setX(orig.getX() + geometry.getX());
937             orig.setY(orig.getY() + geometry.getY());
938         }
939         geometry.setPoints(points);
940         getModel().setGeometry(newLink1, geometry);
941
942         /*
943          * Update the geometry
944          */
945         // should be cloned to generate an event
946         geometry = (mxGeometry) getModel().getGeometry(newLink1).clone();
947         final double dx = orig.getX();
948         final double dy = orig.getY();
949
950         geometry.translate(dx, dy);
951         getModel().setGeometry(newLink1, geometry);
952     }
953
954     /**
955      * Initialize component settings for a graph.
956      *
957      * This method *must* be used to setup the component after any
958      * reassociation.
959      */
960     public void initComponent() {
961         getAsComponent().setToolTips(true);
962
963         // This enable stop editing cells when pressing Enter.
964         getAsComponent().setEnterStopsCellEditing(false);
965
966         getAsComponent().setTolerance(1);
967
968         getAsComponent().getViewport().setOpaque(false);
969
970         getAsComponent().setBackground(XcosOptions.getEdition().getGraphBackground());
971
972         final boolean gridEnable = XcosOptions.getEdition().isGraphGridEnable();
973         setGridVisible(gridEnable);
974         if (gridEnable) {
975             setGridSize(XcosOptions.getEdition().getGraphGrid());
976         }
977
978         /*
979          * Reinstall related listeners
980          */
981
982         // Property change Listener
983         // Will say if a diagram has been modified or not.
984         final PropertyChangeListener p = new PropertyChangeListener() {
985             @Override
986             public void propertyChange(final PropertyChangeEvent e) {
987                 if (e.getPropertyName().compareTo(MODIFIED) == 0) {
988                     if (!e.getOldValue().equals(e.getNewValue())) {
989                         updateTabTitle();
990                     }
991                 }
992             }
993         };
994         getAsComponent().removePropertyChangeListener(MODIFIED, p);
995         getAsComponent().addPropertyChangeListener(MODIFIED, p);
996     }
997
998     /**
999      * Install the multiplicities (use for link checking)
1000      */
1001     private void setMultiplicities() {
1002         final List<mxMultiplicity> multiplicities = new ArrayList<mxMultiplicity>();
1003
1004         // Input data port
1005         multiplicities.add(new PortCheck(ExplicitInputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1006             private static final long serialVersionUID = -4987163442006736665L;
1007             {
1008                 add(ExplicitOutputPort.class);
1009                 add(ExplicitLink.class);
1010             }
1011         }), XcosMessages.LINK_ERROR_EXPLICIT_IN));
1012         multiplicities.add(new PortCheck(ImplicitInputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1013             private static final long serialVersionUID = 886376532181210926L;
1014             {
1015                 add(ImplicitOutputPort.class);
1016                 add(ImplicitInputPort.class);
1017                 add(ImplicitLink.class);
1018             }
1019         }), XcosMessages.LINK_ERROR_IMPLICIT_IN));
1020
1021         // Output data port
1022         multiplicities.add(new PortCheck(ExplicitOutputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1023             private static final long serialVersionUID = 4594127972486054821L;
1024             {
1025                 add(ExplicitInputPort.class);
1026             }
1027         }), XcosMessages.LINK_ERROR_EXPLICIT_OUT));
1028         multiplicities.add(new PortCheck(ImplicitOutputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1029             private static final long serialVersionUID = -3719677806532507973L;
1030             {
1031                 add(ImplicitInputPort.class);
1032                 add(ImplicitOutputPort.class);
1033                 add(ImplicitLink.class);
1034             }
1035         }), XcosMessages.LINK_ERROR_IMPLICIT_OUT));
1036
1037         // Control port
1038         multiplicities.add(new PortCheck(ControlPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1039             private static final long serialVersionUID = 2941077191386058497L;
1040             {
1041                 add(CommandPort.class);
1042                 add(CommandControlLink.class);
1043             }
1044         }), XcosMessages.LINK_ERROR_EVENT_IN));
1045
1046         // Command port
1047         multiplicities.add(new PortCheck(CommandPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1048             private static final long serialVersionUID = -3470370027962480362L;
1049             {
1050                 add(ControlPort.class);
1051             }
1052         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1053
1054         // ExplicitLink connections
1055         multiplicities.add(new PortCheck(ExplicitLink.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1056             private static final long serialVersionUID = 7423543162930147373L;
1057
1058             {
1059                 add(ExplicitInputPort.class);
1060             }
1061         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1062
1063         // ImplicitLink connections
1064         multiplicities.add(new PortCheck(ImplicitLink.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1065             private static final long serialVersionUID = 7775100011122283282L;
1066
1067             {
1068                 add(ImplicitInputPort.class);
1069                 add(ImplicitOutputPort.class);
1070             }
1071         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1072
1073         // CommandControlLink connections
1074         multiplicities.add(new PortCheck(CommandControlLink.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1075             private static final long serialVersionUID = 3260421433507192386L;
1076
1077             {
1078                 add(ControlPort.class);
1079             }
1080         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1081
1082         // Already connected port
1083         multiplicities.add(new PortCheck(BasicPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1084             private static final long serialVersionUID = 6376349598052836660L;
1085
1086             {
1087                 add(BasicPort.class);
1088             }
1089         }), XcosMessages.LINK_ERROR_ALREADY_CONNECTED));
1090
1091         setMultiplicities(multiplicities.toArray(new mxMultiplicity[multiplicities.size()]));
1092     }
1093
1094     /**
1095      * Install all needed Listeners.
1096      */
1097     public void installListeners() {
1098         /*
1099          * First remove all listeners if present
1100          */
1101         removeListener(CellResizedTracker.getInstance());
1102         getUndoManager().removeListener(UndoUpdateTracker.getInstance());
1103         removeListener(RefreshBlockTracker.getInstance());
1104
1105         // Track when resizing a cell.
1106         addListener(mxEvent.CELLS_RESIZED, CellResizedTracker.getInstance());
1107
1108         // Update the blocks view on undo/redo
1109         getUndoManager().addListener(mxEvent.UNDO, UndoUpdateTracker.getInstance());
1110         getUndoManager().addListener(mxEvent.REDO, UndoUpdateTracker.getInstance());
1111
1112     }
1113
1114     /**
1115      * Removes the given cells from the graph including all connected edges if
1116      * includeEdges is true. The change is carried out using cellsRemoved.
1117      *
1118      * @param cells
1119      *            the cells to be removed
1120      * @param includeEdges
1121      *            true if the edges must be removed, false otherwise.
1122      * @return the deleted cells
1123      * @see com.mxgraph.view.mxGraph#removeCells(java.lang.Object[], boolean)
1124      */
1125     @Override
1126     public Object[] removeCells(final Object[] cells, final boolean includeEdges) {
1127         if (cells == null || cells.length == 0) {
1128             return super.removeCells(cells, includeEdges);
1129         }
1130
1131         /*
1132          * First remove all links connected to a removed Split if applicable
1133          */
1134         final Object[] initialCells;
1135         if (includeEdges) {
1136             initialCells = addAllEdges(cells);
1137         } else {
1138             initialCells = cells;
1139         }
1140
1141         // stash used on the loop
1142         final Queue<Object> loopCells = new LinkedList<Object>(Arrays.asList(initialCells));
1143         // the cells that need to be really
1144         final Set<Object> removedCells = new HashSet<Object>(loopCells);
1145         // couple of cells to reconnect
1146         final List<BasicPort[]> connectedCells = new ArrayList<BasicPort[]>();
1147         final List<List<mxPoint>> connectedPoints = new ArrayList<List<mxPoint>>();
1148
1149         /*
1150          * Then loop on the algorithm to select the right edges
1151          */
1152         // /!\ not bounded algorithm
1153         while (loopCells.size() > 0) {
1154             Object cell = loopCells.remove();
1155
1156             if (cell instanceof BasicLink) {
1157                 /*
1158                  * Continue on non fully connected links
1159                  */
1160                 if (((BasicLink) cell).getSource() == null) {
1161                     continue;
1162                 }
1163                 if (((BasicLink) cell).getTarget() == null) {
1164                     continue;
1165                 }
1166
1167                 /*
1168                  * Add any split to a link
1169                  */
1170                 addTerminalParent(((BasicLink) cell).getSource(), removedCells, loopCells);
1171                 addTerminalParent(((BasicLink) cell).getTarget(), removedCells, loopCells);
1172
1173             } else if (cell instanceof SplitBlock) {
1174                 final SplitBlock splitBlock = (SplitBlock) cell;
1175
1176                 /*
1177                  * Remove related connection or not and reconnect.
1178                  */
1179
1180                 if (splitBlock.getIn().getEdgeCount() == 0 || splitBlock.getOut1().getEdgeCount() == 0 || splitBlock.getOut2().getEdgeCount() == 0) {
1181                     // corner case, all links will be removed
1182                     continue;
1183                 }
1184
1185                 final mxICell inLink = splitBlock.getIn().getEdgeAt(0);
1186                 final mxICell out1Link = splitBlock.getOut1().getEdgeAt(0);
1187                 final mxICell out2Link = splitBlock.getOut2().getEdgeAt(0);
1188
1189                 final boolean inRemoved = removedCells.contains(inLink);
1190                 final boolean out1Removed = removedCells.contains(out1Link);
1191                 final boolean out2Removed = removedCells.contains(out2Link);
1192
1193                 /*
1194                  * Explicit case, if the in link is deleted; all the out links
1195                  * also should.
1196                  */
1197                 if (inLink instanceof ExplicitLink && inRemoved) {
1198                     if (removedCells.add(out1Link)) {
1199                         loopCells.add(out1Link);
1200                     }
1201
1202                     if (removedCells.add(out2Link)) {
1203                         loopCells.add(out2Link);
1204                     }
1205                 }
1206
1207                 /*
1208                  * Global case reconnect if not removed
1209                  */
1210                 final BasicPort[] connection;
1211                 List<mxPoint> points = null;
1212                 if (!inRemoved && !out1Removed && out2Removed) {
1213                     connection = findTerminals(inLink, out1Link, removedCells);
1214                     points = getDirectPoints(splitBlock, inLink, out1Link);
1215                 } else if (!inRemoved && out1Removed && !out2Removed) {
1216                     connection = findTerminals(inLink, out2Link, removedCells);
1217                     points = getDirectPoints(splitBlock, inLink, out2Link);
1218                 } else if (inRemoved && !out1Removed && !out2Removed) {
1219                     // only implicit or event case, log otherwise
1220                     if (out1Link instanceof ExplicitLink || out2Link instanceof ExplicitLink) {
1221                         LOG.severe("Reconnection failed for explicit links");
1222                         connection = null;
1223                     } else {
1224                         connection = findTerminals(out1Link, out2Link, removedCells);
1225                         points = getDirectPoints(splitBlock, out1Link, out2Link);
1226                     }
1227                 } else {
1228                     connection = null;
1229                 }
1230
1231                 if (connection != null) {
1232                     connectedCells.add(connection);
1233                     connectedPoints.add(points);
1234                 }
1235             }
1236         }
1237
1238         final Object[] ret;
1239         getModel().beginUpdate();
1240         try {
1241             ret = super.removeCells(removedCells.toArray(), includeEdges);
1242             for (int i = 0; i < connectedCells.size(); i++) {
1243                 final BasicPort[] connection = connectedCells.get(i);
1244                 final List<mxPoint> points = connectedPoints.get(i);
1245                 if (!removedCells.contains(connection[0].getParent()) && !removedCells.contains(connection[1].getParent())) {
1246                     connect(connection[0], connection[1], points, new mxPoint());
1247                 }
1248             }
1249         } finally {
1250             getModel().endUpdate();
1251         }
1252         return ret;
1253     }
1254
1255     /**
1256      * Add any terminal parent to the removed cells
1257      *
1258      * @param terminal
1259      *            the current terminal (instance of BasicPort)
1260      * @param removedCells
1261      *            the "to be removed" set
1262      * @param loopCells
1263      *            the "while loop" set
1264      */
1265     private void addTerminalParent(mxICell terminal, Collection<Object> removedCells, Collection<Object> loopCells) {
1266         assert (terminal == null || terminal instanceof BasicPort);
1267         assert (removedCells != null);
1268         assert (loopCells != null);
1269
1270         // getting terminal parent
1271         mxICell target = null;
1272         if (terminal != null) {
1273             target = terminal.getParent();
1274         } else {
1275             target = null;
1276         }
1277
1278         // add target if applicable
1279         if (target instanceof SplitBlock) {
1280             if (removedCells.add(target)) {
1281                 loopCells.add(target);
1282             }
1283         }
1284     }
1285
1286     /**
1287      * Find the terminals when relinking the 2 links
1288      *
1289      * This method ensure that {source, target} are not child of removed blocks.
1290      *
1291      * @param linkSource
1292      *            the normal source link
1293      * @param linkTerminal
1294      *            the normal target link
1295      * @param removedCells
1296      *            the set of removed objects
1297      * @return the {source, target} connection
1298      */
1299     private BasicPort[] findTerminals(final mxICell linkSource, final mxICell linkTerminal, final Set<Object> removedCells) {
1300         BasicPort src = (BasicPort) linkSource.getTerminal(true);
1301         BasicPort tgt = (BasicPort) linkTerminal.getTerminal(false);
1302         if (linkSource instanceof ImplicitLink) {
1303             if (removedCells.contains(src.getParent())) {
1304                 src = (BasicPort) linkSource.getTerminal(false);
1305             }
1306             if (removedCells.contains(tgt.getParent())) {
1307                 tgt = (BasicPort) linkTerminal.getTerminal(true);
1308             }
1309         }
1310
1311         return new BasicPort[] { src, tgt };
1312     }
1313
1314     /**
1315      * Get the direct points from inLink.getSource() to outLink.getTarget().
1316      *
1317      * @param splitBlock
1318      *            the current splitblock (added as a mid-point)
1319      * @param inLink
1320      *            the link before the split
1321      * @param outLink
1322      *            the link after the split
1323      * @return the points
1324      */
1325     private List<mxPoint> getDirectPoints(final SplitBlock splitBlock, final mxICell inLink, final mxICell outLink) {
1326         List<mxPoint> points;
1327         // add the points before the split
1328         points = new ArrayList<mxPoint>();
1329         if (inLink.getGeometry().getPoints() != null) {
1330             points.addAll(inLink.getGeometry().getPoints());
1331         }
1332
1333         // add a new point at the split location
1334         points.add(new mxPoint(snap(splitBlock.getGeometry().getCenterX()), snap(splitBlock.getGeometry().getCenterY())));
1335
1336         // add the points after the split
1337         if (outLink.getGeometry().getPoints() != null) {
1338             points.addAll(outLink.getGeometry().getPoints());
1339         }
1340
1341         return points;
1342     }
1343
1344     /**
1345      * Manage Group to be CellFoldable i.e with a (-) to reduce and a (+) to
1346      * expand them. Labels (mxCell instance with value) should not have a
1347      * visible foldable sign.
1348      *
1349      * @param cell
1350      *            the selected cell
1351      * @param collapse
1352      *            the collapse settings
1353      * @return always <code>false</code>
1354      * @see com.mxgraph.view.mxGraph#isCellFoldable(java.lang.Object, boolean)
1355      */
1356     @Override
1357     public boolean isCellFoldable(final Object cell, final boolean collapse) {
1358         return false;
1359     }
1360
1361     /**
1362      * Not BasicBLock cell have a moveable label.
1363      *
1364      * @param cell
1365      *            the cell
1366      * @return true if the corresponding label is moveable
1367      * @see com.mxgraph.view.mxGraph#isLabelMovable(java.lang.Object)
1368      */
1369     @Override
1370     public boolean isLabelMovable(Object cell) {
1371         return !(cell instanceof BasicBlock);
1372     }
1373
1374     /**
1375      * Return true if selectable
1376      *
1377      * @param cell
1378      *            the cell
1379      * @return status
1380      * @see com.mxgraph.view.mxGraph#isCellSelectable(java.lang.Object)
1381      */
1382     @Override
1383     public boolean isCellSelectable(final Object cell) {
1384         if (cell instanceof BasicPort) {
1385             return false;
1386         }
1387         return super.isCellSelectable(cell);
1388     }
1389
1390     /**
1391      * Return true if movable
1392      *
1393      * @param cell
1394      *            the cell
1395      * @return status
1396      * @see com.mxgraph.view.mxGraph#isCellMovable(java.lang.Object)
1397      */
1398     @Override
1399     public boolean isCellMovable(final Object cell) {
1400         if (cell instanceof BasicPort) {
1401             return false;
1402         }
1403
1404         boolean movable = false;
1405         final Object[] cells = getSelectionCells();
1406
1407         // don't move if selection is only links
1408         for (Object c : cells) {
1409             if (!(c instanceof BasicLink)) {
1410                 movable = true;
1411                 break;
1412             }
1413         }
1414
1415         return movable && super.isCellMovable(cell);
1416     }
1417
1418     /**
1419      * Return true if resizable
1420      *
1421      * @param cell
1422      *            the cell
1423      * @return status
1424      * @see com.mxgraph.view.mxGraph#isCellResizable(java.lang.Object)
1425      */
1426     @Override
1427     public boolean isCellResizable(final Object cell) {
1428         if (cell instanceof SplitBlock) {
1429             return false;
1430         }
1431         return (cell instanceof BasicBlock) && super.isCellResizable(cell);
1432     }
1433
1434     /**
1435      * A cell is deletable if it is not a locked block or an identifier cell
1436      *
1437      * @param cell
1438      *            the cell
1439      * @return status
1440      * @see com.mxgraph.view.mxGraph#isCellDeletable(java.lang.Object)
1441      */
1442     @Override
1443     public boolean isCellDeletable(final Object cell) {
1444         final boolean isALockedBLock = cell instanceof BasicBlock && ((BasicBlock) cell).isLocked();
1445         final boolean isAnIdentifier = cell.getClass().equals(mxCell.class);
1446
1447         if (isALockedBLock) {
1448             return false;
1449         }
1450         if (isAnIdentifier) {
1451             return true;
1452         }
1453
1454         return super.isCellDeletable(cell);
1455     }
1456
1457     /**
1458      * Return true if editable
1459      *
1460      * @param cell
1461      *            the cell
1462      * @return status
1463      * @see com.mxgraph.view.mxGraph#isCellEditable(java.lang.Object)
1464      */
1465     @Override
1466     public boolean isCellEditable(final Object cell) {
1467         return (cell instanceof TextBlock) && super.isCellDeletable(cell);
1468     }
1469
1470     /**
1471      * Return or create the identifier for the cell
1472      *
1473      * @param cell
1474      *            the cell to check
1475      * @return the identifier cell
1476      */
1477     public mxCell getOrCreateCellIdentifier(final mxCell cell) {
1478         if (cell.getId().endsWith(HASH_IDENTIFIER)) {
1479             return cell;
1480         }
1481
1482         final mxGraphModel graphModel = (mxGraphModel) getModel();
1483
1484         final mxCell identifier;
1485         final String cellId = cell.getId() + HASH_IDENTIFIER;
1486         if (graphModel.getCell(cellId) == null) {
1487             // create the identifier
1488             identifier = createCellIdentifier(cell);
1489
1490             // add the identifier
1491             graphModel.add(cell, identifier, cell.getChildCount());
1492         } else {
1493             identifier = (mxCell) graphModel.getCell(cellId);
1494         }
1495         return identifier;
1496     }
1497
1498     /**
1499      * Return the identifier for the cell
1500      *
1501      * @param cell
1502      *            the cell to check
1503      * @return the identifier cell
1504      */
1505     public mxCell getCellIdentifier(final mxCell cell) {
1506         final mxGraphModel graphModel = (mxGraphModel) getModel();
1507         final String cellId = cell.getId() + HASH_IDENTIFIER;
1508
1509         return (mxCell) graphModel.getCell(cellId);
1510     }
1511
1512     /**
1513      * Create a cell identifier for a specific cell
1514      *
1515      * @param cell
1516      *            the cell
1517      * @return the cell identifier.
1518      */
1519     public mxCell createCellIdentifier(final mxCell cell) {
1520         final mxCell identifier;
1521         final String cellId = cell.getId() + HASH_IDENTIFIER;
1522
1523         identifier = new mxCell(null, (mxGeometry) DEFAULT_LABEL_GEOMETRY.clone(), "noLabel=0;opacity=0;");
1524         identifier.getGeometry().setRelative(true);
1525         identifier.setVertex(true);
1526         identifier.setConnectable(false);
1527         identifier.setId(cellId);
1528
1529         return identifier;
1530     }
1531
1532     /**
1533      * Get the label for the cell according to its style.
1534      *
1535      * @param cell
1536      *            the cell object
1537      * @return a representative the string (block name) or a style specific
1538      *         style.
1539      * @see com.mxgraph.view.mxGraph#convertValueToString(java.lang.Object)
1540      */
1541     @Override
1542     public String convertValueToString(Object cell) {
1543         String ret = null;
1544
1545         if (cell != null) {
1546             JavaController controller = new JavaController();
1547             final Map<String, Object> style = getCellStyle(cell);
1548
1549             final String displayedLabel = (String) style.get("displayedLabel");
1550             if (displayedLabel != null) {
1551                 if (cell instanceof BasicBlock) {
1552                     try {
1553                         VectorOfDouble v = new VectorOfDouble();
1554                         controller.getObjectProperty(((BasicBlock) cell).getUID(), Kind.BLOCK, ObjectProperties.EXPRS, v);
1555
1556                         // FIXME : decode these exprs
1557                         //                        ret = String.format(displayedLabel, ((BasicBlock) cell).getExprsFormat());
1558                         ret = String.format(displayedLabel, "Not handled exprs ; please report a bug");
1559                     } catch (IllegalFormatException e) {
1560                         LOG.severe(e.toString());
1561                         ret = displayedLabel;
1562                     }
1563                 } else {
1564                     ret = displayedLabel;
1565                 }
1566             } else {
1567                 final String label = super.convertValueToString(cell);
1568                 if (label.isEmpty() && cell instanceof BasicBlock) {
1569                     String[] interfaceFunction = new String[1];
1570
1571                     controller.getObjectProperty(((BasicBlock) cell).getUID(), Kind.BLOCK, ObjectProperties.INTERFACE_FUNCTION, interfaceFunction);
1572                     ret = interfaceFunction[0];
1573                 } else {
1574                     ret = label;
1575                 }
1576             }
1577         }
1578
1579         return ret;
1580     }
1581
1582     /**
1583      * Return true if auto sized
1584      *
1585      * @param cell
1586      *            the cell
1587      * @return status
1588      * @see com.mxgraph.view.mxGraph#isAutoSizeCell(java.lang.Object)
1589      */
1590     @Override
1591     public boolean isAutoSizeCell(final Object cell) {
1592         boolean status = super.isAutoSizeCell(cell);
1593
1594         if (cell instanceof AfficheBlock) {
1595             status |= true;
1596         }
1597
1598         if (cell instanceof TextBlock) {
1599             status &= false;
1600         }
1601
1602         return status;
1603     }
1604
1605     /**
1606      * {@inheritDoc} Do not extends if the port position is north or south.
1607      */
1608     @Override
1609     public boolean isExtendParent(Object cell) {
1610         final boolean extendsParents;
1611
1612         if (cell instanceof BasicPort) {
1613             final BasicPort p = (BasicPort) cell;
1614             extendsParents = !(p.getOrientation() == Orientation.NORTH || p.getOrientation() == Orientation.SOUTH) && super.isExtendParent(p);
1615         } else {
1616             extendsParents = super.isExtendParent(cell);
1617         }
1618         return extendsParents;
1619     }
1620
1621     /**
1622      * @return the parameters
1623      */
1624     public ScicosParameters getScicosParameters() {
1625         return scicosParameters;
1626     }
1627
1628     /**
1629      * @return the model ID
1630      */
1631     public long getUID() {
1632         return uid;
1633     }
1634
1635     /**
1636      * @return Kind.DIAGRAM or Kind.BLOCK
1637      */
1638     public Kind getKind() {
1639         return kind;
1640     }
1641
1642     /**
1643      * Manage the visibility of the grid and the associated menu
1644      *
1645      * @param status
1646      *            new status
1647      */
1648     public void setGridVisible(final boolean status) {
1649         setGridEnabled(status);
1650         getAsComponent().setGridVisible(status);
1651         getAsComponent().repaint();
1652     }
1653
1654     /**
1655      * @return save status
1656      */
1657     public boolean saveDiagram() {
1658         final boolean isSuccess = saveDiagramAs(getSavedFile());
1659
1660         if (isSuccess) {
1661             setModified(false);
1662         }
1663
1664         return isSuccess;
1665     }
1666
1667     /**
1668      * @param fileName
1669      *            diagram filename
1670      * @return save status
1671      */
1672     public boolean saveDiagramAs(final File fileName) {
1673         boolean isSuccess = false;
1674         File writeFile = fileName;
1675         XcosFileType format = XcosOptions.getPreferences().getFileFormat();
1676
1677         info(XcosMessages.SAVING_DIAGRAM);
1678         if (fileName == null) {
1679             final SwingScilabFileChooser fc = SaveAsAction.createFileChooser();
1680             SaveAsAction.configureFileFilters(fc);
1681             ConfigurationManager.configureCurrentDirectory(fc);
1682
1683             if (getSavedFile() != null) {
1684                 // using save-as, the file chooser should have a filename
1685                 // without extension as default
1686                 String filename = getSavedFile().getName();
1687                 filename = filename.substring(0, filename.lastIndexOf('.'));
1688                 fc.setSelectedFile(new File(filename));
1689             } else {
1690                 final String title = getTitle();
1691                 if (title != null) {
1692                     /*
1693                      * Escape file to handle not supported character in file
1694                      * name (may be Windows only) see
1695                      * http://msdn.microsoft.com/en
1696                      * -us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
1697                      */
1698                     final char[] regex = "<>:\"/\\|?*".toCharArray();
1699                     String escaped = title;
1700                     for (char c : regex) {
1701                         escaped = escaped.replace(c, '-');
1702                     }
1703
1704                     fc.setSelectedFile(new File(escaped));
1705                 }
1706             }
1707
1708             int status = fc.showSaveDialog(this.getAsComponent());
1709             if (status != JFileChooser.APPROVE_OPTION) {
1710                 info(XcosMessages.EMPTY_INFO);
1711                 return isSuccess;
1712             }
1713
1714             // change the format if the user choose a save-able file format
1715             final XcosFileType selectedFilter = XcosFileType.findFileType(fc.getFileFilter());
1716             if (XcosFileType.getAvailableSaveFormats().contains(selectedFilter)) {
1717                 format = selectedFilter;
1718             }
1719             writeFile = fc.getSelectedFile();
1720         }
1721
1722         /* Extension/format update */
1723
1724         // using a String filename also works on a non-existing file
1725         final String filename = writeFile.getName();
1726
1727         /*
1728          * Look for the user extension if it does not exists, append a default
1729          * one
1730          *
1731          * if the specified extension is handled, update the save format ; else
1732          * append a default extension and use the default format
1733          */
1734         XcosFileType userExtension = XcosFileType.findFileType(filename);
1735         if (userExtension == null) {
1736             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
1737             userExtension = format;
1738         }
1739         if (XcosFileType.getAvailableSaveFormats().contains(userExtension)) {
1740             format = userExtension;
1741         } else {
1742             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
1743         }
1744
1745         /*
1746          * If the file exists, ask for confirmation if this is not the
1747          * previously saved file
1748          */
1749         if (writeFile.exists() && !writeFile.equals(getSavedFile())) {
1750             final boolean overwrite = ScilabModalDialog.show(XcosTab.get(this), XcosMessages.OVERWRITE_EXISTING_FILE, XcosMessages.XCOS,
1751                                       IconType.QUESTION_ICON, ButtonType.YES_NO) == AnswerOption.YES_OPTION;
1752
1753             if (!overwrite) {
1754                 info(XcosMessages.EMPTY_INFO);
1755                 return false;
1756             }
1757         }
1758
1759         /*
1760          * Really save the data
1761          */
1762         try {
1763             format.save(writeFile.getCanonicalPath(), getRootDiagram());
1764             setSavedFile(writeFile);
1765
1766             setTitle(writeFile.getName().substring(0, writeFile.getName().lastIndexOf('.')));
1767             ConfigurationManager.getInstance().addToRecentFiles(writeFile);
1768             setModified(false);
1769             isSuccess = true;
1770         } catch (final Exception e) {
1771             LOG.severe(e.toString());
1772
1773             XcosDialogs.couldNotSaveFile(this);
1774         }
1775
1776         info(XcosMessages.EMPTY_INFO);
1777         return isSuccess;
1778     }
1779
1780     /**
1781      * Perform post loading initialization.
1782      *
1783      * @param file
1784      *            the loaded file
1785      */
1786     public void postLoad(final File file) {
1787         final String name = file.getName();
1788
1789         if (XcosFileType.getAvailableSaveFormats().contains(XcosFileType.findFileType(file))) {
1790             setSavedFile(file);
1791         }
1792         setTitle(name.substring(0, name.lastIndexOf('.')));
1793         setModified(false);
1794
1795         fireEvent(new mxEventObject(mxEvent.ROOT));
1796
1797         info(XcosMessages.EMPTY_INFO);
1798     }
1799
1800     /**
1801      * Set the title of the diagram
1802      *
1803      * @param title
1804      *            the title
1805      * @see org.scilab.modules.graph.ScilabGraph#setTitle(java.lang.String)
1806      */
1807     @Override
1808     public void setTitle(final String title) {
1809         super.setTitle(title);
1810         updateTabTitle();
1811
1812         JavaController controller = new JavaController();
1813         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.TITLE, title);
1814     }
1815
1816     /**
1817      * Update the title
1818      */
1819     public void updateTabTitle() {
1820         // get the modifier string
1821         final String modified;
1822         if (isModified()) {
1823             modified = "*";
1824         } else {
1825             modified = "";
1826         }
1827
1828         // get the title string
1829         final String title = getTitle();
1830
1831         // get the path
1832         CharSequence formattedPath = "";
1833         final File savedFile = getSavedFile();
1834         if (savedFile != null) {
1835             try {
1836                 final String path = savedFile.getCanonicalPath();
1837                 formattedPath = new StringBuilder().append(" (").append(path).append(')');
1838             } catch (final IOException e) {
1839                 LOG.warning(e.toString());
1840             }
1841         }
1842
1843         // Product name
1844         final String product = Xcos.TRADENAME;
1845
1846         final String tabTitle = new StringBuilder().append(modified).append(title).append(formattedPath).append(" - ").append(product).toString();
1847
1848         final SwingScilabDockablePanel tab = ScilabTabFactory.getInstance().getFromCache(getGraphTab());
1849         if (tab != null) {
1850             tab.setName(tabTitle);
1851         }
1852     }
1853
1854     /**
1855      * Load a file with different method depending on it extension
1856      *
1857      * @param controller the used controller
1858      * @param file
1859      *            File to load (can be null)
1860      */
1861     public void transformAndLoadFile(final JavaController controller, final String file) {
1862         final File f;
1863         final XcosFileType filetype;
1864         if (file != null) {
1865             f = new File(file);
1866             filetype = XcosFileType.findFileType(f);
1867         } else {
1868             f = null;
1869             filetype = null;
1870         }
1871         new SwingWorker<XcosDiagram, ActionEvent>() {
1872             int counter = 0;
1873             final Timer t = new Timer(1000, new ActionListener() {
1874                 @Override
1875                 public void actionPerformed(ActionEvent e) {
1876                     counter = (counter + 1) % (XcosMessages.DOTS.length() + 1);
1877                     String str = XcosMessages.LOADING_DIAGRAM + XcosMessages.DOTS.substring(0, counter);
1878
1879                     XcosDiagram.this.info(str);
1880                 }
1881             });
1882
1883             @Override
1884             protected XcosDiagram doInBackground() {
1885                 t.start();
1886
1887                 final Xcos instance = Xcos.getInstance();
1888                 XcosDiagram diag = XcosDiagram.this;
1889
1890                 diag.setReadOnly(true);
1891
1892                 /*
1893                  * Load, log errors and notify
1894                  */
1895                 synchronized (instance) {
1896                     try {
1897
1898                         if (f != null && filetype != null) {
1899                             filetype.load(file, XcosDiagram.this);
1900                         } else {
1901                             //                          FIXME: implement the model decoding
1902                             //                          controller.getObjectProperty(uid, k, p, v)
1903                         }
1904
1905                         instance.setLastError("");
1906                     } catch (Exception e) {
1907                         Throwable ex = e;
1908                         while (ex instanceof RuntimeException) {
1909                             ex = ex.getCause();
1910                         }
1911                         instance.setLastError(ex.getMessage());
1912                     }
1913                     instance.notify();
1914                 }
1915
1916                 return diag;
1917             }
1918
1919             @Override
1920             protected void done() {
1921                 t.stop();
1922                 XcosDiagram.this.setReadOnly(false);
1923                 XcosDiagram.this.getUndoManager().clear();
1924                 XcosDiagram.this.refresh();
1925
1926                 /*
1927                  * Load has finished
1928                  */
1929                 if (f != null && filetype != null) {
1930                     postLoad(f);
1931                 }
1932                 XcosDiagram.this.info(XcosMessages.EMPTY_INFO);
1933             }
1934
1935         } .execute();
1936     }
1937
1938     /**
1939      * Getting the root diagram of a decomposed diagram
1940      *
1941      * @return Root parent of the whole parent
1942      */
1943     public XcosDiagram getRootDiagram() {
1944         JavaController controller = new JavaController();
1945         long[] parent = new long[1];
1946         controller.getObjectProperty(uid, kind, ObjectProperties.PARENT_DIAGRAM, parent);
1947
1948         Collection<XcosDiagram> diagrams = Xcos.getInstance().getDiagrams(parent[0]);
1949         return diagrams.stream().filter(d -> d.getUID() == parent[0])
1950                .findFirst().get();
1951     }
1952
1953     /**
1954      * Returns the tooltip to be used for the given cell.
1955      *
1956      * @param cell
1957      *            block
1958      * @return cell tooltip
1959      */
1960     @Override
1961     public String getToolTipForCell(final Object cell) {
1962         if (cell instanceof XcosCell) {
1963             return String.valueOf(((XcosCell) cell).getUID());
1964         }
1965         return "";
1966     }
1967
1968     /**
1969      * Display the message in info bar.
1970      *
1971      * @param message
1972      *            Information
1973      */
1974     public void info(final String message) {
1975         final XcosTab tab = XcosTab.get(this);
1976
1977         if (tab != null && tab.getInfoBar() != null) {
1978             tab.getInfoBar().setText(message);
1979         }
1980     }
1981
1982     /**
1983      * Display the message into an error popup
1984      *
1985      * @param message
1986      *            Error of the message
1987      */
1988     public void error(final String message) {
1989         JOptionPane.showMessageDialog(getAsComponent(), message, XcosMessages.XCOS, JOptionPane.ERROR_MESSAGE);
1990     }
1991
1992     /**
1993      * Find the block corresponding to the given uid and display a warning
1994      * message.
1995      *
1996      * @param uid
1997      *            - A String as UID.
1998      * @param message
1999      *            - The message to display.
2000      */
2001     public void warnCellByUID(final String uid, final String message) {
2002         final Object cell = ((mxGraphModel) getModel()).getCell(uid);
2003         if (cell == null) {
2004             return;
2005         }
2006
2007         if (GraphicsEnvironment.isHeadless()) {
2008             System.err.printf("%s at %s\n    %s: %s\n", "warnCell", getRootDiagram().getTitle(), uid, message);
2009             return;
2010         }
2011
2012         // open the tab
2013         if (XcosTab.get(this) == null) {
2014             XcosTab.restore(this);
2015         }
2016
2017         if (message.isEmpty()) {
2018             getAsComponent().clearCellOverlays(cell);
2019         } else {
2020             getAsComponent().setCellWarning(cell, message, null, true);
2021         }
2022     }
2023
2024     @Override
2025     public File getSavedFile() {
2026         if (kind == Kind.DIAGRAM) {
2027             return super.getSavedFile();
2028         } else {
2029             return getRootDiagram().getSavedFile();
2030         }
2031     }
2032
2033     /**
2034      * Set the current diagram in a modified state
2035      *
2036      * @param modified
2037      *            True or False whether the current diagram must be saved or
2038      *            not.
2039      */
2040     @Override
2041     public void setModified(final boolean modified) {
2042         super.setModified(modified);
2043         updateTabTitle();
2044     }
2045
2046     /**
2047      * Evaluate the current context
2048      *
2049      * @return The resulting data. Keys are variable names and Values are
2050      *         evaluated values.
2051      */
2052     public Map<String, String> evaluateContext() {
2053         Map<String, String> result = Collections.emptyMap();
2054         // FIXME: evaluate the context on scilab 6 ?
2055         //        final ScilabDirectHandler handler = ScilabDirectHandler.acquire();
2056         //        if (handler == null) {
2057         //            return result;
2058         //        }
2059         //
2060         //        try {
2061         //            // first write the context strings
2062         //            handler.writeContext(getContext());
2063         //
2064         //            // evaluate using script2var
2065         //            ScilabInterpreterManagement.synchronousScilabExec(ScilabDirectHandler.CONTEXT + " = script2var(" + ScilabDirectHandler.CONTEXT + ", struct());");
2066         //
2067         //            // read the structure
2068         //            result = handler.readContext();
2069         //        } catch (final InterpreterException e) {
2070         //            info("Unable to evaluate the contexte");
2071         //            e.printStackTrace();
2072         //        } finally {
2073         //            handler.release();
2074         //        }
2075
2076         return result;
2077     }
2078
2079     /**
2080      * Returns true if the given cell is a not a block nor a port.
2081      *
2082      * @param cell
2083      *            the drop target
2084      * @param cells
2085      *            the cells to be dropped
2086      * @return the drop status
2087      * @see com.mxgraph.view.mxGraph#isValidDropTarget(java.lang.Object,
2088      *      java.lang.Object[])
2089      */
2090     @Override
2091     public boolean isValidDropTarget(final Object cell, final Object[] cells) {
2092         return !(cell instanceof BasicBlock) && !(cell instanceof BasicBlock) && !(cell instanceof BasicPort) && super.isValidDropTarget(cell, cells);
2093     }
2094
2095     /**
2096      * Construct a new selection model used on this graph.
2097      *
2098      * @return a new selection model instance.
2099      * @see com.mxgraph.view.mxGraph#createSelectionModel()
2100      */
2101     @Override
2102     protected mxGraphSelectionModel createSelectionModel() {
2103         return new mxGraphSelectionModel(this) {
2104             /**
2105              * When we only want to select a cell which is a port, select the
2106              * parent block.
2107              *
2108              * @param cell
2109              *            the cell
2110              * @see com.mxgraph.view.mxGraphSelectionModel#setCell(java.lang.Object)
2111              */
2112             @Override
2113             public void setCell(final Object cell) {
2114                 final Object current;
2115                 if (cell instanceof BasicPort) {
2116                     current = getModel().getParent(cell);
2117                 } else {
2118                     current = cell;
2119                 }
2120                 super.setCell(current);
2121             }
2122         };
2123     }
2124
2125     @Override
2126     protected void finalize() throws Throwable {
2127         JavaController controller = new JavaController();
2128         controller.deleteObject(uid);
2129     }
2130 }