All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openbp.jaspira.gui.interaction.DragDropPane Maven / Gradle / Ivy

The newest version!
/*
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package org.openbp.jaspira.gui.interaction;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.AbstractAction;
import javax.swing.FocusManager;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.ToolTipManager;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
import javax.swing.text.JTextComponent;

import org.openbp.common.icon.MultiIcon;
import org.openbp.common.rc.ResourceCollection;
import org.openbp.common.rc.ResourceCollectionMgr;
import org.openbp.jaspira.JaspiraConstants;

/**
 * A drag and drop pane is a component that is to be used as glass pane for a window.
 * It is used to handle drag and drop events and propagate them to {@link InteractionClient} s via {@link DragAwareRegion} s.
 * It also provides support for break out box (bob) menus.
 * A bob is a circular overlay window that presents items that can be imported into the underlying
 * drop client. The user may select the item by simply clicking on it. A bob menu is always initiated
 * by a hot key.
 *
 * @author Stephan Moritz
 */
public class DragDropPane extends JComponent
{
	/** Accept cursor. Indicates that a drop is possible at the current position. */
	public static CursorPrototype acceptCursorPrototype;

	/** Reject cursor. Indicates that a drop is not possible at the current position. */
	public static CursorPrototype rejectCursorPrototype;

	/** Size of a break out box entry */
	private static final int BOB_ENTRY_SIZE = 60;

	/** Size of the break out box item circle */
	private static final int BOB_CIRCLE_SIZE = 32;

	/** Key for the escape action in the action map */
	private static final String ESCAPE = "escape";

	/** List of currently registered drop clients (contains {@link InteractionClient} objects) */
	private Set dropClients;

	/** Drawing composite used for drawing on the glass pane */
	private Composite composite;

	/** List of all drag-aware regions currently active */
	private List currentRegions;

	/** The currently active region of a dnd action */
	private DragAwareRegion currentRegion;

	/** The currently transported data of a dnd action */
	private Transferable currentTransferable;

	/** The supported flavors of the current drag action */
	private List currentFlavors;

	/** The currently active drag originator of a dnd action */
	private DragOrigin currentDragOrigin;

	/** Icon denoting the currently dragged drag item */
	private MultiIcon currentDragIcon;

	/** Current accept cursor */
	private Cursor currentAcceptCursor;

	/** Current reject cursor */
	private Cursor currentRejectCursor;

	/** Mouse event that triggered the drag action */
	private MouseEvent mouseEvent;

	/** The last point used during drag or bobmode */
	private Point lastLocation;

	/** Is the bob shown? */
	private boolean breakoutshown;

	/** all regions of our breakoutmenu */
	private BreakoutBoxEntry[] bobEntries;

	/** Mouse listener for bob mode */
	private MouseInputListener bobMouseListener;

	/** Mouse wheel listener for bob mode */
	private MouseWheelListener bobMouseWheelListener;

	/** Point where the bob is being displayed */
	private Point bobPosition;

	/** Point where we drop the selection of the bob */
	private Point bobDropPoint;

	/** Radius of the bob */
	private int bobRadius;

	/** Angle between two entries */
	private double angleDelta;

	/** Angle offset */
	private double angleOffset;

	/** Title of the recently selected entry */
	private String recentlySelectedEntryTitle;

	private int animationPos = -1;
	private double animationAngle;
	private Timer animationTimer; 

	private static int[] animationControl = new int[] { 1, 2, 4, 7, 12, 20, 12, 7, 4, 2, 1 };
	private static int animationLevels;
	static
	{
		animationLevels = 0;
		for (int i = 0; i < animationControl.length; ++i)
		{
			animationLevels += animationControl[i];
		}
	}
	private static long animationInterval = 50;

	static
	{
		// Create cursor prototypes
		ResourceCollection res = ResourceCollectionMgr.getDefaultInstance().getResource(JaspiraConstants.RESOURCE_JASPIRA, DragDropPane.class);

		acceptCursorPrototype = new BasicCursorPrototype(null, ((ImageIcon) res.getRequiredObject("cursor.dragaccept")).getImage(), new Point());

		rejectCursorPrototype = new BasicCursorPrototype(null, ((ImageIcon) res.getRequiredObject("cursor.dragreject")).getImage(), new Point());
	}

