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