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 
14 import javax.swing.JComponent;
15 import javax.swing.SwingUtilities;
16 
17 /**
18  * This class allows you to move a Component by using a mouse. The Component
19  * moved can be a high level Window (ie. Window, Frame, Dialog) in which case
20  * the Window is moved within the desktop. Or the Component can belong to a
21  * Container in which case the Component is moved within the Container.
22  *
23  * When moving a Window, the listener can be added to a child Component of the
24  * Window. In this case attempting to move the child will result in the Window
25  * moving. For example, you might create a custom "Title Bar" for an undecorated
26  * Window and moving of the Window is accomplished by moving the title bar only.
27  * Multiple components can be registered as "window movers".
28  *
29  * Components can be registered when the class is created. Additional components
30  * can be added at any time using the registerComponent() method.
31  *
32  * Taken from http://tips4java.wordpress.com/2009/06/14/moving-windows/
33  */
34 public class ComponentMover extends MouseAdapter {
35 	private Insets dragInsets = new Insets(0, 0, 0, 0);
36 	private Dimension snapSize = new Dimension(1, 1);
37 	private Insets edgeInsets = new Insets(0, 0, 0, 0);
38 	private boolean changeCursor = true;
39 	private boolean autoLayout = false;
40 
41 	private Class destinationClass;
42 	private Component destinationComponent;
43 	private Component destination;
44 	private Component source;
45 
46 	private Point pressed;
47 	private Point location;
48 
49 	private Cursor originalCursor;
50 	private boolean autoscrolls;
51 	private boolean potentialDrag;
52 
53 	/**
54 	 * Constructor for moving individual components. The components must be
55 	 * regisetered using the registerComponent() method.
56 	 */
ComponentMover()57 	public ComponentMover() {
58 	}
59 
60 	/**
61 	 * Constructor to specify a Class of Component that will be moved when drag
62 	 * events are generated on a registered child component. The events will be
63 	 * passed to the first ancestor of this specified class.
64 	 *
65 	 * @param destinationClass
66 	 *            the Class of the ancestor component
67 	 * @param component
68 	 *            the Components to be registered for forwarding drag events to
69 	 *            the ancestor Component.
70 	 */
ComponentMover(Class destinationClass, Component... components)71 	public ComponentMover(Class destinationClass, Component... components) {
72 		this.destinationClass = destinationClass;
73 		registerComponent(components);
74 	}
75 
76 	/**
77 	 * Constructor to specify a parent component that will be moved when drag
78 	 * events are generated on a registered child component.
79 	 *
80 	 * @param destinationComponent
81 	 *            the component drage events should be forwareded to
82 	 * @param components
83 	 *            the Components to be registered for forwarding drag events to
84 	 *            the parent component to be moved
85 	 */
ComponentMover(Component destinationComponent, Component... components)86 	public ComponentMover(Component destinationComponent,
87 			Component... components) {
88 		this.destinationComponent = destinationComponent;
89 		registerComponent(components);
90 	}
91 
92 	/**
93 	 * Get the auto layout property
94 	 *
95 	 * @return the auto layout property
96 	 */
isAutoLayout()97 	public boolean isAutoLayout() {
98 		return autoLayout;
99 	}
100 
101 	/**
102 	 * Set the auto layout property
103 	 *
104 	 * @param autoLayout
105 	 *            when true layout will be invoked on the parent container
106 	 */
setAutoLayout(boolean autoLayout)107 	public void setAutoLayout(boolean autoLayout) {
108 		this.autoLayout = autoLayout;
109 	}
110 
111 	/**
112 	 * Get the change cursor property
113 	 *
114 	 * @return the change cursor property
115 	 */
isChangeCursor()116 	public boolean isChangeCursor() {
117 		return changeCursor;
118 	}
119 
120 	/**
121 	 * Set the change cursor property
122 	 *
123 	 * @param changeCursor
124 	 *            when true the cursor will be changed to the Cursor.MOVE_CURSOR
125 	 *            while the mouse is pressed
126 	 */
setChangeCursor(boolean changeCursor)127 	public void setChangeCursor(boolean changeCursor) {
128 		this.changeCursor = changeCursor;
129 	}
130 
131 	/**
132 	 * Get the drag insets
133 	 *
134 	 * @return the drag insets
135 	 */
getDragInsets()136 	public Insets getDragInsets() {
137 		return dragInsets;
138 	}
139 
140 	/**
141 	 * Set the drag insets. The insets specify an area where mouseDragged events
142 	 * should be ignored and therefore the component will not be moved. This
143 	 * will prevent these events from being confused with a MouseMotionListener
144 	 * that supports component resizing.
145 	 *
146 	 * @param dragInsets
147 	 */
setDragInsets(Insets dragInsets)148 	public void setDragInsets(Insets dragInsets) {
149 		this.dragInsets = dragInsets;
150 	}
151 
152 	/**
153 	 * Get the bounds insets
154 	 *
155 	 * @return the bounds insets
156 	 */
getEdgeInsets()157 	public Insets getEdgeInsets() {
158 		return edgeInsets;
159 	}
160 
161 	/**
162 	 * Set the edge insets. The insets specify how close to each edge of the
163 	 * parent component that the child component can be moved. Positive values
164 	 * means the component must be contained within the parent. Negative values
165 	 * means the component can be moved outside the parent.
166 	 *
167 	 * @param edgeInsets
168 	 */
setEdgeInsets(Insets edgeInsets)169 	public void setEdgeInsets(Insets edgeInsets) {
170 		this.edgeInsets = edgeInsets;
171 	}
172 
173 	/**
174 	 * Remove listeners from the specified component
175 	 *
176 	 * @param component
177 	 *            the component the listeners are removed from
178 	 */
deregisterComponent(Component... components)179 	public void deregisterComponent(Component... components) {
180 		for (Component component : components)
181 			component.removeMouseListener(this);
182 	}
183 
184 	/**
185 	 * Add the required listeners to the specified component
186 	 *
187 	 * @param component
188 	 *            the component the listeners are added to
189 	 */
registerComponent(Component... components)190 	public void registerComponent(Component... components) {
191 		for (Component component : components)
192 			component.addMouseListener(this);
193 	}
194 
195 	/**
196 	 * Get the snap size
197 	 *
198 	 * @return the snap size
199 	 */
getSnapSize()200 	public Dimension getSnapSize() {
201 		return snapSize;
202 	}
203 
204 	/**
205 	 * Set the snap size. Forces the component to be snapped to the closest grid
206 	 * position. Snapping will occur when the mouse is dragged half way.
207 	 */
setSnapSize(Dimension snapSize)208 	public void setSnapSize(Dimension snapSize) {
209 		if (snapSize.width < 1 || snapSize.height < 1)
210 			throw new IllegalArgumentException(
211 					"Snap sizes must be greater than 0");
212 
213 		this.snapSize = snapSize;
214 	}
215 
216 	/**
217 	 * Setup the variables used to control the moving of the component:
218 	 *
219 	 * source - the source component of the mouse event destination - the
220 	 * component that will ultimately be moved pressed - the Point where the
221 	 * mouse was pressed in the destination component coordinates.
222 	 */
223 	@Override
mousePressed(MouseEvent e)224 	public void mousePressed(MouseEvent e) {
225 		source = e.getComponent();
226 		int width = source.getSize().width - dragInsets.left - dragInsets.right;
227 		int height = source.getSize().height - dragInsets.top
228 				- dragInsets.bottom;
229 		Rectangle r = new Rectangle(dragInsets.left, dragInsets.top, width,
230 				height);
231 
232 		if (r.contains(e.getPoint()))
233 			setupForDragging(e);
234 	}
235 
setupForDragging(MouseEvent e)236 	private void setupForDragging(MouseEvent e) {
237 		source.addMouseMotionListener(this);
238 		potentialDrag = true;
239 
240 		// Determine the component that will ultimately be moved
241 
242 		if (destinationComponent != null) {
243 			destination = destinationComponent;
244 		} else if (destinationClass == null) {
245 			destination = source;
246 		} else // forward events to destination component
247 		{
248 			destination = SwingUtilities.getAncestorOfClass(destinationClass,
249 					source);
250 		}
251 
252 		pressed = e.getLocationOnScreen();
253 		location = destination.getLocation();
254 
255 		if (changeCursor) {
256 			originalCursor = source.getCursor();
257 			source.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
258 		}
259 
260 		// Making sure autoscrolls is false will allow for smoother dragging of
261 		// individual components
262 
263 		if (destination instanceof JComponent) {
264 			JComponent jc = (JComponent) destination;
265 			autoscrolls = jc.getAutoscrolls();
266 			jc.setAutoscrolls(false);
267 		}
268 	}
269 
270 	/**
271 	 * Move the component to its new location. The dragged Point must be in the
272 	 * destination coordinates.
273 	 */
274 	@Override
mouseDragged(MouseEvent e)275 	public void mouseDragged(MouseEvent e) {
276 		Point dragged = e.getLocationOnScreen();
277 		int dragX = getDragDistance(dragged.x, pressed.x, snapSize.width);
278 		int dragY = getDragDistance(dragged.y, pressed.y, snapSize.height);
279 
280 		int locationX = location.x + dragX;
281 		int locationY = location.y + dragY;
282 
283 		// Mouse dragged events are not generated for every pixel the mouse
284 		// is moved. Adjust the location to make sure we are still on a
285 		// snap value.
286 
287 //		while (locationX < edgeInsets.left)
288 //			locationX += snapSize.width;
289 //
290 //		while (locationY < edgeInsets.top)
291 //			locationY += snapSize.height;
292 //
293 //		Dimension d = getBoundingSize(destination);
294 
295 //		while (locationX + destination.getSize().width + edgeInsets.right > d.width)
296 //			locationX -= snapSize.width;
297 //
298 //		while (locationY + destination.getSize().height + edgeInsets.bottom > d.height)
299 //			locationY -= snapSize.height;
300 
301 		// Adjustments are finished, move the component
302 
303 		destination.setLocation(locationX, locationY);
304 	}
305 
306 	/*
307 	 * Determine how far the mouse has moved from where dragging started (Assume
308 	 * drag direction is down and right for positive drag distance)
309 	 */
getDragDistance(int larger, int smaller, int snapSize)310 	private int getDragDistance(int larger, int smaller, int snapSize) {
311 		int halfway = snapSize / 2;
312 		int drag = larger - smaller;
313 		drag += (drag < 0) ? -halfway : halfway;
314 		drag = (drag / snapSize) * snapSize;
315 
316 		return drag;
317 	}
318 
319 	/*
320 	 * Get the bounds of the parent of the dragged component.
321 	 */
getBoundingSize(Component source)322 	private Dimension getBoundingSize(Component source) {
323 		if (source instanceof Window) {
324 			GraphicsEnvironment env = GraphicsEnvironment
325 					.getLocalGraphicsEnvironment();
326 			Rectangle bounds = env.getMaximumWindowBounds();
327 			return new Dimension(bounds.width, bounds.height);
328 		} else {
329 			return source.getParent().getSize();
330 		}
331 	}
332 
333 	/**
334 	 * Restore the original state of the Component
335 	 */
336 	@Override
mouseReleased(MouseEvent e)337 	public void mouseReleased(MouseEvent e) {
338 		if (!potentialDrag)
339 			return;
340 
341 		source.removeMouseMotionListener(this);
342 		potentialDrag = false;
343 
344 		if (changeCursor)
345 			source.setCursor(originalCursor);
346 
347 		if (destination instanceof JComponent) {
348 			((JComponent) destination).setAutoscrolls(autoscrolls);
349 		}
350 
351 		// Layout the components on the parent container
352 
353 		if (autoLayout) {
354 			if (destination instanceof JComponent) {
355 				((JComponent) destination).revalidate();
356 			} else {
357 				destination.validate();
358 			}
359 		}
360 	}
361 }
362