1 package com.hammurapi.jcapture;
2 
3 import java.awt.Component;
4 import java.awt.Cursor;
5 import java.awt.Dimension;
6 import java.awt.GraphicsEnvironment;
7 import java.awt.Insets;
8 import java.awt.Point;
9 import java.awt.Rectangle;
10 import java.awt.Window;
11 import java.awt.event.MouseAdapter;
12 import java.awt.event.MouseEvent;
13 import java.util.HashMap;
14 import java.util.Map;
15 
16 import javax.swing.JComponent;
17 import javax.swing.SwingUtilities;
18 
19 /**
20  * The ComponentResizer allows you to resize a component by dragging a border of
21  * the component.
22  *
23  * Taken from http://tips4java.wordpress.com/2009/09/13/resizing-components/
24  */
25 public class ComponentResizer extends MouseAdapter {
26 	private final static Dimension MINIMUM_SIZE = new Dimension(10, 10);
27 	private final static Dimension MAXIMUM_SIZE = new Dimension(
28 			Integer.MAX_VALUE, Integer.MAX_VALUE);
29 
30 	private static Map<Integer, Integer> cursors = new HashMap<Integer, Integer>();
31 	{
32 		cursors.put(1, Cursor.N_RESIZE_CURSOR);
33 		cursors.put(2, Cursor.W_RESIZE_CURSOR);
34 		cursors.put(4, Cursor.S_RESIZE_CURSOR);
35 		cursors.put(8, Cursor.E_RESIZE_CURSOR);
36 		cursors.put(3, Cursor.NW_RESIZE_CURSOR);
37 		cursors.put(9, Cursor.NE_RESIZE_CURSOR);
38 		cursors.put(6, Cursor.SW_RESIZE_CURSOR);
39 		cursors.put(12, Cursor.SE_RESIZE_CURSOR);
40 	}
41 
42 	private Insets dragInsets;
43 	private Dimension snapSize;
44 
45 	private int direction;
46 	protected static final int NORTH = 1;
47 	protected static final int WEST = 2;
48 	protected static final int SOUTH = 4;
49 	protected static final int EAST = 8;
50 
51 	private Cursor sourceCursor;
52 	private boolean resizing;
53 	private Rectangle bounds;
54 	private Point pressed;
55 	private boolean autoscrolls;
56 
57 	private Dimension minimumSize = MINIMUM_SIZE;
58 	private Dimension maximumSize = MAXIMUM_SIZE;
59 
60 	/**
61 	 * Convenience contructor. All borders are resizable in increments of a
62 	 * single pixel. Components must be registered separately.
63 	 */
ComponentResizer()64 	public ComponentResizer() {
65 		this(new Insets(5, 5, 5, 5), new Dimension(1, 1));
66 	}
67 
68 	/**
69 	 * Convenience contructor. All borders are resizable in increments of a
70 	 * single pixel. Components can be registered when the class is created or
71 	 * they can be registered separately afterwards.
72 	 *
73 	 * @param components
74 	 *            components to be automatically registered
75 	 */
ComponentResizer(Component... components)76 	public ComponentResizer(Component... components) {
77 		this(new Insets(5, 5, 5, 5), new Dimension(1, 1), components);
78 	}
79 
80 	/**
81 	 * Convenience contructor. Eligible borders are resisable in increments of a
82 	 * single pixel. Components can be registered when the class is created or
83 	 * they can be registered separately afterwards.
84 	 *
85 	 * @param dragInsets
86 	 *            Insets specifying which borders are eligible to be resized.
87 	 * @param components
88 	 *            components to be automatically registered
89 	 */
ComponentResizer(Insets dragInsets, Component... components)90 	public ComponentResizer(Insets dragInsets, Component... components) {
91 		this(dragInsets, new Dimension(1, 1), components);
92 	}
93 
94 	/**
95 	 * Create a ComponentResizer.
96 	 *
97 	 * @param dragInsets
98 	 *            Insets specifying which borders are eligible to be resized.
99 	 * @param snapSize
100 	 *            Specify the dimension to which the border will snap to when
101 	 *            being dragged. Snapping occurs at the halfway mark.
102 	 * @param components
103 	 *            components to be automatically registered
104 	 */
ComponentResizer(Insets dragInsets, Dimension snapSize, Component... components)105 	public ComponentResizer(Insets dragInsets, Dimension snapSize,
106 			Component... components) {
107 		setDragInsets(dragInsets);
108 		setSnapSize(snapSize);
109 		registerComponent(components);
110 	}
111 
112 	/**
113 	 * Get the drag insets
114 	 *
115 	 * @return the drag insets
116 	 */
getDragInsets()117 	public Insets getDragInsets() {
118 		return dragInsets;
119 	}
120 
121 	/**
122 	 * Set the drag dragInsets. The insets specify an area where mouseDragged
123 	 * events are recognized from the edge of the border inwards. A value of 0
124 	 * for any size will imply that the border is not resizable. Otherwise the
125 	 * appropriate drag cursor will appear when the mouse is inside the
126 	 * resizable border area.
127 	 *
128 	 * @param dragInsets
129 	 *            Insets to control which borders are resizeable.
130 	 */
setDragInsets(Insets dragInsets)131 	public void setDragInsets(Insets dragInsets) {
132 		validateMinimumAndInsets(minimumSize, dragInsets);
133 
134 		this.dragInsets = dragInsets;
135 	}
136 
137 	/**
138 	 * Get the components maximum size.
139 	 *
140 	 * @return the maximum size
141 	 */
getMaximumSize()142 	public Dimension getMaximumSize() {
143 		return maximumSize;
144 	}
145 
146 	/**
147 	 * Specify the maximum size for the component. The component will still be
148 	 * constrained by the size of its parent.
149 	 *
150 	 * @param maximumSize
151 	 *            the maximum size for a component.
152 	 */
setMaximumSize(Dimension maximumSize)153 	public void setMaximumSize(Dimension maximumSize) {
154 		this.maximumSize = maximumSize;
155 	}
156 
157 	/**
158 	 * Get the components minimum size.
159 	 *
160 	 * @return the minimum size
161 	 */
getMinimumSize()162 	public Dimension getMinimumSize() {
163 		return minimumSize;
164 	}
165 
166 	/**
167 	 * Specify the minimum size for the component. The minimum size is
168 	 * constrained by the drag insets.
169 	 *
170 	 * @param minimumSize
171 	 *            the minimum size for a component.
172 	 */
setMinimumSize(Dimension minimumSize)173 	public void setMinimumSize(Dimension minimumSize) {
174 		validateMinimumAndInsets(minimumSize, dragInsets);
175 
176 		this.minimumSize = minimumSize;
177 	}
178 
179 	/**
180 	 * Remove listeners from the specified component
181 	 *
182 	 * @param component
183 	 *            the component the listeners are removed from
184 	 */
deregisterComponent(Component... components)185 	public void deregisterComponent(Component... components) {
186 		for (Component component : components) {
187 			component.removeMouseListener(this);
188 			component.removeMouseMotionListener(this);
189 		}
190 	}
191 
192 	/**
193 	 * Add the required listeners to the specified component
194 	 *
195 	 * @param component
196 	 *            the component the listeners are added to
197 	 */
registerComponent(Component... components)198 	public void registerComponent(Component... components) {
199 		for (Component component : components) {
200 			component.addMouseListener(this);
201 			component.addMouseMotionListener(this);
202 		}
203 	}
204 
205 	/**
206 	 * Get the snap size.
207 	 *
208 	 * @return the snap size.
209 	 */
getSnapSize()210 	public Dimension getSnapSize() {
211 		return snapSize;
212 	}
213 
214 	/**
215 	 * Control how many pixels a border must be dragged before the size of the
216 	 * component is changed. The border will snap to the size once dragging has
217 	 * passed the halfway mark.
218 	 *
219 	 * @param snapSize
220 	 *            Dimension object allows you to separately spcify a horizontal
221 	 *            and vertical snap size.
222 	 */
setSnapSize(Dimension snapSize)223 	public void setSnapSize(Dimension snapSize) {
224 		this.snapSize = snapSize;
225 	}
226 
227 	/**
228 	 * When the components minimum size is less than the drag insets then we
229 	 * can't determine which border should be resized so we need to prevent this
230 	 * from happening.
231 	 */
validateMinimumAndInsets(Dimension minimum, Insets drag)232 	private void validateMinimumAndInsets(Dimension minimum, Insets drag) {
233 		int minimumWidth = drag.left + drag.right;
234 		int minimumHeight = drag.top + drag.bottom;
235 
236 		if (minimum.width < minimumWidth || minimum.height < minimumHeight) {
237 			String message = "Minimum size cannot be less than drag insets";
238 			throw new IllegalArgumentException(message);
239 		}
240 	}
241 
242 	/**
243 	 */
244 	@Override
mouseMoved(MouseEvent e)245 	public void mouseMoved(MouseEvent e) {
246 		Component source = e.getComponent();
247 		Point location = e.getPoint();
248 		direction = 0;
249 
250 		if (location.x < dragInsets.left)
251 			direction += WEST;
252 
253 		if (location.x > source.getWidth() - dragInsets.right - 1)
254 			direction += EAST;
255 
256 		if (location.y < dragInsets.top)
257 			direction += NORTH;
258 
259 		if (location.y > source.getHeight() - dragInsets.bottom - 1)
260 			direction += SOUTH;
261 
262 		// Mouse is no longer over a resizable border
263 
264 		if (direction == 0) {
265 			source.setCursor(sourceCursor);
266 		} else // use the appropriate resizable cursor
267 		{
268 			int cursorType = cursors.get(direction);
269 			Cursor cursor = Cursor.getPredefinedCursor(cursorType);
270 			source.setCursor(cursor);
271 		}
272 	}
273 
274 	@Override
mouseEntered(MouseEvent e)275 	public void mouseEntered(MouseEvent e) {
276 		if (!resizing) {
277 			Component source = e.getComponent();
278 			sourceCursor = source.getCursor();
279 		}
280 	}
281 
282 	@Override
mouseExited(MouseEvent e)283 	public void mouseExited(MouseEvent e) {
284 		if (!resizing) {
285 			Component source = e.getComponent();
286 			source.setCursor(sourceCursor);
287 		}
288 	}
289 
290 	@Override
mousePressed(MouseEvent e)291 	public void mousePressed(MouseEvent e) {
292 		// The mouseMoved event continually updates this variable
293 
294 		if (direction == 0)
295 			return;
296 
297 		// Setup for resizing. All future dragging calculations are done based
298 		// on the original bounds of the component and mouse pressed location.
299 
300 		resizing = true;
301 
302 		Component source = e.getComponent();
303 		pressed = e.getPoint();
304 		SwingUtilities.convertPointToScreen(pressed, source);
305 		bounds = source.getBounds();
306 
307 		// Making sure autoscrolls is false will allow for smoother resizing
308 		// of components
309 
310 		if (source instanceof JComponent) {
311 			JComponent jc = (JComponent) source;
312 			autoscrolls = jc.getAutoscrolls();
313 			jc.setAutoscrolls(false);
314 		}
315 	}
316 
317 	/**
318 	 * Restore the original state of the Component
319 	 */
320 	@Override
mouseReleased(MouseEvent e)321 	public void mouseReleased(MouseEvent e) {
322 		resizing = false;
323 
324 		Component source = e.getComponent();
325 		source.setCursor(sourceCursor);
326 
327 		if (source instanceof JComponent) {
328 			((JComponent) source).setAutoscrolls(autoscrolls);
329 		}
330 	}
331 
332 	/**
333 	 * Resize the component ensuring location and size is within the bounds of
334 	 * the parent container and that the size is within the minimum and maximum
335 	 * constraints.
336 	 *
337 	 * All calculations are done using the bounds of the component when the
338 	 * resizing started.
339 	 */
340 	@Override
mouseDragged(MouseEvent e)341 	public void mouseDragged(MouseEvent e) {
342 		if (resizing == false)
343 			return;
344 
345 		Component source = e.getComponent();
346 		Point dragged = e.getPoint();
347 		SwingUtilities.convertPointToScreen(dragged, source);
348 
349 		changeBounds(source, direction, bounds, pressed, dragged);
350 	}
351 
changeBounds(Component source, int direction, Rectangle bounds, Point pressed, Point current)352 	protected void changeBounds(Component source, int direction,
353 			Rectangle bounds, Point pressed, Point current) {
354 		// Start with original locaton and size
355 
356 		int x = bounds.x;
357 		int y = bounds.y;
358 		int width = bounds.width;
359 		int height = bounds.height;
360 
361 		// Resizing the West or North border affects the size and location
362 
363 		if (WEST == (direction & WEST)) {
364 			int drag = getDragDistance(pressed.x, current.x, snapSize.width);
365 			int maximum = Math.min(width + x, maximumSize.width);
366 			drag = getDragBounded(drag, snapSize.width, width,
367 					minimumSize.width, maximum);
368 
369 			x -= drag;
370 			width += drag;
371 		}
372 
373 		if (NORTH == (direction & NORTH)) {
374 			int drag = getDragDistance(pressed.y, current.y, snapSize.height);
375 			int maximum = Math.min(height + y, maximumSize.height);
376 			drag = getDragBounded(drag, snapSize.height, height,
377 					minimumSize.height, maximum);
378 
379 			y -= drag;
380 			height += drag;
381 		}
382 
383 		// Resizing the East or South border only affects the size
384 
385 		if (EAST == (direction & EAST)) {
386 			int drag = getDragDistance(current.x, pressed.x, snapSize.width);
387 			Dimension boundingSize = getBoundingSize(source);
388 			int maximum = Math.min(boundingSize.width - x, maximumSize.width);
389 			drag = getDragBounded(drag, snapSize.width, width,
390 					minimumSize.width, maximum);
391 			width += drag;
392 		}
393 
394 		if (SOUTH == (direction & SOUTH)) {
395 			int drag = getDragDistance(current.y, pressed.y, snapSize.height);
396 			Dimension boundingSize = getBoundingSize(source);
397 			int maximum = Math.min(boundingSize.height - y, maximumSize.height);
398 			drag = getDragBounded(drag, snapSize.height, height,
399 					minimumSize.height, maximum);
400 			height += drag;
401 		}
402 
403 		source.setBounds(x, y, width, height);
404 		source.validate();
405 	}
406 
407 	/*
408 	 * Determine how far the mouse has moved from where dragging started
409 	 */
getDragDistance(int larger, int smaller, int snapSize)410 	private int getDragDistance(int larger, int smaller, int snapSize) {
411 		int halfway = snapSize / 2;
412 		int drag = larger - smaller;
413 		drag += (drag < 0) ? -halfway : halfway;
414 		drag = (drag / snapSize) * snapSize;
415 
416 		return drag;
417 	}
418 
419 	/*
420 	 * Adjust the drag value to be within the minimum and maximum range.
421 	 */
getDragBounded(int drag, int snapSize, int dimension, int minimum, int maximum)422 	private int getDragBounded(int drag, int snapSize, int dimension,
423 			int minimum, int maximum) {
424 		while (dimension + drag < minimum)
425 			drag += snapSize;
426 
427 		while (dimension + drag > maximum)
428 			drag -= snapSize;
429 
430 		return drag;
431 	}
432 
433 	/*
434 	 * Keep the size of the component within the bounds of its parent.
435 	 */
getBoundingSize(Component source)436 	private Dimension getBoundingSize(Component source) {
437 		if (source instanceof Window) {
438 			GraphicsEnvironment env = GraphicsEnvironment
439 					.getLocalGraphicsEnvironment();
440 			Rectangle bounds = env.getMaximumWindowBounds();
441 			return new Dimension(bounds.width, bounds.height);
442 		} else {
443 			return source.getParent().getSize();
444 		}
445 	}
446 }
447