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