	/**
	 * Creates a new drag drop pane and installs it as a glass pane for the
	 * givent container.
	 * Does nothing if a dnd pane has already been installed
	 * @param container Container to install the pane for
	 * @return The installed dnd pane
	 */
	public static DragDropPane installDragDropPane(DropPaneContainer container)
	{
		Component glassPane = container.getGlassPane();
		if (glassPane instanceof DragDropPane)
			return (DragDropPane) glassPane;

		DragDropPane pane = new DragDropPane();
		container.setDragDropPane(pane);
		return pane;
	}

	/**
	 * Constructor.
	 * We do not want a DragPane to be installed into open space.
	 * Rather use: install ().
	 */
	private DragDropPane()
	{
		super();

		// We need it to be invisible
		setOpaque(false);

		// Make the dnd pane slightly opaque when displayed
		setTransparency(0.2f);

		dropClients = new HashSet();
		currentRegions = new ArrayList();

		// The pane may provide tool tips for regions
		ToolTipManager.sharedInstance().registerComponent(this);

		// Register an action to close the breakout box when escape is being pressed.
		registerBobCloseAction();

		lastLocation = new Point();
	}

	/////////////////////////////////////////////////////////////////////////
	// @@ Painting
	/////////////////////////////////////////////////////////////////////////

	/**
	 * Draws the Pane (i\.e\. the active DropRegions)
	 * @see java.awt.Component#paint(Graphics)
	 */
	public void paint(Graphics g)
	{
		if (!isVisible())
			return;

		if (currentFlavors != null)
		{
			// We are in drag/Drop mode
			Graphics2D g2 = (Graphics2D) g;

			// We save the old Composite object of the context
			// Set our composite (transparency)
			Composite oldComposite = g2.getComposite();
			g2.setComposite(composite);

			for (Iterator it = currentRegions.iterator(); it.hasNext();)
			{
				((DragAwareRegion) it.next()).draw(g2);
			}

			if (currentRegion != null && currentRegion.getOverlay() != null)
			{
				g2.setColor(Color.BLACK);

				g2.draw(currentRegion.getOverlay());
			}

			// Reset the composite
			g2.setComposite(oldComposite);
		}
		else if (bobEntries != null)
		{
			// We are in bob mode
			Graphics2D g2 = (Graphics2D) g;

			// Set our Composite (transparency)
			Composite oldComposite = g2.getComposite();
			g2.setComposite(composite);

			// Bob is always blue
			g2.setColor(Color.BLUE);

			// Draw the outer bob circle
			g2.fillOval(bobPosition.x - bobRadius, bobPosition.y - bobRadius, bobRadius * 2, bobRadius * 2);

			// Reset the composite
			g2.setComposite(oldComposite);

			// Draw the inner bob circle
			g2.drawOval(bobPosition.x - 5, bobPosition.y - 5, 10, 10);

			// Draw the bob entries
			for (int i = 0; i < bobEntries.length; i++)
			{
				bobEntries[i].draw(g);
			}
		}
	}

	/**
	 * Rebuilds the DragAwareRegions of the pane.
	 */
	public void regionsInvalidated()
	{
		repaintRegions();

		currentRegions.clear();

		if (currentFlavors != null)
		{
			for (Iterator it = dropClients.iterator(); it.hasNext();)
			{
				InteractionClient next = (InteractionClient) it.next();
				List regions = next.getAllDropRegions(currentFlavors, currentTransferable, mouseEvent);
				if (regions != null)
				{
					currentRegions.addAll(regions);
				}
			}
		}

		repaintRegions();
	}

	/**
	 * Repaints the current dnd regions.
	 */
	private void repaintRegions()
	{
		Rectangle invalidateRect = new Rectangle();

		for (Iterator it = currentRegions.iterator(); it.hasNext();)
		{
			DragAwareRegion region = (DragAwareRegion) it.next();

			Rectangle r = region.getBounds();
			invalidateRect.add(r);
		}

		repaint(invalidateRect);
	}

