001/* An action that is associated with a figure. 002 003 Copyright (c) 2000-2018 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 027 */ 028package ptolemy.vergil.toolbox; 029 030import java.awt.Component; 031import java.awt.Frame; 032import java.awt.event.ActionEvent; 033import java.awt.geom.Rectangle2D; 034import java.lang.ref.WeakReference; 035import java.util.Iterator; 036 037import javax.swing.AbstractAction; 038import javax.swing.JButton; 039import javax.swing.JMenu; 040import javax.swing.JMenuItem; 041import javax.swing.JPopupMenu; 042 043import diva.canvas.CanvasComponent; 044import diva.canvas.CanvasLayer; 045import diva.canvas.CanvasPane; 046import diva.canvas.CanvasUtilities; 047import diva.canvas.Figure; 048import diva.canvas.FigureLayer; 049import diva.canvas.event.LayerEvent; 050import diva.canvas.interactor.BasicGrabHandle; 051import diva.graph.GraphController; 052import diva.graph.GraphModel; 053import diva.graph.GraphPane; 054import diva.graph.JGraph; 055import diva.gui.toolbox.JContextMenu; 056import diva.util.UserObjectContainer; 057import ptolemy.kernel.util.InternalErrorException; 058import ptolemy.kernel.util.Location; 059import ptolemy.kernel.util.NamedObj; 060 061/////////////////////////////////////////////////////////////////// 062//// FigureAction 063 064/** 065 An action that is attached to a figure on a named object. 066 Such an action is fired in one of several ways. 067 The first way is through an ActionInteractor that is attached 068 to the figure. The second way is through a context menu that is created 069 on the figure. A third way is through a hotkey. 070 Unfortunately, the source of the event is different in 071 these cases. This class makes it easier to write an action that is 072 triggered by any mechanism. Such an action would be derived from this 073 class, and would invoke super.actionPerformed() first in its own 074 actionPerformed() method. 075 076 @author Steve Neuendorffer and Edward A. Lee 077 @version $Id$ 078 @since Ptolemy II 1.0 079 @Pt.ProposedRating Red (eal) 080 @Pt.AcceptedRating Red (johnr) 081 */ 082@SuppressWarnings("serial") 083public class FigureAction extends AbstractAction { 084 /** Construct an action that is attached to a figure on a named object. 085 * @param name The name of the object. 086 */ 087 public FigureAction(String name) { 088 super(name); 089 } 090 091 /////////////////////////////////////////////////////////////////// 092 //// public methods //// 093 094 /** Determine the target Ptolemy II object, the originating frame, 095 * and the X, Y position of the action, if possible. After this 096 * is invoked, the other public methods can be used to access 097 * this data. 098 * @param e The event. 099 */ 100 @Override 101 public void actionPerformed(ActionEvent e) { 102 Object source = e.getSource(); 103 Component parent = null; 104 105 if (source instanceof LayerEvent) { 106 _sourceType = CANVAS_TYPE; 107 108 // Action activated using an ActionInteractor. 109 LayerEvent event = (LayerEvent) source; 110 CanvasLayer layer = event.getLayerSource(); 111 GraphPane pane = (GraphPane) layer.getCanvasPane(); 112 GraphController controller = pane.getGraphController(); 113 GraphModel model = controller.getGraphModel(); 114 115 _figure = event.getFigureSource(); 116 117 // Set the target. 118 if (_figure == null) { 119 _target = (NamedObj) model.getRoot(); 120 } else { 121 Object object = _figure.getUserObject(); 122 _target = (NamedObj) model.getSemanticObject(object); 123 } 124 125 // Set the position. 126 _x = event.getX(); 127 _y = event.getY(); 128 129 // Set the parent. 130 CanvasPane canvasPane = layer.getCanvasPane(); 131 parent = canvasPane.getCanvas(); 132 } else if (source instanceof JMenuItem) { 133 // Action activated using a context menu or submenu. 134 JMenuItem item = (JMenuItem) source; 135 // Find the original context menu. 136 Component contextMenu = item.getParent(); 137 if (!(contextMenu instanceof JContextMenu)) { 138 // Probably a submenu. 139 // FIXME: This only supports one level of submenus. 140 if (contextMenu instanceof JPopupMenu) { 141 contextMenu = ((JPopupMenu) contextMenu).getInvoker(); 142 } 143 if (contextMenu instanceof JMenu) { 144 contextMenu = contextMenu.getParent(); 145 } 146 } 147 if (contextMenu instanceof JContextMenu) { 148 _sourceType = CONTEXTMENU_TYPE; 149 JContextMenu menu = (JContextMenu) contextMenu; 150 parent = menu.getInvoker(); 151 _target = (NamedObj) menu.getTarget(); 152 _x = item.getX(); 153 _y = item.getY(); 154 } else { 155 // Not implicit location.. should there be? 156 _sourceType = MENUBAR_TYPE; 157 } 158 /* 159 } else if (source instanceof JMenuItem) { 160 // Action activated using a context menu. 161 JMenuItem item = (JMenuItem) source; 162 163 if (item.getParent() instanceof JContextMenu) { 164 _sourceType = CONTEXTMENU_TYPE; 165 166 JContextMenu menu = (JContextMenu) item.getParent(); 167 parent = menu.getInvoker(); 168 _target = (NamedObj) menu.getTarget(); 169 _x = item.getX(); 170 _y = item.getY(); 171 } else { 172 // Not implicit location.. should there be? 173 _sourceType = MENUBAR_TYPE; 174 } 175 */ 176 } else if (source instanceof JButton) { 177 // presumably we are in a toolbar... 178 _sourceType = TOOLBAR_TYPE; 179 _target = null; 180 parent = ((Component) source).getParent(); 181 } else if (source instanceof JGraph) { 182 // This is an absurdly convoluted way to get the info we need. 183 // But there seems to be no other way. 184 // This is an architectural flaw in vergil. 185 GraphPane pane = ((JGraph) source).getGraphPane(); 186 FigureLayer layer = pane.getForegroundLayer(); 187 CanvasComponent currentFigure = layer.getCurrentFigure(); 188 GraphController controller = pane.getGraphController(); 189 GraphModel model = controller.getGraphModel(); 190 191 if (currentFigure != null) { 192 _target = null; 193 194 while (_target == null && currentFigure != null) { 195 Object object = currentFigure; 196 197 if (object instanceof Figure) { 198 object = ((Figure) currentFigure).getUserObject(); 199 } 200 201 _target = (NamedObj) model.getSemanticObject(object); 202 currentFigure = currentFigure.getParent(); 203 } 204 205 // NOTE: _target may end up null here! 206 if (_target == null) { 207 // On 5/29/09, Edward wrote: 208 // "If you select a transition in an FSM, put the 209 // mouse on the blue box that is the grab handle, 210 // and hit Command-L (to look inside), you get an 211 // ugly exception on the command line. 212 // If you put the mouse on the transition but not 213 // on the blue box, you correctly get a message 214 // that there is no refinement." 215 // 216 // So, if the current figure is a BasicGrabHandle, we 217 // do not throw the exception 218 if (!(layer 219 .getCurrentFigure() instanceof BasicGrabHandle)) { 220 throw new InternalErrorException( 221 "Internal error: FigureLayer \"" 222 + layer.getCurrentFigure() 223 + "\" has no associated Ptolemy II object!"); 224 } 225 } 226 } else { 227 _target = (NamedObj) model.getRoot(); 228 } 229 230 _sourceType = HOTKEY_TYPE; 231 232 // FIXME: set _x and _y. How to do this? 233 _x = 0; 234 _y = 0; 235 236 // Set the parent. 237 CanvasPane canvasPane = layer.getCanvasPane(); 238 parent = canvasPane.getCanvas(); 239 } else { 240 _sourceType = null; 241 _target = null; 242 parent = null; 243 _x = 0; 244 _y = 0; 245 } 246 247 if (parent != null) { 248 while (parent.getParent() != null) { 249 parent = parent.getParent(); 250 } 251 } 252 253 if (parent instanceof Frame) { 254 _frame = new WeakReference(parent); 255 } else { 256 _frame = null; 257 } 258 } 259 260 /** Return the figure of this action. 261 * @return The figure of this action. 262 */ 263 public Figure getFigure() { 264 return _figure; 265 } 266 267 // FIXME: The following methods should all be protected. 268 269 /** Return the frame responsible for triggering this action, 270 * or null if none could be found. This can be used to set the 271 * owner of any dialogs triggered by this event. This must 272 * be called after actionPerformed(), and is typically called 273 * inside the actionPerformed() method of a subclass. 274 * @return The frame that triggered this action. 275 */ 276 public Frame getFrame() { 277 return (Frame) _frame.get(); 278 } 279 280 /** Return the source type of this action, which is one of 281 * CANVAS_TYPE, CONTEXTMENU_TYPE, TOOLBAR_TYPE, MENUBAR_TYPE, 282 * HOTKEY_TYPE, or null if none was recognized. 283 * @return The source type of this action. 284 */ 285 public SourceType getSourceType() { 286 return _sourceType; 287 } 288 289 /** Return the target Ptolemy II object for this action, 290 * or null if none could be found. This is typically the object 291 * whose icon is the figure on which this action was invoked. 292 * This must be called after actionPerformed(), and is typically called 293 * inside the actionPerformed() method of a subclass. 294 * @return The object on which this action was invoked. 295 */ 296 public NamedObj getTarget() { 297 return _target; 298 } 299 300 /** Return the horizontal position of the action, or 0 if this 301 * is not relevant (e.g., the action was triggered by a toolbar button). 302 * This must be called after actionPerformed(), and is typically called 303 * inside the actionPerformed() method of a subclass. 304 * @return The x position of the action. 305 */ 306 public int getX() { 307 return _x; 308 } 309 310 /** Return the vertical position of the action, or 0 if this 311 * is not relevant (e.g., the action was triggered by a toolbar button). 312 * This must be called after actionPerformed(), and is typically called 313 * inside the actionPerformed() method of a subclass. 314 * @return The y position of the action. 315 */ 316 public int getY() { 317 return _y; 318 } 319 320 /** Determine a new location for a figure if another figure is 321 * already at that location. 322 * @param x The x value of the proposed location. 323 * @param y The y value of the proposed location. 324 * @param xOffset The x offset to be used if a figure is found. 325 * @param yOffset The y offset to be used if a figure is found. 326 * @param figureClass The Class of the figure to avoid. 327 * @param foregroundLayer The layer containing the figures. 328 * @param visibleRectangle The rectangle that describe the bounds 329 * of the visible pane. 330 * @return An array of two doubles (x and y) that represents either 331 * the original location or an offset location that does not obscure 332 * an object of class <i>figure</i>. 333 */ 334 static public double[] offsetFigure(double x, double y, double xOffset, 335 double yOffset, Class<?> figureClass, FigureLayer foregroundLayer, 336 Rectangle2D visibleRectangle) { 337 // Solve the problem of items from the toolbar overlapping. 338 // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3002 339 340 // This method is in this class so that we can handle 341 // ports and relations. 342 343 double[] point = new double[2]; 344 point[0] = x; 345 point[1] = y; 346 347 double originalX = x; 348 double originalY = y; 349 350 // See EditorDropTarget for similar code. 351 352 double halo = foregroundLayer.getPickHalo(); 353 double width = halo * 2; 354 355 // Used to handle cases where we get to the edge. 356 int xMax = 0; 357 int yMax = 0; 358 359 // Set to true if we need to check for a Figure at x and y 360 boolean checkFigure = false; 361 do { 362 // If we are looping again, we set checkFigure to false 363 // until we later possibly find a Figure. 364 checkFigure = false; 365 366 // The rectangle in which we search for a Figure. 367 Rectangle2D region = new Rectangle2D.Double(point[0] - halo, 368 point[1] - halo, width, width); 369 370 // Iterate through figures within the region. 371 Iterator<?> foregroundFigures = foregroundLayer.getFigures() 372 .getIntersectedFigures(region).figuresFromFront(); 373 Iterator<?> pickFigures = CanvasUtilities 374 .pickIter(foregroundFigures, region); 375 376 while (pickFigures.hasNext() && !checkFigure) { 377 CanvasComponent possibleFigure = (CanvasComponent) pickFigures 378 .next(); 379 if (possibleFigure == null) { 380 // Nothing to see here, move along - there is no Figure. 381 break; 382 } else if (possibleFigure instanceof UserObjectContainer) { 383 // Work our way up the CanvasComponent parent tree 384 // See EditorDropTarget for similar code. 385 Object userObject = null; 386 387 while (possibleFigure instanceof UserObjectContainer 388 && userObject == null && !checkFigure) { 389 userObject = ((UserObjectContainer) possibleFigure) 390 .getUserObject(); 391 if (userObject instanceof Location && (figureClass 392 .isInstance(userObject) 393 || figureClass.isInstance(possibleFigure))) { 394 // We found a figure here, so we will 395 // loop again. 396 checkFigure = true; 397 point[0] += xOffset; 398 point[1] += yOffset; 399 400 // Check to make sure we are not outside the view 401 if (point[0] > visibleRectangle.getWidth()) { 402 point[0] = originalX; 403 point[1] = originalY 404 - PASTE_OFFSET * 2 * ++xMax; 405 if (point[1] < 0) { 406 point[1] = originalY 407 + PASTE_OFFSET * 2 * ++xMax; 408 } 409 } 410 411 if (point[1] > visibleRectangle.getHeight()) { 412 point[0] = originalX 413 - PASTE_OFFSET * 2 * ++yMax; 414 if (point[0] < 0) { 415 point[0] = originalX 416 + PASTE_OFFSET * 2 * ++xMax; 417 } 418 point[1] = originalY; 419 420 } 421 422 // Fail safe. Don't try forever, just give up. 423 if (point[0] < 0 || point[1] < 0 424 || point[0] > visibleRectangle.getWidth() 425 || point[1] > visibleRectangle 426 .getHeight()) { 427 // Can't do anything here, so return. 428 point[0] = originalX + 0.5 * xOffset; 429 point[1] = originalY + 0.5 * yOffset; 430 return point; 431 } 432 } 433 possibleFigure = possibleFigure.getParent(); 434 } 435 } 436 } 437 } while (checkFigure); 438 return point; 439 } 440 441 /////////////////////////////////////////////////////////////////// 442 //// public variables //// 443 444 /** When the action was fired from a canvas interactor. 445 */ 446 public static final SourceType CANVAS_TYPE = new SourceType("canvas"); 447 448 /** When the action was fired from a context menu. 449 */ 450 public static final SourceType CONTEXTMENU_TYPE = new SourceType( 451 "contextmenu"); 452 453 /** When the action was fired from a hotkey. 454 */ 455 public static final SourceType HOTKEY_TYPE = new SourceType("hotkey"); 456 457 /** When the action was fired from a menubar. 458 */ 459 public static final SourceType MENUBAR_TYPE = new SourceType("menubar"); 460 461 /** Offset used when pasting objects. See also OffsetMoMLChangeRequest. */ 462 public static final int PASTE_OFFSET = 20; 463 464 /** When the action was fired from a toolbar icon. 465 */ 466 public static final SourceType TOOLBAR_TYPE = new SourceType("toolbar"); 467 468 /////////////////////////////////////////////////////////////////// 469 //// protected methods //// 470 471 /////////////////////////////////////////////////////////////////// 472 //// protected variables //// 473 474 /////////////////////////////////////////////////////////////////// 475 //// inner classes //// 476 477 /** The source of the action. */ 478 public static class SourceType { 479 /** Construct a SourceType. 480 * @param name The name of the SourceType. 481 */ 482 private SourceType(String name) { 483 _name = name; 484 } 485 486 /** Get the name of the SourceType. 487 * @return the name. 488 */ 489 public String getName() { 490 return _name; 491 } 492 493 private String _name; 494 } 495 496 /////////////////////////////////////////////////////////////////// 497 //// private variables //// 498 private Figure _figure = null; 499 500 private WeakReference _frame = null; 501 502 private SourceType _sourceType = null; 503 504 private NamedObj _target = null; 505 506 private int _x = 0; 507 508 private int _y = 0; 509}