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