	//////////////////////////////////////////////////
	// @@ Dnd support
	//////////////////////////////////////////////////

	/**
	 * Initiates a dragging action.
	 * Determines the flavors supported by the transferable and notifies the drop clients.
	 * @param transferable Transferable to be dragged
	 * @param dragOrigin Originator of the dragging action
	 * @param mouseEvent Mouse event that triggered the drag action
	 */
	public void startDrag(Transferable transferable, DragOrigin dragOrigin, MouseEvent mouseEvent)
	{
		currentTransferable = transferable;
		currentDragOrigin = dragOrigin;
		currentFlavors = Arrays.asList(currentTransferable.getTransferDataFlavors());
		this.mouseEvent = mouseEvent;

		// Display the pane
		setVisible(true);

		// Create a drag cursors
		// Use the dragged item for prototype construction.
		currentDragIcon = currentDragOrigin.getDragImage();
		currentAcceptCursor = acceptCursorPrototype.createCursor(currentDragIcon);
		currentRejectCursor = rejectCursorPrototype.createCursor(currentDragIcon);

		// Initially set the reject cursor
		setCursor(currentRejectCursor);

		// Notify clients of started dragging
		for (Iterator it = dropClients.iterator(); it.hasNext();)
		{
			((InteractionClient) it.next()).dragStarted(currentTransferable);
		}

		regionsInvalidated();
	}

	/**
	 * Cancels any currently ongoing drag action.
	 */
	public void cancelDrag()
	{
		if (currentTransferable != null && currentDragOrigin != null)
		{
			currentDragOrigin.dropCanceled(currentTransferable);
		}
		endDrag();
	}

	/**
	 * Called when the drag ends. Clears all regions and notifies the drop clients.
	 */
	public void endDrag()
	{
		Transferable oldTransferable = currentTransferable;

		currentDragOrigin = null;
		currentRegion = null;
		currentRegions.clear();
		currentFlavors = null;
		currentTransferable = null;
		mouseEvent = null;

		currentDragIcon = null;
		currentAcceptCursor = null;
		currentRejectCursor = null;

		// Notify clients of ended dragging
		if (oldTransferable != null)
		{
			for (Iterator it = dropClients.iterator(); it.hasNext();)
			{
				((InteractionClient) it.next()).dragEnded(oldTransferable);
			}
		}

		setCursor(Cursor.getDefaultCursor());

		setVisible(false);
	}

	//////////////////////////////////////////////////
	// @@ BOB support
	//////////////////////////////////////////////////

	/**
	 * Starts the bob mode using the given provider.
	 *
	 * @param provider Provider for bob entries
	 * @param position Center of the bob in screen coordinates
	 */
	public void startBreakOutMode(final BreakoutProvider provider, Point position)
	{
		if (FocusManager.getCurrentManager().getFocusOwner() instanceof JTextComponent)
		{
			// Umpf, quick hack to prevent keys entered in text fields from initiating the bob mode
			return;
		}

		bobMouseListener = new MouseInputAdapter()
		{
			public void mouseClicked(MouseEvent e)
			{
				if (breakoutshown)
				{
					if (bobEntries != null)
					{
						for (int i = 0; i < bobEntries.length; i++)
						{
							if (bobEntries[i].reactsOn(e.getX(), e.getY()))
							{
								BreakoutBoxEntry bobEntry = bobEntries[i];

								endBreakOutMode();

								bobEntry.importData(bobDropPoint);

								recentlySelectedEntryTitle = bobEntry.getToolTipText();
								return;
							}
						}
					}

					endBreakOutMode();
					return;
				}
			}

			/**
			 * @see javax.swing.event.MouseInputAdapter#mouseMoved(java.awt.event.MouseEvent)
			 */
			public void mouseMoved(MouseEvent e)
			{
				// We store the new position
				lastLocation = e.getPoint();
			}
		};
		addMouseListener(bobMouseListener);
		addMouseMotionListener(bobMouseListener);

		bobMouseWheelListener = new MouseWheelListener()
		{
			public void mouseWheelMoved(MouseWheelEvent e)
			{
				int notches = e.getWheelRotation();

				rotateBobEntries(notches);
			}
		};
		addMouseWheelListener(bobMouseWheelListener);

		bobPosition = new Point(position);
		bobDropPoint = new Point(position);
		lastLocation = new Point(bobDropPoint);

		buildBreakOutMenu(provider);
		showBreakOutMenu();
		setVisible(true);
		breakoutshown = true;
	}

