001/* A MoMLChangeRequest that offsets any objects that are created.
002
003   Copyright (c) 2007-2016 The Regents of the University of California.
004   All rights reserved.
005   Permission is hereby granted, without written agreement and without
006   license or royalty fees, to use, copy, modify, and distribute this
007   software and its documentation for any purpose, provided that the above
008   copyright notice and the following two paragraphs appear in all copies
009   of this software.
010
011   IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012   FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013   ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014   THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015   SUCH DAMAGE.
016
017   THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020   PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021   CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022   ENHANCEMENTS, OR MODIFICATIONS.
023
024   PT_COPYRIGHT_VERSION_2
025   COPYRIGHTENDKEY
026   2
027 */
028package ptolemy.vergil.basic;
029
030import java.awt.MouseInfo;
031import java.awt.Point;
032import java.awt.geom.AffineTransform;
033import java.awt.geom.Rectangle2D;
034import java.util.HashSet;
035import java.util.Iterator;
036import java.util.Set;
037
038import javax.swing.SwingUtilities;
039
040import diva.canvas.Figure;
041import diva.canvas.interactor.Interactor;
042import diva.canvas.interactor.SelectionInteractor;
043import diva.canvas.interactor.SelectionModel;
044import diva.graph.GraphController;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.Locatable;
047import ptolemy.kernel.util.NamedObj;
048import ptolemy.moml.MoMLChangeRequest;
049import ptolemy.moml.MoMLParser;
050import ptolemy.util.MessageHandler;
051
052///////////////////////////////////////////////////////////////////
053//// OffsetMoMLChangeRequest
054/**
055 A mutation request specified in MoML that offsets any objects
056 that are created in the toplevel.
057
058 <p>This class is used by the paste action in Vergil so that the
059 pasted icon does not overlap the original icon.
060 If a BasicGraphFrame can be found, then the position of the mouse
061 is used to determine the offsite.  Otherwise, a small offset is
062 used.</p>
063
064 <p>The pasted objects are selected so that the can be moved as a
065 group.</p>
066
067 @author  Christopher Brooks, based on code from BasicGraphFrame by Edward A. Lee
068 @version $Id$
069 @since Ptolemy II 6.1
070 @Pt.ProposedRating Red (cxh)
071 @Pt.AcceptedRating Red (cxh)
072 */
073public class OffsetMoMLChangeRequest extends MoMLChangeRequest {
074
075    /** Construct a mutation request to be executed in the specified
076     *  context.  The context is typically a Ptolemy II container,
077     *  such as an entity, within which the objects specified by the
078     *  MoML code will be placed.  This method resets and uses a
079     *  parser that is a static member of this class.
080     *  A listener to changes will probably want to check the originator
081     *  so that when it is notified of errors or successful completion
082     *  of changes, it can tell whether the change is one it requested.
083     *  Alternatively, it can call waitForCompletion().
084     *  All external references are assumed to be absolute URLs.  Whenever
085     *  possible, use a different constructor that specifies the base.
086     *  @param originator The originator of the change request.
087     *  @param context The context in which to execute the MoML.
088     *  @param request The mutation request in MoML.
089     */
090    public OffsetMoMLChangeRequest(Object originator, NamedObj context,
091            String request) {
092        super(originator, context, request);
093        _context = context;
094    }
095
096    ///////////////////////////////////////////////////////////////////
097    ////                         protected method                  ////
098
099    /** Offset the locations of top level objects that are created
100     *  by the change request.
101     *  If a BasicGraphFrame can be found, then the position of the mouse
102     *  is used to determine the offsite.  Otherwise, a small offset is
103     *  used.
104     *  @param parser The parser
105     */
106    @Override
107    protected void _postParse(MoMLParser parser) {
108        // Find the upper-most, left-most location.  Note that
109        // this is the center of the component.
110        double[] minimumLocation = new double[] { Double.MAX_VALUE,
111                Double.MAX_VALUE };
112        Iterator topObjects = parser.topObjectsCreated().iterator();
113        while (topObjects.hasNext()) {
114            NamedObj topObject = (NamedObj) topObjects.next();
115            Iterator locations = topObject.attributeList(Locatable.class)
116                    .iterator();
117            while (locations.hasNext()) {
118                Locatable location = (Locatable) locations.next();
119                double[] locationValue = location.getLocation();
120                for (int i = 0; i < locationValue.length
121                        && i < minimumLocation.length; i++) {
122                    if (locationValue[i] < minimumLocation[i]) {
123                        minimumLocation[i] = locationValue[i];
124                    }
125                }
126            }
127        }
128
129        double xOffset = _PASTE_OFFSET;
130        double yOffset = _PASTE_OFFSET;
131        double scale = 1.0;
132        GraphController controller = null;
133        // If there is a basic graph frame, then get the mouse location.
134        BasicGraphFrame basicGraphFrame = BasicGraphFrame
135                .getBasicGraphFrame(_context);
136        if (basicGraphFrame != null) {
137            controller = basicGraphFrame.getJGraph().getGraphPane()
138                    .getGraphController();
139            Point componentLocation = basicGraphFrame.getJGraph().getGraphPane()
140                    .getCanvas().getLocationOnScreen();
141
142            AffineTransform current = basicGraphFrame.getJGraph()
143                    .getCanvasPane().getTransformContext().getTransform();
144
145            // We assume the scaling in the X and Y directions are the same.
146            scale = current.getScaleX();
147
148            Rectangle2D visibleCanvas = basicGraphFrame
149                    .getVisibleCanvasRectangle();
150
151            // Get the mouse location.  We don't use a MouseMotionListener here because we
152            // need the mouse location only when we paste.
153            Point mouseLocation = MouseInfo.getPointerInfo().getLocation();
154
155            // Take in to account the panner and read values from visibleCanvas.
156            //xOffset = mouseLocation.x - componentLocation.x - minimumLocation[0];
157            //yOffset = mouseLocation.y - componentLocation.y - minimumLocation[1];
158
159            // We adjust by the scale here to get from screen coordinates to model coordinates?
160            xOffset = (mouseLocation.x - componentLocation.x) / scale
161                    + visibleCanvas.getX() - minimumLocation[0];
162            yOffset = (mouseLocation.y - componentLocation.y) / scale
163                    + visibleCanvas.getY() - minimumLocation[1];
164
165            //System.out.println("OffsetMoMLChangeRequest: mouse.x: " + mouseLocation.x + " comp.x: " + componentLocation.x + " visCanv.x: " + visibleCanvas.getX() + " min.x: " + minimumLocation[0] + " scale: " + scale + " xOff: " + xOffset + " " + visibleCanvas);
166            //System.out.println("OffsetMoMLChangeRequest: mouse.y: " + mouseLocation.y + " comp.y: " + componentLocation.y + " visCanv.y: " + visibleCanvas.getY() + " min.y: " + minimumLocation[1] + " scale: " + scale + " yOff: " + yOffset);
167        }
168
169        NamedObj container = null;
170        final Set _topObjects = new HashSet<NamedObj>();
171
172        // Update the locations.
173        topObjects = parser.topObjectsCreated().iterator();
174        while (topObjects.hasNext()) {
175            NamedObj topObject = (NamedObj) topObjects.next();
176            _topObjects.add(topObject);
177            if (container == null) {
178                container = topObject.getContainer();
179            }
180            try {
181                // Update the location of each top object.
182                Iterator locations = topObject.attributeList(Locatable.class)
183                        .iterator();
184                while (locations.hasNext()) {
185                    Locatable location = (Locatable) locations.next();
186                    double[] locationValue = location.getLocation();
187                    for (int i = 0; i < locationValue.length; i++) {
188                        if (i == 0) {
189                            locationValue[i] += xOffset;
190                        } else if (i == 1) {
191                            locationValue[i] += yOffset;
192                        } else {
193                            locationValue[i] += _PASTE_OFFSET;
194                        }
195                        location.setLocation(locationValue);
196                    }
197                }
198            } catch (IllegalActionException e) {
199                MessageHandler.error("Change failed", e);
200            }
201        }
202
203        if (controller != null) {
204            // Select the pasted objects so that they can be dragged.
205            // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3003
206
207            final GraphController controllerFinal = controller;
208            final NamedObj containerFinal = container;
209            Runnable doHelloWorld = new Runnable() {
210                @Override
211                public void run() {
212                    Interactor interactor = null;
213                    try {
214                        interactor = controllerFinal
215                                .getEdgeController(new Object())
216                                .getEdgeInteractor();
217                    } catch (Exception ex) {
218                        interactor = controllerFinal.getNodeController(null)
219                                .getNodeInteractor();
220                    }
221                    SelectionInteractor selectionInteractor = (SelectionInteractor) interactor;
222                    selectionInteractor.getSelectionRenderer();
223                    SelectionModel selectionModel = controllerFinal
224                            .getSelectionModel();
225                    selectionModel.clearSelection();
226                    AbstractBasicGraphModel graphModel = (AbstractBasicGraphModel) controllerFinal
227                            .getGraphModel();
228
229                    // If we copy text from annotation and then paste
230                    // it in to the background we get a NPE here.
231                    if (graphModel != null && containerFinal != null) {
232                        Iterator nodes = graphModel.nodes(containerFinal);
233                        while (nodes.hasNext()) {
234                            Locatable node = (Locatable) nodes.next();
235                            NamedObj entity = (NamedObj) graphModel
236                                    .getSemanticObject(node);
237                            if (_topObjects.contains(entity)) {
238                                // If we don't do this in an invokeLater, then the
239                                // canvas will not be updated so the controller will
240                                // not have the figures and this will be null.
241                                Figure figure = controllerFinal.getFigure(node);
242                                selectionModel.addSelection(figure);
243                            }
244                        }
245                    }
246                }
247            };
248
249            SwingUtilities.invokeLater(doHelloWorld);
250        }
251
252        parser.clearTopObjectsList();
253    }
254
255    /** Clear the list of top objects.
256     *  @param parser The parser
257     */
258    @Override
259    protected void _preParse(MoMLParser parser) {
260        super._preParse(parser);
261        parser.clearTopObjectsList();
262    }
263
264    ///////////////////////////////////////////////////////////////////
265    ////                         private variables                 ////
266
267    /** The context in which to execute the moml. */
268    private NamedObj _context;
269
270    /** Offset used when pasting objects. */
271    private static int _PASTE_OFFSET = 10;
272
273}