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}