	/**
	 * Ends the bob mode
	 */
	public void endBreakOutMode()
	{
		if (breakoutshown)
		{
			// Invalidate the bob area
			int r = bobRadius + BOB_ENTRY_SIZE;
			Rectangle invalidateRect = new Rectangle(bobPosition.x - r, bobPosition.y - r, 2 * r, 2 * r);
			repaint(invalidateRect);

			bobEntries = null;
			breakoutshown = false;
		}

		removeMouseListener(bobMouseListener);
		removeMouseMotionListener(bobMouseListener);
		bobMouseListener = null;

		removeMouseWheelListener(bobMouseWheelListener);
		bobMouseWheelListener = null;

		Point pos = getLocationOnScreen();
		pos.translate(lastLocation.x, lastLocation.y);
		setVisible(false);
	}

	private void buildBreakOutMenu(final BreakoutProvider provider)
	{
		// Determine the importers that might be displayed in the bob
		List importers = new ArrayList();
		for (Iterator it = dropClients.iterator(); it.hasNext();)
		{
			InteractionClient client = (InteractionClient) it.next();

			List clientImporters = client.getAllImportersAt(bobDropPoint);
			if (clientImporters != null)
			{
				importers.addAll(clientImporters);
			}
		}

		// Retrieve all valid BreakOutEntries for the given coordinates and location
		// and arrange them on a circle.
		bobEntries = provider.createBreakOutEntries(importers);

		bobRadius = Math.max((int) Math.round(bobEntries.length * BOB_ENTRY_SIZE / (2d * Math.PI)), 30);
		bobPosition.translate(-bobRadius, 0);

		angleOffset = 0d;
		angleDelta = 2d * Math.PI / bobEntries.length;

		if (recentlySelectedEntryTitle != null)
		{
			for (int i = 0; i < bobEntries.length; ++i)
			{
				if (recentlySelectedEntryTitle.equals(bobEntries[i].getToolTipText()))
				{
					angleOffset += i * angleDelta;
				}
			}
		}
	}

	private void showBreakOutMenu()
	{
		angleOffset = normalizeAngle(angleOffset);

		Rectangle invalidateRect = new Rectangle();
		int n = bobEntries.length;
		for (int i = 0; i < n; i++)
		{
			Rectangle rect = new Rectangle(bobPosition.x, bobPosition.y, BOB_CIRCLE_SIZE, BOB_CIRCLE_SIZE);

			double alpha = angleOffset + angleDelta * (n - i) + Math.PI / 2;
			rect.x += (int) Math.round(Math.sin(alpha) * bobRadius) - (BOB_CIRCLE_SIZE / 2);
			rect.y += (int) Math.round(Math.cos(alpha) * bobRadius) - (BOB_CIRCLE_SIZE / 2);

			bobEntries[i].setLocationOnGlassPanel(rect);

			Rectangle invalid = new Rectangle(rect);
			invalid.grow(10, 10);
			invalidateRect.add(invalid);
		}

		// Ensure that the bob box is entirely visible on the screen
		int xDiff = 0;
		int yDiff = 0;
		Rectangle screenRect = getBounds();
		if (invalidateRect.x < screenRect.x)
			xDiff = screenRect.x - invalidateRect.x;
		else if (invalidateRect.x + invalidateRect.width > screenRect.x + screenRect.width)
			xDiff = (screenRect.x + screenRect.width) - (invalidateRect.x + invalidateRect.width);
		if (invalidateRect.y < screenRect.y)
			yDiff = screenRect.y - invalidateRect.y;
		else if (invalidateRect.y + invalidateRect.height > screenRect.y + screenRect.height)
			yDiff = (screenRect.y + screenRect.height) - (invalidateRect.y + invalidateRect.height);

		if (xDiff != 0 || yDiff != 0)
		{
			// Doesn't fit on screen, move into screen bounds
			bobPosition.translate(xDiff, yDiff);
			invalidateRect.translate(xDiff, yDiff);

			for (int i = 0; i < n; i++)
			{
				Rectangle rect = bobEntries[i].getLocationOnGlassPanel();
				rect.translate(xDiff, yDiff);
				bobEntries[i].setLocationOnGlassPanel(rect);
			}
		}

		repaint(invalidateRect);
	}

