468560: add ShellDragSupport

Change-Id: I6c357b238a200de87fe7f16085e2d8b68b1c7c43
Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=468560
diff --git a/org.eclipse.mylyn.commons.ui.tests/src/org/eclipse/mylyn/commons/ui/ShellDragSupportTest.java b/org.eclipse.mylyn.commons.ui.tests/src/org/eclipse/mylyn/commons/ui/ShellDragSupportTest.java
new file mode 100644
index 0000000..dccc359
--- /dev/null
+++ b/org.eclipse.mylyn.commons.ui.tests/src/org/eclipse/mylyn/commons/ui/ShellDragSupportTest.java
@@ -0,0 +1,105 @@
+package org.eclipse.mylyn.commons.ui;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Shell;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class ShellDragSupportTest {
+	private final Composite composite = mock(Composite.class, Mockito.RETURNS_DEEP_STUBS);
+
+	private final Shell shell = spy(new Shell());
+
+	private ShellDragSupport support;
+
+	@Before
+	public void setUp() {
+		shell.setLocation(100, 150);
+		when(composite.getShell()).thenReturn(shell);
+		support = new ShellDragSupport(composite);
+	}
+
+	@Test
+	public void shellDragSupport() {
+		verify(composite).getDisplay();
+		verify(composite).getShell();
+		verify(composite).addListener(SWT.MouseEnter, support);
+		verify(composite).addListener(SWT.MouseExit, support);
+		verify(composite).addListener(SWT.MouseMove, support);
+		verify(composite).addListener(SWT.MouseDown, support);
+		verify(composite).addListener(SWT.MouseUp, support);
+		verify(composite).addListener(SWT.Dispose, support);
+		verifyNoMoreInteractions(composite);
+	}
+
+	@Test
+	public void handleDisposeEnter() {
+		handleEvent(SWT.Dispose);
+		assertTrue(support.getMoveCursor().isDisposed());
+	}
+
+	@Test
+	public void handleMouseEnter() {
+		handleEvent(SWT.MouseEnter);
+		verify(shell).setCursor(argThat(equalTo(support.getMoveCursor())));
+	}
+
+	@Test
+	public void handleMouseExit() {
+		handleEvent(SWT.MouseExit);
+		verify(shell).setCursor(argThat(nullValue(Cursor.class)));
+	}
+
+	@Test
+	public void handleMouseMove() {
+		handleEvent(SWT.MouseMove, 110, 160);
+		assertEquals(new Point(100, 150), shell.getLocation());
+
+		handleEvent(SWT.MouseDown, 105, 160);
+		handleEvent(SWT.MouseMove, 109, 167);
+		verify(shell).setLocation(104, 157);
+
+		handleEvent(SWT.MouseMove, 111, 170);
+		verify(shell).setLocation(106, 160);
+
+		handleEvent(SWT.MouseMove, 110, 174);
+		verify(shell).setLocation(105, 164);
+
+		handleEvent(SWT.MouseUp);
+		handleEvent(SWT.MouseMove, 112, 176);
+		assertEquals(new Point(105, 164), shell.getLocation());
+	}
+
+	private void handleEvent(int type) {
+		handleEvent(type, 0, 0);
+	}
+
+	/**
+	 * x and y are relative to the display
+	 */
+	private void handleEvent(int type, int x, int y) {
+		Event event = new Event();
+		event.type = type;
+		Point pt = shell.toControl(x, y);
+		event.x = pt.x;
+		event.y = pt.y;
+		support.handleEvent(event);
+	}
+
+}
diff --git a/org.eclipse.mylyn.commons.ui/src/org/eclipse/mylyn/commons/ui/ShellDragSupport.java b/org.eclipse.mylyn.commons.ui/src/org/eclipse/mylyn/commons/ui/ShellDragSupport.java
new file mode 100644
index 0000000..fef19db
--- /dev/null
+++ b/org.eclipse.mylyn.commons.ui/src/org/eclipse/mylyn/commons/ui/ShellDragSupport.java
@@ -0,0 +1,66 @@
+package org.eclipse.mylyn.commons.ui;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Allows users to move a shell by dragging the given composite.
+ * 
+ * @since 3.17
+ */
+public final class ShellDragSupport implements Listener {
+	private int x = -1, y;
+
+	private final Cursor moveCursor;
+
+	private final Shell shell;
+
+	public ShellDragSupport(Composite composite) {
+		moveCursor = new Cursor(composite.getDisplay(), SWT.CURSOR_SIZEALL);
+		shell = composite.getShell();
+		composite.addListener(SWT.MouseEnter, this);
+		composite.addListener(SWT.MouseExit, this);
+		composite.addListener(SWT.MouseMove, this);
+		composite.addListener(SWT.MouseDown, this);
+		composite.addListener(SWT.MouseUp, this);
+		composite.addListener(SWT.Dispose, this);
+	}
+
+	@Override
+	public void handleEvent(Event event) {
+		Point pt = shell.toDisplay(event.x, event.y);
+		switch (event.type) {
+		case SWT.MouseEnter:
+			shell.setCursor(moveCursor);
+			break;
+		case SWT.MouseExit:
+			shell.setCursor(null);
+			break;
+		case SWT.MouseMove:
+			if (x == -1) {
+				break;
+			}
+			Point location = shell.getLocation();
+			shell.setLocation(location.x + pt.x - x, location.y + pt.y - y);
+			// fall through
+		case SWT.MouseDown:
+			x = pt.x;
+			y = pt.y;
+			break;
+		case SWT.MouseUp:
+			x = -1;
+			break;
+		case SWT.Dispose:
+			moveCursor.dispose();
+		}
+	}
+
+	Cursor getMoveCursor() {
+		return moveCursor;
+	}
+}
\ No newline at end of file