Merge remote-tracking branch 'origin/master' into YaSp
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / graph / SuperBlockDiagram.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009-2009 - DIGITEO - Antoine ELIAS
4  * Copyright (C) 2009-2010 - DIGITEO - Clement DAVID
5  *
6  * This file must be used under the terms of the CeCILL.
7  * This source file is licensed as described in the file COPYING, which
8  * you should have received as part of this distribution.  The terms
9  * are also available at
10  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
11  *
12  */
13
14 package org.scilab.modules.xcos.graph;
15
16 import java.io.File;
17 import java.io.Serializable;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.Comparator;
21 import java.util.Hashtable;
22 import java.util.List;
23 import java.util.logging.Logger;
24
25 import org.scilab.modules.types.ScilabDouble;
26 import org.scilab.modules.xcos.Xcos;
27 import org.scilab.modules.xcos.block.BasicBlock;
28 import org.scilab.modules.xcos.block.SuperBlock;
29 import org.scilab.modules.xcos.block.io.ContextUpdate;
30 import org.scilab.modules.xcos.block.io.ContextUpdate.IOBlocks;
31 import org.scilab.modules.xcos.block.io.EventInBlock;
32 import org.scilab.modules.xcos.block.io.EventOutBlock;
33 import org.scilab.modules.xcos.block.io.ExplicitInBlock;
34 import org.scilab.modules.xcos.block.io.ExplicitOutBlock;
35 import org.scilab.modules.xcos.block.io.ImplicitInBlock;
36 import org.scilab.modules.xcos.block.io.ImplicitOutBlock;
37 import org.scilab.modules.xcos.utils.XcosEvent;
38 import org.scilab.modules.xcos.utils.XcosMessages;
39
40 import com.mxgraph.model.mxCell;
41 import com.mxgraph.model.mxICell;
42 import com.mxgraph.util.mxEvent;
43 import com.mxgraph.util.mxEventObject;
44 import com.mxgraph.util.mxUtils;
45
46 public final class SuperBlockDiagram extends XcosDiagram implements Serializable, Cloneable {
47
48     private static final String PARENT_DIAGRAM_WAS_NULL = "Parent diagram was null";
49     private static final long serialVersionUID = -402918614723713301L;
50
51     private static final String IN = "in";
52     private static final String OUT = "out";
53     private static final String EIN = "ein";
54     private static final String EOUT = "eout";
55
56     private SuperBlock container;
57
58     /**
59      * Constructor
60      */
61     public SuperBlockDiagram() {
62         super();
63     }
64
65     /**
66      * @param superBlock
67      *            parent super block
68      */
69     public SuperBlockDiagram(SuperBlock superBlock) {
70         super();
71         setContainer(superBlock);
72     }
73
74     /**
75      * @return parent super block
76      */
77     public SuperBlock getContainer() {
78         return container;
79     }
80
81     /**
82      * @param container
83      *            parent super block
84      */
85     public void setContainer(SuperBlock container) {
86         this.container = container;
87     }
88
89     @Override
90     public File getSavedFile() {
91         if (getContainer() != null) {
92             XcosDiagram parent = getContainer().getParentDiagram();
93             if (parent != null) {
94                 return parent.getSavedFile();
95             }
96         }
97
98         return super.getSavedFile();
99     }
100
101     /**
102      * Concatenate the context with the parent one
103      *
104      * @return the context
105      * @see org.scilab.modules.xcos.graph.XcosDiagram#getContext()
106      */
107     @Override
108     public String[] getContext() {
109         final SuperBlock block = getContainer();
110         XcosDiagram graph = block.getParentDiagram();
111         if (graph == null) {
112             block.setParentDiagram(Xcos.findParent(block));
113             graph = block.getParentDiagram();
114             Logger.getLogger(SuperBlockDiagram.class.getName()).finest(PARENT_DIAGRAM_WAS_NULL);
115         }
116
117         final String[] parent;
118         if (graph == null) {
119             parent = new String[] {};
120         } else {
121             parent = graph.getContext();
122         }
123
124         final String[] current = super.getContext();
125
126         String[] full = new String[current.length + parent.length];
127         System.arraycopy(parent, 0, full, 0, parent.length);
128         System.arraycopy(current, 0, full, parent.length, current.length);
129         return full;
130     }
131
132     /**
133      * Validate I/O ports.
134      *
135      * @param cell
136      *            Cell that represents the cell to validate.
137      * @param context
138      *            Hashtable that represents the global validation state.
139      */
140     @SuppressWarnings("unchecked")
141     @Override
142     public String validateCell(final Object cell, final Hashtable<Object, Object> context) {
143         String err = null;
144
145         /*
146          * Only validate I/O blocks
147          */
148
149         // get the key
150         final String key;
151         if (cell instanceof ExplicitInBlock || cell instanceof ImplicitInBlock) {
152             key = IN;
153         } else if (cell instanceof ExplicitOutBlock || cell instanceof ImplicitOutBlock) {
154             key = OUT;
155         } else if (cell instanceof EventInBlock) {
156             key = EIN;
157         } else if (cell instanceof EventOutBlock) {
158             key = EOUT;
159         } else {
160             key = null;
161         }
162
163         final BasicBlock block;
164         if (key != null) {
165             block = (BasicBlock) cell;
166         } else {
167             return err;
168         }
169
170         /*
171          * Prepare validation
172          */
173
174         // fill the context
175         fillContext(context);
176
177         /*
178          * Validate with ipar
179          */
180
181         // get the real index
182         final List <? extends BasicBlock > blocks = (List <? extends BasicBlock > ) context.get(key);
183         final int realIndex = blocks.indexOf(block) + 1;
184
185         // get the user index
186         final ScilabDouble data = (ScilabDouble) block.getIntegerParameters();
187         if (data.getWidth() < 1 || data.getHeight() < 1) {
188             return err;
189         }
190         final int userIndex = (int) data.getRealPart()[0][0];
191
192         // if the indexes are not equals, alert the user.
193         if (realIndex != userIndex) {
194             final StringBuilder str = new StringBuilder();
195             str.append("<html><body><em>");
196             str.append(XcosMessages.WRONG_PORT_NUMBER);
197             str.append("</em><br/>");
198             str.append(String.format(XcosMessages.EXPECTING_NUMBER, realIndex));
199             str.append("</body></html>    ");
200
201             err = str.toString();
202         }
203
204         // Update the port labels on the superblock
205         if (err == null) {
206             mxCell identifier = this.getOrCreateCellIdentifier(block);
207             final Object current = this.getModel().getValue(identifier);
208             String text = "";
209             if (current == null) {
210                 text = "";
211             } else {
212                 text = mxUtils.getBodyMarkup(current.toString(), false);
213             }
214             this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED, "cell", identifier, "value", text, "parent", block));
215         }
216
217         return err;
218     }
219
220     /**
221      * Fill the context with I/O port
222      *
223      * @param context
224      *            the context to fill
225      */
226     @SuppressWarnings("unchecked")
227     private void fillContext(final Hashtable<Object, Object> context) {
228         if (!context.containsKey(IN)) {
229             context.put(IN, iparSort(getAllTypedBlock(new Class[] { ExplicitInBlock.class, ImplicitInBlock.class })));
230         }
231         if (!context.containsKey(OUT)) {
232             context.put(OUT, iparSort(getAllTypedBlock(new Class[] { ExplicitOutBlock.class, ImplicitOutBlock.class })));
233         }
234         if (!context.containsKey(EIN)) {
235             context.put(EIN, iparSort(getAllTypedBlock(EventInBlock.class)));
236         }
237         if (!context.containsKey(EOUT)) {
238             context.put(EOUT, iparSort(getAllTypedBlock(EventOutBlock.class)));
239         }
240     }
241
242     /**
243      * Sort the blocks per first integer parameter value
244      *
245      * @param blocks
246      *            the block list
247      * @return the sorted block list (same instance)
248      */
249     private List <? extends BasicBlock > iparSort(final List <? extends BasicBlock > blocks) {
250         Collections.sort(blocks, new Comparator<BasicBlock>() {
251
252             @Override
253             public int compare(BasicBlock o1, BasicBlock o2) {
254                 final ScilabDouble data1 = (ScilabDouble) o1.getIntegerParameters();
255                 final ScilabDouble data2 = (ScilabDouble) o2.getIntegerParameters();
256
257                 int value1 = 0;
258                 int value2 = 0;
259
260                 if (data1.getWidth() >= 1 && data1.getHeight() >= 1) {
261                     value1 = (int) data1.getRealPart()[0][0];
262                 }
263                 if (data2.getWidth() >= 1 && data2.getHeight() >= 1) {
264                     value2 = (int) data2.getRealPart()[0][0];
265                 }
266
267                 return value1 - value2;
268             }
269         });
270         return blocks;
271     }
272
273     /**
274      * @param <T>
275      *            The type to work on
276      * @param klass
277      *            the class instance to work on
278      * @return list of typed block
279      */
280     @SuppressWarnings("unchecked")
281     private <T extends BasicBlock> List<T> getAllTypedBlock(Class<T> klass) {
282         final List<T> list = new ArrayList<T>();
283
284         int blockCount = getModel().getChildCount(getDefaultParent());
285
286         for (int i = 0; i < blockCount; i++) {
287             Object cell = getModel().getChildAt(getDefaultParent(), i);
288             if (klass.isInstance(cell)) {
289                 // According to the test we are sure that the cell is an
290                 // instance of T. Thus we can safely cast it.
291                 list.add((T) cell);
292             }
293         }
294         return list;
295     }
296
297     /**
298      * @param <T>
299      *            The type to work on
300      * @param klasses
301      *            the class instance list to work on
302      * @return list of typed block
303      */
304     private <T extends BasicBlock> List<T> getAllTypedBlock(Class<T>[] klasses) {
305         final List<T> list = new ArrayList<T>();
306         for (Class<T> klass : klasses) {
307             list.addAll(getAllTypedBlock(klass));
308         }
309         return list;
310     }
311
312     /**
313      * Listener for SuperBlock diagram events.
314      */
315     @SuppressWarnings(value = { "serial" })
316     private static final class GenericSuperBlockListener implements mxIEventListener, Serializable {
317         private static GenericSuperBlockListener instance;
318
319         /**
320          * Reduce constructor visibility
321          */
322         private GenericSuperBlockListener() {
323             super();
324         }
325
326         /**
327          * Mono-threaded singleton implementation getter
328          *
329          * @return The unique instance
330          */
331         public static GenericSuperBlockListener getInstance() {
332             if (instance == null) {
333                 instance = new GenericSuperBlockListener();
334             }
335             return instance;
336         }
337
338         /**
339          * Update the IOPorts colors and values.
340          *
341          * @param arg0
342          *            the source
343          * @param arg1
344          *            the event data
345          * @see com.mxgraph.util.mxEventSource.mxIEventListener#invoke(java.lang.Object,
346          *      com.mxgraph.util.mxEventObject)
347          */
348         @Override
349         public void invoke(Object arg0, mxEventObject arg1) {
350             final SuperBlockDiagram graph = ((SuperBlockDiagram) arg0);
351             final SuperBlock block = graph.getContainer();
352             if (block != null) {
353                 block.updateExportedPort();
354             }
355         }
356     }
357
358     /**
359      * Listener for SuperBlock diagram events.
360     @SuppressWarnings(value = { "serial" })
361      */
362     private static final class ContentChangedListener implements mxIEventListener, Serializable {
363         private static ContentChangedListener instance;
364
365         /**
366          * Reduce constructor visibility
367          */
368         private ContentChangedListener() {
369             super();
370         }
371
372         /**
373          * Mono-threaded singleton implementation getter
374          *
375          * @return The unique instance
376          */
377         public static ContentChangedListener getInstance() {
378             if (instance == null) {
379                 instance = new ContentChangedListener();
380             }
381             return instance;
382         }
383
384         /**
385          * Update the IOPorts colors and values.
386          *
387          * @param arg0
388          *            the source
389          * @param arg1
390          *            the event data
391          * @see com.mxgraph.util.mxEventSource.mxIEventListener#invoke(java.lang.Object,
392          *      com.mxgraph.util.mxEventObject)
393          */
394         @Override
395         public void invoke(Object arg0, mxEventObject arg1) {
396             final SuperBlockDiagram graph = ((SuperBlockDiagram) arg0);
397             final SuperBlock block = graph.getContainer();
398             if (block != null) {
399                 block.invalidateRpar();
400             }
401         }
402     }
403
404     /**
405     @SuppressWarnings(value = { "serial" })
406      * Update the diagram labels
407      */
408     private static final class LabelBlockListener implements mxIEventListener, Serializable {
409         private static LabelBlockListener instance;
410
411         /**
412          * Default Constructor
413          */
414         private LabelBlockListener() {
415             super();
416         }
417
418         /**
419          * @return the instance
420          */
421         public static LabelBlockListener getInstance() {
422             if (instance == null) {
423                 instance = new LabelBlockListener();
424             }
425             return instance;
426         }
427
428         /** {@inheritDoc} */
429         @Override
430         public void invoke(Object sender, mxEventObject evt) {
431             final String value = (String) evt.getProperty("value");
432             final Object parent = evt.getProperty("parent");
433             if (parent instanceof ContextUpdate) {
434                 final ContextUpdate block = (ContextUpdate) parent;
435                 final ScilabDouble data = (ScilabDouble) block.getIntegerParameters();
436
437                 final int index;
438                 if (data.getHeight() > 0 && data.getWidth() > 0) {
439                     index = (int) data.getRealPart()[0][0];
440                 } else {
441                     index = 1;
442                 }
443
444                 final SuperBlock container = ((SuperBlockDiagram) sender).getContainer();
445                 if (container == null) {
446                     return;
447                 }
448
449                 container.sortChildren();
450                 final List<mxICell> ports = IOBlocks.getPorts(container, block.getClass());
451
452                 XcosDiagram graph = container.getParentDiagram();
453                 if (graph == null) {
454                     container.setParentDiagram(Xcos.findParent(container));
455                     graph = container.getParentDiagram();
456                     Logger.getLogger(SuperBlockDiagram.class.getName()).finest(PARENT_DIAGRAM_WAS_NULL);
457                 }
458
459                 if (index > 0 && index <= ports.size()) {
460                     container.getParentDiagram().cellLabelChanged(ports.get(index - 1), value, false);
461                 }
462             }
463         }
464     }
465
466     /**
467      * Install the specific listeners for {@link SuperBlockDiagram}.
468      */
469     public void installSuperBlockListeners() {
470         removeListener(GenericSuperBlockListener.getInstance());
471         removeListener(LabelBlockListener.getInstance());
472         removeListener(ContentChangedListener.getInstance());
473
474         addListener(mxEvent.CELLS_ADDED, GenericSuperBlockListener.getInstance());
475         addListener(mxEvent.CELLS_REMOVED, GenericSuperBlockListener.getInstance());
476         addListener(XcosEvent.IO_PORT_VALUE_UPDATED, GenericSuperBlockListener.getInstance());
477         addListener(mxEvent.LABEL_CHANGED, LabelBlockListener.getInstance());
478
479         addListener(mxEvent.CELLS_ADDED, ContentChangedListener.getInstance());
480         addListener(mxEvent.CELLS_MOVED, ContentChangedListener.getInstance());
481         addListener(mxEvent.CELLS_REMOVED, ContentChangedListener.getInstance());
482         addListener(mxEvent.CELLS_RESIZED, ContentChangedListener.getInstance());
483     }
484
485     /**
486      * This function set the SuperBlock diagram and all its parents in a
487      * modified state or not.
488      *
489      * @param modified
490      *            status
491      */
492     @Override
493     public void setModified(boolean modified) {
494         super.setModified(modified);
495
496         if (getContainer() != null && getContainer().getParentDiagram() != null) {
497             getContainer().getParentDiagram().setModified(modified);
498         }
499     }
500
501     /**
502      * This function set the SuperBlock diagram in a modified state or not.
503      *
504      * It doesn't perform recursively on the parent diagrams. If you want such a
505      * behavior use setModified instead.
506      *
507      * @param modified
508      *            status
509      * @see #setModified
510      */
511     public void setModifiedNonRecursively(boolean modified) {
512         super.setModified(modified);
513     }
514
515     /** {@inheritDoc} */
516     // CSOFF: SuperClone
517     @Override
518     public Object clone() throws CloneNotSupportedException {
519         final SuperBlockDiagram clone = new SuperBlockDiagram();
520         clone.installListeners();
521         clone.installSuperBlockListeners();
522
523         clone.setScicosParameters((ScicosParameters) getScicosParameters().clone());
524         clone.addCells(cloneCells(getChildCells(getDefaultParent())), clone.getDefaultParent());
525
526         return clone;
527     }
528     // CSON: SuperClone
529 }