	private void rotateBobEntries(int delta)
	{
		double angleToMove = -delta * angleDelta;

		// Finish the last animation
		if (animationTimer != null)
		{
			endAnimation();
			angleToMove += getRemainingAnimationAngle();
		}

		int n = bobEntries.length;
		if (n == 0)
			return;
		delta %= n;
		if (delta == 0)
			return;

		// Computer and start animation
		animationPos = 0;
		animationAngle = angleToMove / animationLevels;

		animationTimer = new Timer("Breakout menu animation timer");
		animationTimer.schedule(new TimerTask()
		{
			public void run()
			{
				if (! advanceAnimation())
				{
					endAnimation();
					angleOffset += getRemainingAnimationAngle();
					showBreakOutMenu();
				}
			}
		}, 0, animationInterval);
	}

	private void endAnimation()
	{
		if (animationTimer != null)
		{
			animationTimer.cancel();
			animationTimer = null;
		}
	}

	private double getRemainingAnimationAngle()
	{
		double remainingAngle = 0d;
		if (animationPos >= 0 && animationPos < animationControl.length)
		{
			int n = 0;
			for (int i = animationPos; i < animationControl.length; ++i)
			{
				n += animationControl[i];
			}
			remainingAngle = n * animationAngle;
		}
		return remainingAngle;
	}

	private boolean advanceAnimation()
	{
		if (animationPos < 0)
			return false;

		angleOffset += animationControl[animationPos] * animationAngle;
		showBreakOutMenu();

		if (++animationPos < animationControl.length)
		{
			return true;
		}
		return false;
	}

