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