package org.myrobotlab.control.widget; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.Map; import javax.swing.JComponent; import javax.swing.SwingUtilities; /** * The ComponentResizer allows you to resize a component by dragging a border of * the component. */ public class ComponentResizer extends MouseAdapter { private final static Dimension MINIMUM_SIZE = new Dimension(10, 10); private final static Dimension MAXIMUM_SIZE = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); private static Map<Integer, Integer> cursors = new HashMap<Integer, Integer>(); { cursors.put(1, Cursor.N_RESIZE_CURSOR); cursors.put(2, Cursor.W_RESIZE_CURSOR); cursors.put(4, Cursor.S_RESIZE_CURSOR); cursors.put(8, Cursor.E_RESIZE_CURSOR); cursors.put(3, Cursor.NW_RESIZE_CURSOR); cursors.put(9, Cursor.NE_RESIZE_CURSOR); cursors.put(6, Cursor.SW_RESIZE_CURSOR); cursors.put(12, Cursor.SE_RESIZE_CURSOR); } private Insets dragInsets; private Dimension snapSize; private int direction; protected static final int NORTH = 1; protected static final int WEST = 2; protected static final int SOUTH = 4; protected static final int EAST = 8; private Cursor sourceCursor; private boolean resizing; private Rectangle bounds; private Point pressed; private boolean autoscrolls; private Dimension minimumSize = MINIMUM_SIZE; private Dimension maximumSize = MAXIMUM_SIZE; /** * Convenience contructor. All borders are resizable in increments of a single * pixel. Components must be registered separately. */ public ComponentResizer() { this(new Insets(5, 5, 5, 5), new Dimension(1, 1)); } /** * Convenience contructor. All borders are resizable in increments of a single * pixel. Components can be registered when the class is created or they can * be registered separately afterwards. * * @param components * components to be automatically registered */ public ComponentResizer(Component... components) { this(new Insets(5, 5, 5, 5), new Dimension(1, 1), components); } /** * Convenience contructor. Eligible borders are resisable in increments of a * single pixel. Components can be registered when the class is created or * they can be registered separately afterwards. * * @param dragInsets * Insets specifying which borders are eligible to be resized. * @param components * components to be automatically registered */ public ComponentResizer(Insets dragInsets, Component... components) { this(dragInsets, new Dimension(1, 1), components); } /** * Create a ComponentResizer. * * @param dragInsets * Insets specifying which borders are eligible to be resized. * @param snapSize * Specify the dimension to which the border will snap to when being * dragged. Snapping occurs at the halfway mark. * @param components * components to be automatically registered */ public ComponentResizer(Insets dragInsets, Dimension snapSize, Component... components) { setDragInsets(dragInsets); setSnapSize(snapSize); registerComponent(components); } protected void changeBounds(Component source, int direction, Rectangle bounds, Point pressed, Point current) { // Start with original locaton and size int x = bounds.x; int y = bounds.y; int width = bounds.width; int height = bounds.height; // Resizing the West or North border affects the size and location if (WEST == (direction & WEST)) { int drag = getDragDistance(pressed.x, current.x, snapSize.width); int maximum = Math.min(width + x, maximumSize.width); drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum); x -= drag; width += drag; } if (NORTH == (direction & NORTH)) { int drag = getDragDistance(pressed.y, current.y, snapSize.height); int maximum = Math.min(height + y, maximumSize.height); drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum); y -= drag; height += drag; } // Resizing the East or South border only affects the size if (EAST == (direction & EAST)) { int drag = getDragDistance(current.x, pressed.x, snapSize.width); Dimension boundingSize = getBoundingSize(source); int maximum = Math.min(boundingSize.width - x, maximumSize.width); drag = getDragBounded(drag, snapSize.width, width, minimumSize.width, maximum); width += drag; } if (SOUTH == (direction & SOUTH)) { int drag = getDragDistance(current.y, pressed.y, snapSize.height); Dimension boundingSize = getBoundingSize(source); int maximum = Math.min(boundingSize.height - y, maximumSize.height); drag = getDragBounded(drag, snapSize.height, height, minimumSize.height, maximum); height += drag; } source.setBounds(x, y, width, height); source.validate(); } /** * Remove listeners from the specified component * * @param component * the component the listeners are removed from */ public void deregisterComponent(Component... components) { for (Component component : components) { component.removeMouseListener(this); component.removeMouseMotionListener(this); } } /* * Keep the size of the component within the bounds of its parent. */ private Dimension getBoundingSize(Component source) { if (source instanceof Window) { GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); Rectangle bounds = env.getMaximumWindowBounds(); return new Dimension(bounds.width, bounds.height); } else { return source.getParent().getSize(); } } /* * Adjust the drag value to be within the minimum and maximum range. */ private int getDragBounded(int drag, int snapSize, int dimension, int minimum, int maximum) { while (dimension + drag < minimum) drag += snapSize; while (dimension + drag > maximum) drag -= snapSize; return drag; } /* * Determine how far the mouse has moved from where dragging started */ private int getDragDistance(int larger, int smaller, int snapSize) { int halfway = snapSize / 2; int drag = larger - smaller; drag += (drag < 0) ? -halfway : halfway; drag = (drag / snapSize) * snapSize; return drag; } /** * Get the drag insets * * @return the drag insets */ public Insets getDragInsets() { return dragInsets; } /** * Get the components maximum size. * * @return the maximum size */ public Dimension getMaximumSize() { return maximumSize; } /** * Get the components minimum size. * * @return the minimum size */ public Dimension getMinimumSize() { return minimumSize; } /** * Get the snap size. * * @return the snap size. */ public Dimension getSnapSize() { return snapSize; } /** * Resize the component ensuring location and size is within the bounds of the * parent container and that the size is within the minimum and maximum * constraints. * * All calculations are done using the bounds of the component when the * resizing started. */ @Override public void mouseDragged(MouseEvent e) { if (resizing == false) return; Component source = e.getComponent(); Point dragged = e.getPoint(); SwingUtilities.convertPointToScreen(dragged, source); changeBounds(source, direction, bounds, pressed, dragged); } @Override public void mouseEntered(MouseEvent e) { if (!resizing) { Component source = e.getComponent(); sourceCursor = source.getCursor(); } } @Override public void mouseExited(MouseEvent e) { if (!resizing) { Component source = e.getComponent(); source.setCursor(sourceCursor); } } /** */ @Override public void mouseMoved(MouseEvent e) { Component source = e.getComponent(); Point location = e.getPoint(); direction = 0; if (location.x < dragInsets.left) direction += WEST; if (location.x > source.getWidth() - dragInsets.right - 1) direction += EAST; if (location.y < dragInsets.top) direction += NORTH; if (location.y > source.getHeight() - dragInsets.bottom - 1) direction += SOUTH; // Mouse is no longer over a resizable border if (direction == 0) { source.setCursor(sourceCursor); } else // use the appropriate resizable cursor { int cursorType = cursors.get(direction); Cursor cursor = Cursor.getPredefinedCursor(cursorType); source.setCursor(cursor); } } @Override public void mousePressed(MouseEvent e) { // The mouseMoved event continually updates this variable if (direction == 0) return; // Setup for resizing. All future dragging calculations are done based // on the original bounds of the component and mouse pressed location. resizing = true; Component source = e.getComponent(); pressed = e.getPoint(); SwingUtilities.convertPointToScreen(pressed, source); bounds = source.getBounds(); // Making sure autoscrolls is false will allow for smoother resizing // of components if (source instanceof JComponent) { JComponent jc = (JComponent) source; autoscrolls = jc.getAutoscrolls(); jc.setAutoscrolls(false); } } /** * Restore the original state of the Component */ @Override public void mouseReleased(MouseEvent e) { resizing = false; Component source = e.getComponent(); source.setCursor(sourceCursor); if (source instanceof JComponent) { ((JComponent) source).setAutoscrolls(autoscrolls); } } /** * Add the required listeners to the specified component * * @param component * the component the listeners are added to */ public void registerComponent(Component... components) { for (Component component : components) { component.addMouseListener(this); component.addMouseMotionListener(this); } } /** * Set the drag dragInsets. The insets specify an area where mouseDragged * events are recognized from the edge of the border inwards. A value of 0 for * any size will imply that the border is not resizable. Otherwise the * appropriate drag cursor will appear when the mouse is inside the resizable * border area. * * @param dragInsets * Insets to control which borders are resizeable. */ public void setDragInsets(Insets dragInsets) { validateMinimumAndInsets(minimumSize, dragInsets); this.dragInsets = dragInsets; } /** * Specify the maximum size for the component. The component will still be * constrained by the size of its parent. * * @param maximumSize * the maximum size for a component. */ public void setMaximumSize(Dimension maximumSize) { this.maximumSize = maximumSize; } /** * Specify the minimum size for the component. The minimum size is constrained * by the drag insets. * * @param minimumSize * the minimum size for a component. */ public void setMinimumSize(Dimension minimumSize) { validateMinimumAndInsets(minimumSize, dragInsets); this.minimumSize = minimumSize; } /** * Control how many pixels a border must be dragged before the size of the * component is changed. The border will snap to the size once dragging has * passed the halfway mark. * * @param snapSize * Dimension object allows you to separately spcify a horizontal and * vertical snap size. */ public void setSnapSize(Dimension snapSize) { this.snapSize = snapSize; } /** * When the components minimum size is less than the drag insets then we can't * determine which border should be resized so we need to prevent this from * happening. */ private void validateMinimumAndInsets(Dimension minimum, Insets drag) { int minimumWidth = drag.left + drag.right; int minimumHeight = drag.top + drag.bottom; if (minimum.width < minimumWidth || minimum.height < minimumHeight) { String message = "Minimum size cannot be less than drag insets"; throw new IllegalArgumentException(message); } } }