* Bug #13030 fixed - Selection to Super block did not reset the origin
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / block / actions / RegionToSuperblockAction.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab Copyright (C)
3  * 2011 - DIGITEO - Clement DAVID This file must be used under the terms of the
4  * CeCILL. This source file is licensed as described in the file COPYING, which
5  * you should have received as part of this distribution. The terms are also
6  * available at http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
7  */
8
9 package org.scilab.modules.xcos.block.actions;
10
11 import java.awt.event.ActionEvent;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collection;
15 import java.util.LinkedHashSet;
16 import java.util.LinkedList;
17 import java.util.Queue;
18 import java.util.Set;
19 import java.util.TreeSet;
20 import java.util.logging.Logger;
21
22 import org.scilab.modules.graph.ScilabComponent;
23 import org.scilab.modules.graph.ScilabGraph;
24 import org.scilab.modules.graph.actions.base.VertexSelectionDependantAction;
25 import org.scilab.modules.gui.menuitem.MenuItem;
26 import org.scilab.modules.types.ScilabDouble;
27 import org.scilab.modules.types.ScilabString;
28 import org.scilab.modules.xcos.Xcos;
29 import org.scilab.modules.xcos.block.BasicBlock;
30 import org.scilab.modules.xcos.block.BlockFactory;
31 import org.scilab.modules.xcos.block.SuperBlock;
32 import org.scilab.modules.xcos.block.io.ContextUpdate;
33 import org.scilab.modules.xcos.block.io.ContextUpdate.IOBlocks;
34 import org.scilab.modules.xcos.graph.SuperBlockDiagram;
35 import org.scilab.modules.xcos.graph.XcosDiagram;
36 import org.scilab.modules.xcos.link.BasicLink;
37 import org.scilab.modules.xcos.port.BasicPort;
38 import org.scilab.modules.xcos.port.control.ControlPort;
39 import org.scilab.modules.xcos.port.input.InputPort;
40 import org.scilab.modules.xcos.port.output.OutputPort;
41 import org.scilab.modules.xcos.utils.BlockPositioning;
42 import org.scilab.modules.xcos.utils.XcosMessages;
43
44 import com.mxgraph.model.mxGeometry;
45 import com.mxgraph.model.mxGraphModel;
46 import com.mxgraph.model.mxICell;
47 import com.mxgraph.util.mxRectangle;
48
49 /**
50  * Move the Selected cells to a new SuperBlock diagram
51  */
52 // CSOFF: ClassFanOutComplexity
53 @SuppressWarnings(value = { "serial" })
54 public class RegionToSuperblockAction extends VertexSelectionDependantAction {
55     /** Name of the action */
56     public static final String NAME = XcosMessages.REGION_TO_SUPERBLOCK;
57     /** Icon name of the action */
58     public static final String SMALL_ICON = "object-group";
59     /** Mnemonic key of the action */
60     public static final int MNEMONIC_KEY = 0;
61     /** Accelerator key for the action */
62     public static final int ACCELERATOR_KEY = 0;
63
64     private static final String INTERFUNCTION_NAME = "SUPER_f";
65
66     /**
67      * Default Constructor
68      *
69      * @param scilabGraph
70      *            the graph
71      */
72     public RegionToSuperblockAction(ScilabGraph scilabGraph) {
73         super(scilabGraph);
74     }
75
76     /**
77      * @param scilabGraph
78      *            graph
79      * @return menu item
80      */
81     public static MenuItem createMenu(ScilabGraph scilabGraph) {
82         return createMenu(scilabGraph, RegionToSuperblockAction.class);
83     }
84
85     /**
86      * Represent a broken link at selection time
87      */
88     private static final class Broken implements Comparable<Broken> {
89         private final mxGraphModel parentModel;
90
91         private final BasicPort source;
92         private final BasicPort target;
93
94         private final BasicLink parentLink;
95         private final boolean containsSource;
96
97         /*
98          * Out of selection port position
99          */
100         private final double x;
101         private final double y;
102
103         /*
104          * lazy allocated fields
105          */
106         private BasicLink childLink;
107         private BasicPort parentPort;
108         private ContextUpdate childBlock;
109
110         /**
111          * Default Constructor
112          *
113          * @param parentModel
114          *            the parent model
115          * @param source
116          *            the source
117          * @param target
118          *            the target
119          * @param link
120          *            the link
121          * @param containsSource
122          *            is the source in selection
123          */
124         public Broken(mxGraphModel parentModel, BasicPort source, BasicPort target, BasicLink link, boolean containsSource) {
125             super();
126             this.parentModel = parentModel;
127
128             this.source = source;
129             this.target = target;
130             this.parentLink = link;
131             this.containsSource = containsSource;
132
133             final BasicPort terminal;
134             if (containsSource) {
135                 terminal = target;
136             } else {
137                 terminal = source;
138             }
139
140             /*
141              * Update position
142              */
143             final mxGeometry pos = parentModel.getGeometry(terminal);
144             final mxGeometry parent = parentModel.getGeometry(parentModel.getParent(terminal));
145             if (pos != null && parent != null) {
146                 this.x = pos.getX() + parent.getX() + (pos.getWidth() / 2) - (parent.getWidth() / 2);
147                 this.y = pos.getY() + parent.getY() + (pos.getHeight() / 2) - (parent.getHeight() / 2);
148             } else {
149                 this.x = 0.0;
150                 this.y = 0.0;
151             }
152         }
153
154         /**
155          * @return the link on the parent diagram
156          */
157         public BasicLink getParentLink() {
158             return parentLink;
159         }
160
161         /**
162          * @param isSource
163          *            is a source port needed
164          * @return the parent terminal (source or not)
165          */
166         public mxICell getParentTerminal(boolean isSource) {
167             final mxICell ret;
168
169             if (containsSource) {
170                 if (isSource) {
171                     ret = getParentPort();
172                 } else {
173                     ret = target;
174                 }
175             } else {
176                 if (isSource) {
177                     ret = source;
178                 } else {
179                     ret = getParentPort();
180                 }
181             }
182
183             return ret;
184         }
185
186         /**
187          * @param isSource
188          *            is a source port needed
189          * @return the child terminal (source or not)
190          */
191         public mxICell getChildTerminal(boolean isSource) {
192             final mxICell ret;
193
194             if (containsSource) {
195                 if (isSource) {
196                     ret = source;
197                 } else {
198                     ret = getChildBlock().getChildAt(0);
199                 }
200             } else {
201                 if (isSource) {
202                     ret = getChildBlock().getChildAt(0);
203                 } else {
204                     ret = target;
205                 }
206             }
207
208             return ret;
209         }
210
211         /**
212          * @return the terminal in the selection
213          */
214         public BasicPort getTerminal() {
215             if (containsSource) {
216                 return source;
217             } else {
218                 return target;
219             }
220         }
221
222         /**
223          * @return the link on the child diagram
224          */
225         public BasicLink getChildLink() {
226             if (childLink == null) {
227                 childLink = (BasicLink) parentModel.cloneCells(new Object[] { parentLink }, true)[0];
228             }
229             return childLink;
230         }
231
232         /**
233          * @return the port on the parent diagram
234          */
235         public BasicPort getParentPort() {
236             if (parentPort == null) {
237                 try {
238                     if (containsSource) {
239                         parentPort = IOBlocks.getOpposite(target.getClass()).newInstance();
240                     } else {
241                         parentPort = IOBlocks.getOpposite(source.getClass()).newInstance();
242                     }
243                 } catch (InstantiationException e) {
244                     Logger.getLogger(RegionToSuperblockAction.class.getName()).severe(e.toString());
245                 } catch (IllegalAccessException e) {
246                     Logger.getLogger(RegionToSuperblockAction.class.getName()).severe(e.toString());
247                 }
248             }
249
250             return parentPort;
251         }
252
253         /**
254          * @return the child block to put on the child diagram
255          */
256         public ContextUpdate getChildBlock() {
257             if (childBlock == null) {
258                 final BasicPort terminal = getTerminal();
259                 childBlock = IOBlocks.createBlock(terminal);
260
261                 /*
262                  * Set the child position
263                  */
264                 childBlock.getGeometry().setX(x);
265                 childBlock.getGeometry().setY(y);
266             }
267
268             return childBlock;
269         }
270
271         /**
272          * Set the ordering on the I/O block and port
273          *
274          * @param ordering
275          *            the ordering to set
276          */
277         public void setOrdering(int ordering) {
278             // update the child ordering
279             getChildBlock().setIntegerParameters(new ScilabDouble(ordering));
280             getChildBlock().setExprs(new ScilabString(Integer.toString(ordering)));
281
282             // update the port value
283             getParentPort().setOrdering(ordering);
284         }
285
286         /**
287          * {@inheritDoc}
288          *
289          * This function is used to sort a {@link TreeSet} of {@link Broken}.
290          */
291         @Override
292         public int compareTo(Broken o) {
293             final int xdiff = (int) (x - o.x);
294             final int ydiff = (int) (y - o.y);
295
296             if (xdiff == 0 && ydiff == 0) {
297                 // same position, sort by hashcode
298                 return hashCode() - o.hashCode();
299             } else {
300                 return (ydiff << (Integer.SIZE / 2)) + xdiff;
301             }
302         }
303
304         /**
305          * {@inheritDoc}
306          */
307         @Override
308         public String toString() {
309             final String labelSep = ": ";
310             final String link = " -> ";
311
312             final StringBuilder str = new StringBuilder();
313             if (getParentLink().getChildCount() > 0) {
314                 // append the label
315                 str.append(getParentLink().getChildAt(0).getValue());
316                 str.append(labelSep);
317             }
318
319             str.append(getParentTerminal(true));
320             str.append(link);
321             str.append(getParentTerminal(false));
322
323             str.append('\n');
324
325             if (getChildLink().getChildCount() > 0) {
326                 // append the label
327                 str.append(getChildLink().getChildAt(0).getValue());
328                 str.append(labelSep);
329             }
330
331             str.append(getChildTerminal(true));
332             str.append(link);
333             str.append(getChildTerminal(false));
334
335             return str.toString();
336         }
337     }
338
339     /**
340      * {@inheritDoc}
341      */
342     @Override
343     public void actionPerformed(ActionEvent e) {
344         final XcosDiagram parentGraph = (XcosDiagram) getGraph(e);
345
346         // action disabled when the cell is edited
347         final ScilabComponent comp = ((ScilabComponent) parentGraph.getAsComponent());
348         if (comp.isEditing()) {
349             return;
350         }
351
352         parentGraph.info(XcosMessages.GENERATE_SUPERBLOCK);
353         parentGraph.getModel().beginUpdate();
354         try {
355
356             final SuperBlock superBlock;
357             final Collection<Broken> brokenLinks;
358             final Set<Object> inSelectionCells;
359
360             /*
361              * Allocate superBlock
362              */
363             final Object[] selection = parentGraph.getSelectionCells();
364             final Object[] blocks = XcosDiagram.filterByClass(selection, BasicBlock.class);
365             inSelectionCells = new LinkedHashSet<Object>(Arrays.asList(blocks));
366
367             superBlock = allocateSuperBlock(parentGraph, selection);
368
369             /*
370              * First perform all modification on the parent diagram to handle
371              * well undo/redo operations.
372              */
373             brokenLinks = updateParent(parentGraph, superBlock, inSelectionCells);
374
375             /*
376              * Then move some cells to the child diagram
377              */
378             final SuperBlockDiagram childGraph = moveToChild(parentGraph, superBlock, brokenLinks, inSelectionCells);
379
380             /*
381              * Finish the install on the child and sync it.
382              */
383             childGraph.installListeners();
384             childGraph.installSuperBlockListeners();
385             superBlock.invalidateRpar();
386             superBlock.updateExportedPort();
387
388             /*
389              * Clear the transaction log in the child.
390              * From the user point of view, the operation cannot be undo'ed.
391              */
392             childGraph.getUndoManager().clear();
393
394             Xcos.getInstance().addDiagram(parentGraph.getSavedFile(), childGraph);
395         } finally {
396             parentGraph.getModel().endUpdate();
397             parentGraph.info(XcosMessages.EMPTY_INFO);
398         }
399     }
400
401     /**
402      * Allocate a superBlock
403      *
404      * @param parentGraph
405      *            the base graph
406      * @param selection
407      *            the selected blocks
408      * @return the allocated super block (without specific listeners)
409      */
410     private SuperBlock allocateSuperBlock(final XcosDiagram parentGraph, final Object[] selection) {
411         final SuperBlock superBlock = (SuperBlock) BlockFactory.createBlock(INTERFUNCTION_NAME);
412         superBlock.setStyle(INTERFUNCTION_NAME);
413
414         /*
415          * Allocate the diagram
416          */
417         final SuperBlockDiagram diag = new SuperBlockDiagram(superBlock);
418         superBlock.setChild(diag);
419         superBlock.setParentDiagram(parentGraph);
420
421         /*
422          * Place the super block
423          */
424         final mxRectangle dims = parentGraph.getBoundingBoxFromGeometry(selection);
425         final double minX = dims.getX();
426         final double maxX = minX + dims.getWidth();
427
428         final double minY = dims.getY();
429         final double maxY = minY + dims.getHeight();
430
431         superBlock.getGeometry().setX((maxX + minX - superBlock.getGeometry().getWidth()) / 2.0);
432         superBlock.getGeometry().setY((maxY + minY - superBlock.getGeometry().getHeight()) / 2.0);
433
434         /*
435          * get statistics
436          */
437         int angleCounter = 0;
438         int flipCounter = 0;
439         int mirrorCounter = 0;
440         for (Object object : selection) {
441             if (object instanceof BasicBlock) {
442                 final BasicBlock b = (BasicBlock) object;
443
444                 angleCounter += b.getAngle();
445                 if (b.getFlip()) {
446                     flipCounter++;
447                 }
448                 if (b.getMirror()) {
449                     mirrorCounter++;
450                 }
451             }
452         }
453
454         /*
455          * apply statistics
456          */
457         final int halfSize = selection.length / 2;
458         superBlock.setAngle(BlockPositioning.roundAngle(angleCounter / selection.length));
459         superBlock.setFlip(flipCounter > halfSize);
460         superBlock.setMirror(mirrorCounter > halfSize);
461
462         return superBlock;
463     }
464
465     /**
466      * Create child cells and add them to the parent diagram. All links are also
467      * reconnected
468      *
469      * @param parentGraph
470      *            the parent diagram
471      * @param superBlock
472      *            the superblock
473      * @param inSelectionCells
474      *            the cells in the selection
475      * @return the broken descriptor set
476      */
477     private Collection<Broken> updateParent(final XcosDiagram parentGraph, final SuperBlock superBlock, final Set<Object> inSelectionCells) {
478         final Collection<Broken> brokenLinks;
479         final mxGraphModel parentModel = (mxGraphModel) parentGraph.getModel();
480
481         parentModel.beginUpdate();
482         try {
483             /*
484              * Add the internal links and fill border links Sort the broken
485              * links by position (to perform a good numbering order) and keep
486              * only one occurrence of a broken link.
487              */
488             brokenLinks = new TreeSet<Broken>();
489             fillLinks(parentModel, inSelectionCells, brokenLinks);
490
491             /*
492              * Disconnect the broken links
493              */
494             for (Broken broken : brokenLinks) {
495                 mxGraphModel.setTerminals(parentModel, broken.getParentLink(), null, null);
496             }
497
498             /*
499              * Add the super block
500              */
501             parentGraph.addCell(superBlock);
502
503             /*
504              * Main broken loop
505              */
506             // ordering access is : IN, OUT, e_IN, e_OUT
507             final int[] ordering = { 0, 0, 0, 0 };
508             for (Broken broken : brokenLinks) {
509
510                 // set the ordering
511                 incrementOrdering(ordering, broken);
512
513                 connectParent(parentGraph, parentModel, superBlock, broken);
514                 connectChild(parentGraph, parentModel, broken);
515
516                 /*
517                  * Update the view
518                  */
519                 BlockPositioning.updateBlockView(broken.getChildBlock());
520             }
521         } finally {
522             parentModel.endUpdate();
523         }
524
525         return brokenLinks;
526     }
527
528     /**
529      * Fill the internalLinks links from a selected block to a selected block
530      * and broken links with a broken object. Also disconnect all the broken
531      * links.
532      *
533      * @param parentModel
534      *            the model
535      * @param inSelectionCells
536      *            the selected blocks (with fast contains operation)
537      * @param brokenLinks
538      *            the broken links to find.
539      */
540     private void fillLinks(final mxGraphModel parentModel, final Collection<Object> inSelectionCells, final Collection<Broken> brokenLinks) {
541         final Queue<Object> loopQueue = new LinkedList<Object>(inSelectionCells);
542
543         while (!loopQueue.isEmpty()) {
544             final Object cell = loopQueue.remove();
545
546             final int childCount = parentModel.getChildCount(cell);
547             for (int i = 0; i < childCount; i++) {
548                 final Object port = parentModel.getChildAt(cell, i);
549
550                 final int edgeCount = parentModel.getEdgeCount(port);
551                 for (int j = 0; j < edgeCount; j++) {
552                     final Object edge = parentModel.getEdgeAt(port, j);
553
554                     final Object source = parentModel.getTerminal(edge, true);
555                     final Object target = parentModel.getTerminal(edge, false);
556
557                     /*
558                      * Add the links
559                      */
560                     final boolean containsSource = inSelectionCells.contains(parentModel.getParent(source));
561                     final boolean containsTarget = inSelectionCells.contains(parentModel.getParent(target));
562
563                     if (containsSource && containsTarget) {
564                         inSelectionCells.add(edge);
565                         continue;
566                     }
567
568                     /*
569                      * Handle a broken link case
570                      */
571
572                     if (containsSource) {
573                         brokenLinks.add(new Broken(parentModel, (BasicPort) source, (BasicPort) target, (BasicLink) edge, true));
574                         continue;
575                     }
576
577                     if (containsTarget) {
578                         brokenLinks.add(new Broken(parentModel, (BasicPort) source, (BasicPort) target, (BasicLink) edge, false));
579                         continue;
580                     }
581                 }
582             }
583         }
584     }
585
586     /**
587      * Increment and set the ordering to the broken entry.
588      *
589      * @param ordering
590      *            4x1 array of ordering
591      * @param broken
592      *            the current broken entry.
593      */
594     // CSOFF: MagicNumber
595     private void incrementOrdering(final int[] ordering, Broken broken) {
596         if (broken.getTerminal() instanceof InputPort) {
597             broken.setOrdering(++ordering[0]);
598         } else if (broken.getTerminal() instanceof OutputPort) {
599             broken.setOrdering(++ordering[1]);
600         } else if (broken.getTerminal() instanceof ControlPort) {
601             broken.setOrdering(++ordering[2]);
602         } else { // if (broken.getTerminal() instanceof CommandPort)
603             broken.setOrdering(++ordering[3]);
604         }
605     }
606
607     // CSON: MagicNumber
608
609     /**
610      * Add I/O port and reconnect them
611      *
612      * @param parentGraph
613      *            the parent graph
614      * @param parentModel
615      *            the parent graph model
616      * @param superBlock
617      *            the super block
618      * @param broken
619      *            the broken entry
620      */
621     private void connectParent(final XcosDiagram parentGraph, final mxGraphModel parentModel, final SuperBlock superBlock, Broken broken) {
622         parentGraph.addCell(broken.getParentPort(), superBlock);
623         parentGraph.addCell(broken.getParentLink());
624
625         final mxICell source = broken.getParentTerminal(true);
626         final mxICell target = broken.getParentTerminal(false);
627
628         // then connect the link
629         mxGraphModel.setTerminals(parentModel, broken.getParentLink(), source, target);
630     }
631
632     /**
633      * Add I/O blocks and reconnect them
634      *
635      * @param childGraph
636      *            the child graph
637      * @param childModel
638      *            the child graph model
639      * @param broken
640      *            the broken entry
641      */
642     private void connectChild(final XcosDiagram childGraph, final mxGraphModel childModel, Broken broken) {
643         childGraph.addCell(broken.getChildBlock());
644         childGraph.addCell(broken.getChildLink());
645
646         final mxICell source = broken.getChildTerminal(true);
647         final mxICell target = broken.getChildTerminal(false);
648
649         // then connect the link
650         mxGraphModel.setTerminals(childModel, broken.getChildLink(), source, target);
651     }
652
653     /**
654      * Move the cells to the child graph
655      *
656      * @param parentGraph
657      *            the parent graph
658      * @param superBlock
659      *            the superBlock
660      * @param brokenLinks
661      *            the broken links set
662      * @param inSelectionCells
663      *            the cells in selection
664      * @return the superblock child diagram
665      */
666     private SuperBlockDiagram moveToChild(final XcosDiagram parentGraph, final SuperBlock superBlock, final Collection<Broken> brokenLinks,
667                                           final Set<Object> inSelectionCells) {
668         final SuperBlockDiagram childGraph = superBlock.getChild();
669         final mxGraphModel childModel = (mxGraphModel) childGraph.getModel();
670
671         childModel.beginUpdate();
672         try {
673             final Collection<Object> cellsToCopy = new ArrayList<Object>();
674
675             /*
676              * create a collection with all the cells to move
677              */
678             cellsToCopy.addAll(inSelectionCells);
679             for (Broken b : brokenLinks) {
680                 cellsToCopy.add(b.getChildBlock());
681                 cellsToCopy.add(b.getChildLink());
682             }
683
684             // create the local array to use the JGraphX API
685             final Object[] cells = cellsToCopy.toArray();
686
687             /*
688              * Translate the cells to the origin
689              *
690              * NOTE: this should be done on the parent diagram because
691              * getBoundsForCells only works for already added cells (not on the transaction)
692              */
693             mxRectangle rect = parentGraph.getBoundsForCells(cells, true, true, false);
694             parentGraph.moveCells(cells, -rect.getX(), -rect.getY());
695
696             /*
697              * Really copy the cells
698              */
699             parentGraph.removeCells(cells, false);
700             childGraph.addCells(cells);
701
702             childGraph.setChildrenParentDiagram();
703             BlockPositioning.updateBlockView(superBlock);
704         } finally {
705             childModel.endUpdate();
706         }
707
708         return childGraph;
709     }
710 }
711 // CSON: ClassFanOutComplexity