/* * Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org * Copyright (c) 1998-2008, University of Maryland * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions * and the following disclaimer in the documentation and/or other materials provided with the * distribution. * * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its * contributors may be used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package edu.umd.cs.piccolox.handles; import java.awt.Cursor; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Iterator; import javax.swing.SwingConstants; import edu.umd.cs.piccolo.PCamera; import edu.umd.cs.piccolo.PNode; import edu.umd.cs.piccolo.event.PBasicInputEventHandler; import edu.umd.cs.piccolo.event.PInputEvent; import edu.umd.cs.piccolo.util.PBounds; import edu.umd.cs.piccolo.util.PDimension; import edu.umd.cs.piccolo.util.PPickPath; import edu.umd.cs.piccolox.util.PBoundsLocator; /** * <b>PBoundsHandle</b> a handle for resizing the bounds of another node. If a * bounds handle is dragged such that the other node's width or height becomes * negative then the each drag handle's locator assciated with that other node * is "flipped" so that they are attached to and dragging a different corner of * the nodes bounds. * * @version 1.0 * @author Jesse Grosjean */ public class PBoundsHandle extends PHandle { private static final long serialVersionUID = 1L; /** * Event handler responsible for changing the mouse when it enters the * handle. */ private transient PBasicInputEventHandler handleCursorHandler; /** * Adds bounds handles to the corners and edges of the provided node. * * @param node node to be extended with bounds handles */ public static void addBoundsHandlesTo(final PNode node) { node.addChild(new PBoundsHandle(PBoundsLocator.createEastLocator(node))); node.addChild(new PBoundsHandle(PBoundsLocator.createWestLocator(node))); node.addChild(new PBoundsHandle(PBoundsLocator.createNorthLocator(node))); node.addChild(new PBoundsHandle(PBoundsLocator.createSouthLocator(node))); node.addChild(new PBoundsHandle(PBoundsLocator.createNorthEastLocator(node))); node.addChild(new PBoundsHandle(PBoundsLocator.createNorthWestLocator(node))); node.addChild(new PBoundsHandle(PBoundsLocator.createSouthEastLocator(node))); node.addChild(new PBoundsHandle(PBoundsLocator.createSouthWestLocator(node))); } /** * Adds stick handles (always visible regardless of scale since they are * attached to the camera) to the node provided. * * @param node node being extended with bounds handles * @param camera camera onto which handles will appear */ public static void addStickyBoundsHandlesTo(final PNode node, final PCamera camera) { camera.addChild(new PBoundsHandle(PBoundsLocator.createEastLocator(node))); camera.addChild(new PBoundsHandle(PBoundsLocator.createWestLocator(node))); camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthLocator(node))); camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthLocator(node))); camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthEastLocator(node))); camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthWestLocator(node))); camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthEastLocator(node))); camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthWestLocator(node))); } /** * Removes all bounds from the node provided. * * @param node node having its handles removed from */ public static void removeBoundsHandlesFrom(final PNode node) { final ArrayList handles = new ArrayList(); final Iterator i = node.getChildrenIterator(); while (i.hasNext()) { final PNode each = (PNode) i.next(); if (each instanceof PBoundsHandle) { handles.add(each); } } node.removeChildren(handles); } /** * Creates a bounds handle that will be attached to the provided locator. * * @param locator locator used to position the node */ public PBoundsHandle(final PBoundsLocator locator) { super(locator); } /** * Installs the handlers to this particular bounds handle. */ protected void installHandleEventHandlers() { super.installHandleEventHandlers(); handleCursorHandler = new MouseCursorUpdateHandler(); addInputEventListener(handleCursorHandler); } /** * Return the event handler that is responsible for setting the mouse cursor * when it enters/exits this handle. * * @return current handler responsible for changing the mouse cursor */ public PBasicInputEventHandler getHandleCursorEventHandler() { return handleCursorHandler; } /** * Is invoked when the a drag starts on this handle. * * @param aLocalPoint point in the handle's coordinate system that is * pressed * @param aEvent event representing the start of the drag */ public void startHandleDrag(final Point2D aLocalPoint, final PInputEvent aEvent) { final PBoundsLocator l = (PBoundsLocator) getLocator(); l.getNode().startResizeBounds(); } /** * Is invoked when the handle is being dragged. * * @param aLocalDimension dimension representing the magnitude of the handle * drag * @param aEvent event responsible for the call */ public void dragHandle(final PDimension aLocalDimension, final PInputEvent aEvent) { final PBoundsLocator l = (PBoundsLocator) getLocator(); final PNode n = l.getNode(); final PBounds b = n.getBounds(); final PNode parent = getParent(); if (parent != n && parent instanceof PCamera) { ((PCamera) parent).localToView(aLocalDimension); } localToGlobal(aLocalDimension); n.globalToLocal(aLocalDimension); final double dx = aLocalDimension.getWidth(); final double dy = aLocalDimension.getHeight(); switch (l.getSide()) { case SwingConstants.NORTH: b.setRect(b.x, b.y + dy, b.width, b.height - dy); break; case SwingConstants.SOUTH: b.setRect(b.x, b.y, b.width, b.height + dy); break; case SwingConstants.EAST: b.setRect(b.x, b.y, b.width + dx, b.height); break; case SwingConstants.WEST: b.setRect(b.x + dx, b.y, b.width - dx, b.height); break; case SwingConstants.NORTH_WEST: b.setRect(b.x + dx, b.y + dy, b.width - dx, b.height - dy); break; case SwingConstants.SOUTH_WEST: b.setRect(b.x + dx, b.y, b.width - dx, b.height + dy); break; case SwingConstants.NORTH_EAST: b.setRect(b.x, b.y + dy, b.width + dx, b.height - dy); break; case SwingConstants.SOUTH_EAST: b.setRect(b.x, b.y, b.width + dx, b.height + dy); break; default: throw new RuntimeException("Invalid side returned from PBoundsLocator"); } boolean flipX = false; boolean flipY = false; if (b.width < 0) { flipX = true; b.width = -b.width; b.x -= b.width; } if (b.height < 0) { flipY = true; b.height = -b.height; b.y -= b.height; } if (flipX || flipY) { flipSiblingBoundsHandles(flipX, flipY); } n.setBounds(b); } /** * Call back invoked when the drag is finished. * * @param aLocalPoint point on the handle where the drag was ended * @param aEvent event responsible for the end of the drag */ public void endHandleDrag(final Point2D aLocalPoint, final PInputEvent aEvent) { final PBoundsLocator l = (PBoundsLocator) getLocator(); l.getNode().endResizeBounds(); } /** * Moves locators around so that they are still logically positioned. * * This is needed when a node is resized until its width or height is * negative. * * @param flipX whether to allow flipping along the x direction * @param flipY whether to allow flipping along the y direction */ public void flipSiblingBoundsHandles(final boolean flipX, final boolean flipY) { final Iterator i = getParent().getChildrenIterator(); while (i.hasNext()) { final Object each = i.next(); if (each instanceof PBoundsHandle) { ((PBoundsHandle) each).flipHandleIfNeeded(flipX, flipY); } } } /** * Flips this bounds around if it needs to be. This is required when a node * is resized until either its height or width is negative. * * @param flipX whether to allow flipping along the x direction * @param flipY whether to allow flipping along the y direction */ public void flipHandleIfNeeded(final boolean flipX, final boolean flipY) { final PBoundsLocator l = (PBoundsLocator) getLocator(); if (!flipX && !flipY) { return; } switch (l.getSide()) { case SwingConstants.NORTH: if (flipY) { l.setSide(SwingConstants.SOUTH); } break; case SwingConstants.SOUTH: if (flipY) { l.setSide(SwingConstants.NORTH); } break; case SwingConstants.EAST: if (flipX) { l.setSide(SwingConstants.WEST); } break; case SwingConstants.WEST: if (flipX) { l.setSide(SwingConstants.EAST); } break; case SwingConstants.NORTH_WEST: if (flipX && flipY) { l.setSide(SwingConstants.SOUTH_EAST); } else if (flipX) { l.setSide(SwingConstants.NORTH_EAST); } else if (flipY) { l.setSide(SwingConstants.SOUTH_WEST); } break; case SwingConstants.SOUTH_WEST: if (flipX && flipY) { l.setSide(SwingConstants.NORTH_EAST); } else if (flipX) { l.setSide(SwingConstants.SOUTH_EAST); } else if (flipY) { l.setSide(SwingConstants.NORTH_WEST); } break; case SwingConstants.NORTH_EAST: if (flipX && flipY) { l.setSide(SwingConstants.SOUTH_WEST); } else if (flipX) { l.setSide(SwingConstants.NORTH_WEST); } else if (flipY) { l.setSide(SwingConstants.SOUTH_EAST); } break; case SwingConstants.SOUTH_EAST: if (flipX && flipY) { l.setSide(SwingConstants.NORTH_WEST); } else if (flipX) { l.setSide(SwingConstants.SOUTH_WEST); } else if (flipY) { l.setSide(SwingConstants.NORTH_EAST); } break; default: throw new RuntimeException("Invalid side received from PBoundsLocator"); } // reset locator to update layout setLocator(l); } /** * Returns an appropriate handle for the given side of a node. * * @param side side given as SwingConstants values. * * @return Appropriate cursor, or null if none can be identified. */ public Cursor getCursorFor(final int side) { switch (side) { case SwingConstants.NORTH: return new Cursor(Cursor.N_RESIZE_CURSOR); case SwingConstants.SOUTH: return new Cursor(Cursor.S_RESIZE_CURSOR); case SwingConstants.EAST: return new Cursor(Cursor.E_RESIZE_CURSOR); case SwingConstants.WEST: return new Cursor(Cursor.W_RESIZE_CURSOR); case SwingConstants.NORTH_WEST: return new Cursor(Cursor.NW_RESIZE_CURSOR); case SwingConstants.SOUTH_WEST: return new Cursor(Cursor.SW_RESIZE_CURSOR); case SwingConstants.NORTH_EAST: return new Cursor(Cursor.NE_RESIZE_CURSOR); case SwingConstants.SOUTH_EAST: return new Cursor(Cursor.SE_RESIZE_CURSOR); default: return null; } } private class MouseCursorUpdateHandler extends PBasicInputEventHandler { boolean cursorPushed; public MouseCursorUpdateHandler() { cursorPushed = false; } /** * When mouse is entered, push appropriate mouse cursor on cursor stack. * * @param aEvent the mouse entered event */ public void mouseEntered(final PInputEvent aEvent) { if (!cursorPushed) { aEvent.pushCursor(getCursorFor(((PBoundsLocator) getLocator()).getSide())); cursorPushed = true; } } /** * When mouse leaves, pop cursor from stack. * * @param aEvent the mouse exited event */ public void mouseExited(final PInputEvent aEvent) { if (cursorPushed) { final PPickPath focus = aEvent.getInputManager().getMouseFocus(); if (focus == null || focus.getPickedNode() != PBoundsHandle.this) { aEvent.popCursor(); cursorPushed = false; } } } /** * If mouse is released, cursor should pop as well. * * @param event the mouse released event */ public void mouseReleased(final PInputEvent event) { if (cursorPushed) { event.popCursor(); cursorPushed = false; } } } }