* Bug #15024 fixed : Xcos labels were not preserved .
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / graph / model / XcosCell.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2015-2017 - Scilab Enterprises - Clement DAVID
4  * Copyright (C) 2017 - ESI Group - Clement DAVID
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16
17 package org.scilab.modules.xcos.graph.model;
18
19 import java.util.Collections;
20 import java.util.List;
21
22 import org.scilab.modules.xcos.JavaController;
23 import org.scilab.modules.xcos.Kind;
24 import org.scilab.modules.xcos.ObjectProperties;
25 import org.scilab.modules.xcos.PortKind;
26 import org.scilab.modules.xcos.VectorOfDouble;
27 import org.scilab.modules.xcos.VectorOfScicosID;
28
29 import com.mxgraph.model.mxCell;
30 import com.mxgraph.model.mxGeometry;
31 import com.mxgraph.model.mxICell;
32 import com.mxgraph.util.mxPoint;
33 import java.rmi.server.UID;
34 import java.util.regex.Pattern;
35 import org.scilab.modules.xcos.block.BasicBlock;
36 import org.scilab.modules.xcos.block.TextBlock;
37
38 /**
39  * An Xcos cell is a JGraphX cell that store most of its information into the
40  * Scicos MVC.
41  *
42  * The reference to the Scicos MVC object is taken at construction time and
43  * released by the destructor. There is thus no need to manage parent /
44  * children, block / port or port / link association referencing as the JVM will
45  * GC the object if needed.
46  */
47 public class XcosCell extends mxCell {
48     private static final long serialVersionUID = 1L;
49     private static final Pattern validCIdentifier = Pattern.compile("[a-zA-Z][a-zA-Z0-9_]+");
50
51     private transient ScicosObjectOwner owner;
52
53     /**
54      * Construct an Xcos graphical object.
55      *
56      * @param controller
57      *            the shared controller
58      * @param uid
59      *            the associated MVC identifier
60      * @param kind
61      *            the associated MVC kind
62      */
63     public XcosCell(final JavaController controller, long uid, Kind kind, Object value, mxGeometry geometry, String style, String id) {
64         super();
65
66         // defensive programming
67         if (uid == 0l) {
68             throw new IllegalArgumentException();
69         }
70
71         owner = new ScicosObjectOwner(controller, uid, kind);
72         setValue(controller, value);
73         setGeometry(controller, geometry);
74         setStyle(controller, style);
75         setId(controller, id);
76     }
77
78     /**
79      * @return the MVC unique identifier
80      */
81     public long getUID() {
82         return owner.getUID();
83     }
84
85     /**
86      * @return the MVC kind of object
87      */
88     public Kind getKind() {
89         return owner.getKind();
90     }
91
92     /*
93      * Override setters and hierarchy modifiers to propagate changes to the model
94      */
95
96     /*
97      * (non-Javadoc)
98      *
99      * @see com.mxgraph.model.mxCell#setValue(java.lang.Object)
100      */
101     @Override
102     public void setValue(Object value) {
103         setValue(new JavaController(), value);
104     }
105
106     public final void setValue(JavaController controller, Object value) {
107         super.setValue(value);
108         setMVCValue(controller, value);
109     }
110
111     @SuppressWarnings("fallthrough")
112     private void setMVCValue(JavaController controller, Object value) {
113         if (value == null) {
114             return;
115         }
116
117         switch (getKind()) {
118             case BLOCK:
119                 if (!(validCIdentifier.matcher(String.valueOf(value)).matches())) {
120                     // a block description should be a valid C / Scilab identifier to ease codegeneration
121                     break;
122                 }
123             case ANNOTATION:
124             case LINK:
125                 controller.setObjectProperty(getUID(), getKind(), ObjectProperties.DESCRIPTION, String.valueOf(value));
126                 break;
127             case PORT:
128                 controller.setObjectProperty(getUID(), getKind(), ObjectProperties.LABEL, String.valueOf(value));
129                 break;
130             default:
131                 break;
132         }
133     }
134
135     /*
136      * (non-Javadoc)
137      *
138      * @see com.mxgraph.model.mxCell#setId(java.lang.String)
139      */
140     @Override
141     public void setId(String id) {
142         setId(new JavaController(), id);
143     }
144
145     public final void setId(JavaController controller, String id) {
146         if (id == null || id.isEmpty()) {
147             id = new UID().toString();
148         }
149
150         super.setId(id);
151         setMVCId(controller, id);
152     }
153
154     private void setMVCId(JavaController controller, String id) {
155         if (id == null) {
156             return;
157         }
158
159         switch (getKind()) {
160             case ANNOTATION:
161             case BLOCK:
162             case LINK:
163             case PORT:
164                 controller.setObjectProperty(getUID(), getKind(), ObjectProperties.UID, id);
165                 break;
166             default:
167                 break;
168         }
169     }
170
171     /*
172      * (non-Javadoc)
173      *
174      * @see com.mxgraph.model.mxCell#setGeometry(com.mxgraph.model.mxGeometry)
175      */
176     @Override
177     public void setGeometry(mxGeometry geometry) {
178         setGeometry(new JavaController(), geometry);
179     }
180
181     public final void setGeometry(JavaController controller, mxGeometry geometry) {
182         super.setGeometry(geometry);
183         setMVCGeometry(controller, geometry);
184     }
185
186     private void setMVCGeometry(JavaController controller, mxGeometry geometry) {
187         if (geometry == null) {
188             return;
189         }
190
191         switch (getKind()) {
192             case ANNOTATION:
193             case BLOCK: {
194                 VectorOfDouble v = new VectorOfDouble(4);
195                 v.set(0, geometry.getX());
196                 v.set(1, geometry.getY());
197                 v.set(2, geometry.getWidth());
198                 v.set(3, geometry.getHeight());
199                 controller.setObjectProperty(getUID(), getKind(), ObjectProperties.GEOMETRY, v);
200                 break;
201             }
202             case LINK: {
203                 /*
204                  * try to find the origin of the source and target accordingly to the JGraphX implementation
205                  */
206                 mxPoint sourcePoint = null;
207                 mxPoint targetPoint = null;
208                 mxICell sourceCell = getSource();
209                 mxICell targetCell = getTarget();
210                 if (sourceCell != null && sourceCell.getGeometry() != null) {
211                     sourcePoint = new mxPoint(sourceCell.getGeometry().getCenterX(), sourceCell.getGeometry().getCenterY());
212                 }
213                 if (targetCell != null && targetCell.getGeometry() != null) {
214                     targetPoint = new mxPoint(targetCell.getGeometry().getCenterX(), targetCell.getGeometry().getCenterY());
215                 }
216                 if (sourcePoint == null) {
217                     sourcePoint = geometry.getSourcePoint();
218                 }
219                 if (targetPoint == null) {
220                     targetPoint = geometry.getTargetPoint();
221                 }
222                 if (sourcePoint == null) {
223                     sourcePoint = new mxPoint();
224                 }
225                 if (targetPoint == null) {
226                     targetPoint = new mxPoint();
227                 }
228                 List<mxPoint> points = geometry.getPoints();
229                 if (points == null) {
230                     points = Collections.emptyList();
231                 }
232
233                 /*
234                  * At that point, the sourcePoint, targetPoint and points are valid values (but may be unknown) encode them to the the CONTROL_POINTS
235                  */
236
237                 // Allocate some space to contains them all
238                 int nbOfPoints = 2 + points.size();
239                 VectorOfDouble v = new VectorOfDouble(2 * nbOfPoints);
240                 int i = 0;
241                 // then fill the allocation space
242                 v.set(2 * i, sourcePoint.getX());
243                 v.set(2 * i + 1, sourcePoint.getY());
244                 i++;
245                 for (; i < nbOfPoints - 1; i++) {
246                     v.set(2 * i, points.get(i - 1).getX());
247                     v.set(2 * i + 1, points.get(i - 1).getY());
248                 }
249                 v.set(2 * i, targetPoint.getX());
250                 v.set(2 * i + 1, targetPoint.getY());
251                 i++;
252                 /*
253                  * Finally push the values to the model
254                  */
255                 controller.setObjectProperty(getUID(), getKind(), ObjectProperties.CONTROL_POINTS, v);
256                 break;
257             }
258             default:
259                 break;
260         }
261     }
262
263     /*
264      * (non-Javadoc)
265      *
266      * @see com.mxgraph.model.mxCell#setStyle(java.lang.String)
267      */
268     @Override
269     public void setStyle(String style) {
270         setStyle(new JavaController(), style);
271     }
272
273     public final void setStyle(JavaController controller, String style) {
274         super.setStyle(style);
275         setMVCStyle(controller, style);
276     }
277
278     private void setMVCStyle(JavaController controller, String style) {
279         if (style == null) {
280             return;
281         }
282
283         switch (getKind()) {
284             case BLOCK:
285             case LINK:
286             case ANNOTATION:
287             case PORT:
288                 controller.setObjectProperty(getUID(), getKind(), ObjectProperties.STYLE, style);
289                 break;
290             default:
291                 break;
292         }
293     }
294
295     /*
296      * (non-Javadoc)
297      *
298      * @see com.mxgraph.model.mxCell#removeFromParent()
299      */
300     @Override
301     public void removeFromParent() {
302         super.removeFromParent();
303
304         // do not remove from parent on SUPER_f diagram creation : there is an MVC parent but no JGraphX one
305         if (parent == null) {
306             return;
307         }
308
309         JavaController controller = new JavaController();
310         switch (getKind()) {
311             case ANNOTATION:
312             case BLOCK:
313             case LINK: {
314                 /*
315                  * Retrieve the parent
316                  */
317                 long[] parent = new long[1];
318                 Kind parentKind = Kind.BLOCK;
319                 ObjectProperties prop = ObjectProperties.PARENT_BLOCK;
320                 controller.getObjectProperty(getUID(), getKind(), prop, parent);
321                 if (parent[0] == 0l) {
322                     parentKind = Kind.DIAGRAM;
323                     prop = ObjectProperties.PARENT_DIAGRAM;
324                     controller.getObjectProperty(getUID(), getKind(), prop, parent);
325                 }
326
327                 /*
328                  * If there is a parent, clear it
329                  */
330                 if (parent[0] != 0l) {
331                     VectorOfScicosID children = new VectorOfScicosID();
332                     controller.getObjectProperty(parent[0], parentKind, ObjectProperties.CHILDREN, children);
333                     children.remove(getUID());
334                     controller.setObjectProperty(parent[0], parentKind, ObjectProperties.CHILDREN, children);
335
336                     controller.setObjectProperty(getUID(), getKind(), prop, 0l);
337                 }
338                 break;
339             }
340             case PORT: {
341                 long[] parent = new long[1];
342                 Kind parentKind = Kind.BLOCK;
343                 controller.getObjectProperty(getUID(), getKind(), ObjectProperties.SOURCE_BLOCK, parent);
344
345                 int[] portKind = new int[1];
346                 controller.getObjectProperty(getUID(), getKind(), ObjectProperties.PORT_KIND, portKind);
347                 ObjectProperties property = relatedPortKindProperty(portKind[0]);
348
349                 VectorOfScicosID ports = new VectorOfScicosID();
350                 controller.getObjectProperty(parent[0], parentKind, property, ports);
351                 ports.remove(getUID());
352                 controller.setObjectProperty(parent[0], parentKind, property, ports);
353
354                 controller.setObjectProperty(getUID(), getKind(), ObjectProperties.SOURCE_BLOCK, 0l);
355                 break;
356             }
357             default:
358                 break;
359         }
360     }
361
362     private ObjectProperties relatedPortKindProperty(int portKind) {
363         ObjectProperties property;
364         switch (PortKind.values()[portKind]) {
365             case PORT_IN:
366                 property = ObjectProperties.INPUTS;
367                 break;
368             case PORT_OUT:
369                 property = ObjectProperties.OUTPUTS;
370                 break;
371             case PORT_EIN:
372                 property = ObjectProperties.EVENT_INPUTS;
373                 break;
374             case PORT_EOUT:
375                 property = ObjectProperties.EVENT_OUTPUTS;
376                 break;
377             default:
378                 property = null;
379                 break;
380         }
381         return property;
382     }
383
384     /*
385      * (non-Javadoc)
386      *
387      * @see com.mxgraph.model.mxCell#setParent(com.mxgraph.model.mxICell)
388      */
389     @Override
390     public void setParent(mxICell parent) {
391         super.setParent(parent);
392
393         if (parent instanceof XcosCell) {
394             XcosCell p = (XcosCell) parent;
395             JavaController controller = new JavaController();
396             switch (getKind()) {
397                 case ANNOTATION:
398                     if (this instanceof TextBlock) {
399                         // a TEXT_f block inside a superblock diagram
400                         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.PARENT_BLOCK, p.getUID());
401
402                         long[] root = new long[1];
403                         controller.getObjectProperty(p.getUID(), p.getKind(), ObjectProperties.PARENT_DIAGRAM, root);
404                         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.PARENT_DIAGRAM, root[0]);
405                     } else {
406                         // an annotation attached to the block
407                         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.RELATED_TO, p.getUID());
408                     }
409                     break;
410                 case BLOCK:
411                 case LINK:
412                     if (p.getKind() == Kind.DIAGRAM) {
413                         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.PARENT_DIAGRAM, p.getUID());
414                     } else {
415                         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.PARENT_BLOCK, p.getUID());
416
417                         long[] root = new long[1];
418                         controller.getObjectProperty(p.getUID(), p.getKind(), ObjectProperties.PARENT_DIAGRAM, root);
419                         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.PARENT_DIAGRAM, root[0]);
420                     }
421                     break;
422                 case PORT:
423                     controller.setObjectProperty(getUID(), getKind(), ObjectProperties.SOURCE_BLOCK, p.getUID());
424                     break;
425                 default:
426                     break;
427             }
428         }
429     }
430
431     @Override
432     public mxICell setTerminal(mxICell terminal, boolean isSource) {
433         mxICell cell = super.setTerminal(terminal, isSource);
434         if (cell == null) {
435             return cell;
436         }
437
438         // a terminal of an XcosCell is always another XcosCell
439         final long uid = ((XcosCell) cell).getUID();
440         final Kind kind = ((XcosCell) cell).getKind();
441
442         JavaController controller = new JavaController();
443         switch (getKind()) {
444             case LINK:
445                 if (isSource) {
446                     controller.setObjectProperty(getUID(), getKind(), ObjectProperties.SOURCE_PORT, uid);
447                 } else {
448                     controller.setObjectProperty(getUID(), getKind(), ObjectProperties.DESTINATION_PORT, uid);
449                 }
450                 if (uid != 0l) {
451                     controller.setObjectProperty(uid, kind, ObjectProperties.CONNECTED_SIGNALS, getUID());
452                 }
453                 break;
454             default:
455                 break;
456         }
457
458         return cell;
459     }
460
461     /*
462      * (non-Javadoc)
463      *
464      * @see com.mxgraph.model.mxCell#insert(com.mxgraph.model.mxICell, int)
465      */
466     @Override
467     public mxICell insert(mxICell child, int index) {
468         mxICell inserted = super.insert(child, index);
469
470         // the child might not be an XcosCell but just an mxCell for whatever reason
471         // it will not be stored on the MVC nor saved on purpose.
472         if (!(child instanceof XcosCell)) {
473             return inserted;
474         }
475
476         XcosCell c = (XcosCell) child;
477
478         // the child is a regular object to add in the Diagram / Superblock hierarchy
479         switch (getKind()) {
480             case BLOCK:
481                 switch (c.getKind()) {
482                     case ANNOTATION:
483                         if (c instanceof TextBlock) {
484                             // a TEXT_f block inside a superblock
485                             insertChild(c, index);
486                         } else {
487                             // an annotation attached to the block
488                             insertLabel(c);
489                         }
490                         break;
491                     case BLOCK:
492                     case LINK:
493                         insertChild(c, index);
494                         break;
495                     case PORT:
496                         insertPort(c, index);
497                         break;
498                 }
499                 break;
500             case DIAGRAM:
501                 insertChild(c, index);
502                 break;
503             default:
504                 break;
505         }
506
507         return inserted;
508     }
509
510     private void insertPort(XcosCell c, int index) {
511         JavaController controller = new JavaController();
512         int[] v = new int[1];
513         controller.getObjectProperty(c.getUID(), c.getKind(), ObjectProperties.PORT_KIND, v);
514
515         VectorOfScicosID children = new VectorOfScicosID();
516         final ObjectProperties property = relatedPortKindProperty(v[0]);
517
518         if (property != null) {
519             controller.getObjectProperty(getUID(), getKind(), property, children);
520             // do no use the index argument as it is not possible to insert out of order on the MVC (as we have kind of port)
521             children.add(c.getUID());
522             controller.setObjectProperty(getUID(), getKind(), property, children);
523         }
524     }
525
526     private void insertChild(XcosCell c, int index) {
527         JavaController controller = new JavaController();
528         VectorOfScicosID children = new VectorOfScicosID();
529
530         controller.getObjectProperty(getUID(), getKind(), ObjectProperties.CHILDREN, children);
531         children.add(c.getUID());
532         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.CHILDREN, children);
533     }
534
535     private void insertLabel(XcosCell c) {
536         JavaController controller = new JavaController();
537
538         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.LABEL, c.getUID());
539     }
540
541     /*
542      * (non-Javadoc)
543      *
544      * @see com.mxgraph.model.mxCell#remove(com.mxgraph.model.mxICell)
545      */
546     @Override
547     public mxICell remove(mxICell child) {
548         mxICell removed = super.remove(child);
549
550         // the child might not be an XcosCell but just an mxCell for whatever reason
551         // it will not be stored on the MVC nor saved on purpose.
552         if (!(child instanceof XcosCell)) {
553             return removed;
554         }
555
556         XcosCell c = (XcosCell) child;
557         switch (getKind()) {
558             case BLOCK:
559                 switch (c.getKind()) {
560                     case ANNOTATION:
561                         if (c instanceof TextBlock) {
562                             // a TEXT_f block inside a superblock
563                             removeChild(c);
564                         } else {
565                             // an annotation attached to the block
566                             removeLabel(c);
567                         }
568                         break;
569                     case BLOCK:
570                     case LINK:
571                         removeChild(c);
572                         break;
573                     case PORT:
574                         removePort(c);
575                         break;
576                 }
577                 break;
578             case DIAGRAM:
579                 removeChild(c);
580                 break;
581             default:
582                 break;
583         }
584         return removed;
585     }
586
587     private void removePort(XcosCell c) {
588         JavaController controller = new JavaController();
589         int[] v = new int[1];
590         controller.getObjectProperty(c.getUID(), c.getKind(), ObjectProperties.PORT_KIND, v);
591
592         VectorOfScicosID children = new VectorOfScicosID();
593         final ObjectProperties property;
594         switch (PortKind.values()[v[0]]) {
595             case PORT_IN:
596                 property = ObjectProperties.INPUTS;
597                 break;
598             case PORT_OUT:
599                 property = ObjectProperties.OUTPUTS;
600                 break;
601             case PORT_EIN:
602                 property = ObjectProperties.EVENT_INPUTS;
603                 break;
604             case PORT_EOUT:
605                 property = ObjectProperties.EVENT_OUTPUTS;
606                 break;
607             default:
608                 property = null;
609                 break;
610         }
611
612         controller.getObjectProperty(getUID(), getKind(), property, children);
613         children.remove(c.getUID());
614         controller.setObjectProperty(getUID(), getKind(), property, children);
615     }
616
617     private void removeChild(XcosCell c) {
618         JavaController controller = new JavaController();
619         VectorOfScicosID children = new VectorOfScicosID();
620
621         controller.getObjectProperty(getUID(), getKind(), ObjectProperties.CHILDREN, children);
622         children.remove(c.getUID());
623         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.CHILDREN, children);
624     }
625
626     private void removeLabel(XcosCell c) {
627         JavaController controller = new JavaController();
628
629         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.LABEL, 0);
630         controller.setObjectProperty(c.getUID(), c.getKind(), ObjectProperties.RELATED_TO, 0);
631     }
632
633     /*
634      * Override methods from Object
635      */
636
637     @Override
638     public Object clone() throws CloneNotSupportedException {
639         JavaController controller = new JavaController();
640         XcosCell c = (XcosCell) super.clone();
641
642         c.owner = new ScicosObjectOwner(controller.cloneObject(getUID(), true, false), getKind());
643         return c;
644     }
645 }