Newer
Older
piccolo2d.java / extras / src / main / java / edu / umd / cs / piccolox / handles / PBoundsHandle.java
/*
 * Copyright (c) 2008, 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 transient PBasicInputEventHandler handleCursorHandler;

    public static void addBoundsHandlesTo(PNode aNode) {
        aNode.addChild(new PBoundsHandle(PBoundsLocator.createEastLocator(aNode)));
        aNode.addChild(new PBoundsHandle(PBoundsLocator.createWestLocator(aNode)));
        aNode.addChild(new PBoundsHandle(PBoundsLocator.createNorthLocator(aNode)));
        aNode.addChild(new PBoundsHandle(PBoundsLocator.createSouthLocator(aNode)));
        aNode.addChild(new PBoundsHandle(PBoundsLocator.createNorthEastLocator(aNode)));
        aNode.addChild(new PBoundsHandle(PBoundsLocator.createNorthWestLocator(aNode)));
        aNode.addChild(new PBoundsHandle(PBoundsLocator.createSouthEastLocator(aNode)));
        aNode.addChild(new PBoundsHandle(PBoundsLocator.createSouthWestLocator(aNode)));
    }

    public static void addStickyBoundsHandlesTo(PNode aNode, PCamera camera) {
        camera.addChild(new PBoundsHandle(PBoundsLocator.createEastLocator(aNode)));
        camera.addChild(new PBoundsHandle(PBoundsLocator.createWestLocator(aNode)));
        camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthLocator(aNode)));
        camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthLocator(aNode)));
        camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthEastLocator(aNode)));
        camera.addChild(new PBoundsHandle(PBoundsLocator.createNorthWestLocator(aNode)));
        camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthEastLocator(aNode)));
        camera.addChild(new PBoundsHandle(PBoundsLocator.createSouthWestLocator(aNode)));
    }

    public static void removeBoundsHandlesFrom(PNode aNode) {
        ArrayList handles = new ArrayList();

        Iterator i = aNode.getChildrenIterator();
        while (i.hasNext()) {
            PNode each = (PNode) i.next();
            if (each instanceof PBoundsHandle) {
                handles.add(each);
            }
        }
        aNode.removeChildren(handles);
    }

    public PBoundsHandle(PBoundsLocator aLocator) {
        super(aLocator);
    }

    protected void installHandleEventHandlers() {
        super.installHandleEventHandlers();
        handleCursorHandler = new PBasicInputEventHandler() {
            boolean cursorPushed = false;

            public void mouseEntered(PInputEvent aEvent) {
                if (!cursorPushed) {
                    aEvent.pushCursor(getCursorFor(((PBoundsLocator) getLocator()).getSide()));
                    cursorPushed = true;
                }
            }

            public void mouseExited(PInputEvent aEvent) {
                PPickPath focus = aEvent.getInputManager().getMouseFocus();
                if (cursorPushed) {
                    if (focus == null || focus.getPickedNode() != PBoundsHandle.this) {
                        aEvent.popCursor();
                        cursorPushed = false;
                    }
                }
            }

            public void mouseReleased(PInputEvent event) {
                if (cursorPushed) {
                    event.popCursor();
                    cursorPushed = false;
                }
            }
        };
        addInputEventListener(handleCursorHandler);
    }

    /**
     * Return the event handler that is responsible for setting the mouse cursor
     * when it enters/exits this handle.
     */
    public PBasicInputEventHandler getHandleCursorEventHandler() {
        return handleCursorHandler;
    }

    public void startHandleDrag(Point2D aLocalPoint, PInputEvent aEvent) {
        PBoundsLocator l = (PBoundsLocator) getLocator();
        l.getNode().startResizeBounds();
    }

    public void dragHandle(PDimension aLocalDimension, PInputEvent aEvent) {
        PBoundsLocator l = (PBoundsLocator) getLocator();

        PNode n = l.getNode();
        PBounds b = n.getBounds();

        PNode parent = getParent();
        if (parent != n && parent instanceof PCamera) {
            ((PCamera) parent).localToView(aLocalDimension);
        }

        localToGlobal(aLocalDimension);
        n.globalToLocal(aLocalDimension);

        double dx = aLocalDimension.getWidth();
        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;
        }

        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);
    }

    public void endHandleDrag(Point2D aLocalPoint, PInputEvent aEvent) {
        PBoundsLocator l = (PBoundsLocator) getLocator();
        l.getNode().endResizeBounds();
    }

    public void flipSiblingBoundsHandles(boolean flipX, boolean flipY) {
        Iterator i = getParent().getChildrenIterator();
        while (i.hasNext()) {
            Object each = i.next();
            if (each instanceof PBoundsHandle) {
                ((PBoundsHandle) each).flipHandleIfNeeded(flipX, flipY);
            }
        }
    }

    public void flipHandleIfNeeded(boolean flipX, boolean flipY) {
        PBoundsLocator l = (PBoundsLocator) getLocator();

        if (flipX || flipY) {
            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;
                }
            }
        }

        // reset locator to update layout
        setLocator(l);
    }

    public Cursor getCursorFor(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);
        }
        return null;
    }
}