	/**
	 * Registers an action to close the bob when escape is being pressed.
	 */
	private void registerBobCloseAction()
	{
		getActionMap().put(ESCAPE, new AbstractAction()
		{
			public void actionPerformed(ActionEvent e)
			{
				if (breakoutshown && isVisible())
				{
					endBreakOutMode();
				}
			}
		});

		getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), ESCAPE);
	}

	/////////////////////////////////////////////////////////////////////////
	// @@ DragInitiator connection
	/////////////////////////////////////////////////////////////////////////

	/**
	 * Handler for 'mouse dragged' events.
	 * Passes the event to the current region in dnd mode and updates the dnd cursor.
	 * @param p Current mouse position
	 */
	public void mouseDragged(Point p)
	{
		lastLocation = p;

		DragAwareRegion newRegion = getRegionAt(p);

		if (newRegion != currentRegion)
		{
			// We are over another region
			if (currentRegion != null)
			{
				currentRegion.dragExit();
			}

			currentRegion = newRegion;
			if (currentRegion != null)
			{
				currentRegion.dragEnter();

				CursorPrototype cursor = currentRegion.getCursor();
				if (cursor != null)
				{
					setCursor(cursor.createCursor(currentDragIcon));
				}
				else if (currentRegion != null && currentRegion.canImport())
				{
					setCursor(currentAcceptCursor);
				}
				else
				{
					setCursor(currentRejectCursor);
				}
			}
			else
			{
				setCursor(currentRejectCursor);
			}
		}
	}

	/**
	 * Handler for 'mouse released' events.
	 * Notifies the current drag originator of the drop and ends dnd mode.
	 * @param p Current mouse position in glass coordinates
	 */
	public void mouseReleased(Point p)
	{
		try
		{
			// Put this in a try-finally block to prevent never-ending drag mode on exceptions
			if (currentDragOrigin != null)
			{
				if (currentRegion != null && currentTransferable != null)
				{
					currentDragOrigin.dropAccepted(currentTransferable);

					if (currentRegion.importData(currentTransferable, p))
					{
						currentDragOrigin.dropPerformed(currentTransferable);
					}
					else
					{
						currentDragOrigin.dropCanceled(currentTransferable);
					}
				}
				else
				{
					currentDragOrigin.dropCanceled(currentTransferable);
				}
			}
		}
		finally
		{
			endDrag();
		}
	}

	/////////////////////////////////////////////////////////////////////////
	// @@ Tooltip
	/////////////////////////////////////////////////////////////////////////

	/**
	 * Returns the tool tip appropriate for the given region.
	 * @param event Mouse event containing the coordinates that should be used to determine
	 * the hovered bob entry if the bob is active.
	 * @return The tip or null
	 */
	public String getToolTipText(MouseEvent event)
	{
		if (event != null)
		{
			if (currentFlavors != null && currentRegion != null)
			{
				// We are in dnd mode, return the tooltip for the current region
				return currentRegion.getToolTipText();
			}
			else if (bobEntries != null)
			{
				for (int i = 0; i < bobEntries.length; i++)
				{
					if (bobEntries[i].reactsOn(event.getX(), event.getY()))
					{
						return bobEntries[i].getToolTipText();
					}
				}
			}
		}

		return null;
	}

	/////////////////////////////////////////////////////////////////////////
	// @@ Regions
	/////////////////////////////////////////////////////////////////////////

	/**
	 * Returns the region at the given point.
	 * @param p Point to check
	 * @return The region or null if no region corresponds to this point
	 */
	private DragAwareRegion getRegionAt(Point p)
	{
		for (Iterator it = currentRegions.iterator(); it.hasNext();)
		{
			DragAwareRegion region = (DragAwareRegion) it.next();

			if (region.reactsOn(p.x, p.y))
			{
				// We have our region
				return region;
			}
		}

		// No region found
		return null;
	}

	/////////////////////////////////////////////////////////////////////////
	// @@ Adding and removal of DropReceivers
	/////////////////////////////////////////////////////////////////////////

	/**
	 * Adds a drop client.
	 * @param client Client to add
	 */
	public void addDropClient(InteractionClient client)
	{
		dropClients.add(client);
	}

	/**
	 * Removes a drop client.
	 * @param client Client to remove
	 */
	public void removeDropClient(InteractionClient client)
	{
		dropClients.remove(client);
	}

	/**
	 * Removes all drop clients.
	 */
	public void clearDropClients()
	{
		dropClients.clear();
	}

	/**
	 * Sets the composite used to draw the glasPaneElements.
	 * This will usually be an instance of AlphaComposite.
	 * Note that it is usually more convient to set the transparency directly via
	 * {@link #setTransparency}, which incidently just creates a new AlphaComposite object.
	 *
	 * @param composite The composite to set
	 * @see java.awt.AlphaComposite
	 */
	public void setComposite(Composite composite)
	{
		this.composite = composite;
	}

	/**
	 * Sets the alpha Component, i\.e\. the transparency of the drop pane.
	 *
	 * @param alpha New alpha value, between 0f (opaque) 1f (fully transparent)
	 */
	public void setTransparency(float alpha)
	{
		setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
	}

	/**
	 * Returns the originator of the current dnd action.
	 * @return The drag originator or null if no dnd action is going on
	 */
	public DragOrigin getDragOrigin()
	{
		return currentDragOrigin;
	}

	/**
	 * Returns the last mouse location.
	 * @return The mouse location in screen coordinates
	 */
	public Point getLastLocation()
	{
		return lastLocation;
	}

	/**
	 * Gets the mouse event that triggered the drag action.
	 */
	public MouseEvent getMouseEvent()
	{
		return mouseEvent;
	}

	private static final double CIRCLE = 2d * Math.PI;

	/**
	 * Normalizes an angle.
	 *
	 * @param angle Angle to normalize
	 * @return The angle in the range [0;2 * PI]
	 */
	private static double normalizeAngle(double angle)
	{
		// Normalize the angle
		while (angle > CIRCLE)
		{
			angle -= CIRCLE;
		}
		while (angle < 0)
		{
			angle += CIRCLE;
		}
		return angle;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy