/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* MouseClickDragListener.java
* Creation date: (12/13/01 4:28:24 PM)
* By: Edward Lam
*/
package org.openquark.gems.client.utilities;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
/**
* MouseListener and MouseMotionListener that use reasonable models for click and drag
* Creation date: (12/13/01 4:28:24 PM)
* @author Edward Lam
*/
public abstract class MouseClickDragListener implements MouseListener, MouseMotionListener {
/** Naggle size the mouse can move before a button press becomes a drag */
protected static final int INITIAL_DRAG_OFFSET = 2;
/** Max distance between clicks to count as a double click */
protected static final int DOUBLE_CLICK_OFFSET_LIMIT = 2;
/** The number of milliseconds that should bound a double click.*/
protected static final long DOUBLE_CLICK_THRESHOLD_TIME = 300L;
/** Null zone for drag */
private java.awt.Rectangle dragNullRect;
/** The previous mouseRelease event. */
private MouseEvent lastMouseRelease;
/** Where we pressed. Null if we're not in the pressed state. */
protected java.awt.Point pressedAt;
/** What type of dragging action we are performing. */
protected DragMode dragMode;
/**
* The current drag was initiated with the CTRL modifier. This allows us to tell
* when a drag was started with the CTRL modifier but didn't end with it.
*/
protected boolean dragInitiatedWithCTRL = false;
/**
* The current drag was initiated with the SHIFT modifier. This allows us to tell
* when a drag was started with the SHIFT modifier but didn't end with it.
*/
protected boolean dragInitiatedWithSHIFT = false;
/**
* Drag action enum pattern.
* Creation date: (31/08/2001 10:58:43 AM)
* @author Edward Lam
*/
public static class DragMode extends EnumClass {
/*
* DRAGGING - dragging
* ABORTED - dragging in aborted state
* NOTDRAGGING - not dragging
*/
public static final DragMode DRAGGING = new DragMode ("Dragging");
public static final DragMode ABORTED = new DragMode ("Aborted");
public static final DragMode NOTDRAGGING = new DragMode ("Not dragging");
/**
* Default constructor for a drag mode.
* Creation date: (12/06/01 10:31:30 AM)
*/
public DragMode(String name) {
super(name);
}
}
/**
* Constructor for a MouseClickDragListener
* Creation date: (12/13/01 4:41:24 PM)
*/
public MouseClickDragListener() {
// Make the drag null zone rectangle
dragNullRect = new java.awt.Rectangle(0, 0, INITIAL_DRAG_OFFSET * 2, INITIAL_DRAG_OFFSET * 2);
// not dragging
dragMode = DragMode.NOTDRAGGING;
}
/**
* Move the drag mode into the aborted state.
* Creation date: (12/06/01 12:22:15 PM)
*/
protected void abortDrag() {
// Set our drag states appropriately
dragMode = DragMode.ABORTED;
}
/**
* Return the number of mouse buttons associated with a mouse event.
* Creation date: (03/08/2001 10:32:13 AM)
* @return int the number of buttons
* @param e MouseEvent the mouse event
*/
public static final int getNumMouseButtons(MouseEvent e) {
int numButtons = 0;
if (javax.swing.SwingUtilities.isLeftMouseButton(e))
numButtons++;
if (javax.swing.SwingUtilities.isMiddleMouseButton(e))
numButtons++;
if (javax.swing.SwingUtilities.isRightMouseButton(e))
numButtons++;
return numButtons;
}
/**
* Whether this drag mode actually enables accomplishing anything.
* Gem dragging, connecting, disconnecting, and selecting are useful.
* Aborted, useless, and not-dragging states are not useful.
* Creation date: (12/06/01 3:31:30 PM)
* @param mode DragMode the DragMode to check
* @return boolean true if accomplishing anything with this drag.
*/
protected boolean isUsefulDragMode(DragMode mode) {
return (mode == DragMode.DRAGGING);
}
/**
* Invoked when the mouse has been clicked on a component.
* Note: DO NOT USE unless you really want annoying UI behaviour. Use mouseReallyClicked() instead.
* You don't want to use this method (unless you're a masochist!)
* Mouse clicked events are not generated unless the pointer doesn't move even one pixel between press and release!
* Creation date: (12/13/01 4:28:24 PM)
* @see MouseListener#mouseClicked(MouseEvent)
*/
public final void mouseClicked(MouseEvent e) {
}
/**
* Creation date: (12/13/01 4:28:24 PM)
* @see MouseMotionListener#mouseDragged(MouseEvent)
*/
public void mouseDragged(MouseEvent e) {
try {
// If we bailed out, do nothing.
if (dragMode == DragMode.ABORTED) {
return;
}
// abort drags with > 1 mouse button
if (getNumMouseButtons(e) > 1) {
abortDrag();
return;
}
// Where are we now?
java.awt.Point where = e.getPoint();
// already dragging?
boolean wasDragging = (dragMode != DragMode.NOTDRAGGING);
// Check for a transition from the pressed to the dragging state.
// This is triggered by a mouse move beyond the mouse naggle.
if (!wasDragging && !dragNullRect.contains(where)) {
// Remember whether any modifiers were used or not
dragInitiatedWithCTRL = e.isControlDown();
dragInitiatedWithSHIFT = e.isShiftDown();
enterDragState(e);
}
// now figure out what to do if we're doing something in the drag state
if (isUsefulDragMode(dragMode)) {
mouseReallyDragged(e, where, wasDragging);
}
} catch (Throwable t) {
// some error occurred. Treat this as an aborted drag.
abortDrag();
t.printStackTrace();
}
}
/**
* Invoked when a mouse button has been pressed on a component.
* Creation date: (12/13/01 4:28:24 PM)
* @see MouseListener#mousePressed(MouseEvent)
*/
public void mousePressed(MouseEvent e){
try {
// If we're already doing a drag action, then abort!
// This can happen if the user is dragging with MB1 down and then presses another button.
if (dragMode != DragMode.NOTDRAGGING && dragMode != DragMode.ABORTED) {
abortDrag();
return;
}
// update the drag mode and press location
dragMode = DragMode.NOTDRAGGING;
pressedAt = e.getPoint();
// Set up the drag null zone for this click
dragNullRect.x = pressedAt.x - INITIAL_DRAG_OFFSET;
dragNullRect.y = pressedAt.y - INITIAL_DRAG_OFFSET;
} catch (Throwable t) {
// some error occurred. Treat this as an aborted drag.
abortDrag();
t.printStackTrace();
}
}
/**
* Invoked when a mouse button has been released on a component.
* Creation date: (12/13/01 4:28:24 PM)
* @see MouseListener#mouseReleased(MouseEvent)
*/
public void mouseReleased(MouseEvent e) {
// are we really dragging? If not, it's a "click".
if (dragMode == DragMode.NOTDRAGGING) {
mouseReallyClicked(convertEvent(e, MouseEvent.MOUSE_CLICKED));
} else {
// We've finished dragging. Finish up and take appropriate action
exitDragState(e);
// Clear the flags that tracks use of the CTRL and SHIFT modifiers.
dragInitiatedWithCTRL = false;
dragInitiatedWithSHIFT = false;
}
}
/**
* Surrogate method for mouseClicked. Called only when our definition of click occurs.
* Creation date: (12/13/01 5:23:24 PM)
* @param e MouseEvent the relevant event
* @return boolean true if the click was a double click
*/
public boolean mouseReallyClicked(MouseEvent e){
boolean doubleClicked = false;
long eventTime = e.getWhen();
long mouseUpClickTime = (lastMouseRelease == null) ? 0 : lastMouseRelease.getWhen();
// Calculate the time difference since the last release. Make sure the difference is a positive number.
// Note that the time value on a long of machines is a 64-bit *unsigned* value, but Java doesn't have unsigned
// numbers, so at a certain point the values roll over into the very high negative numbers which then
// actually *decrease* (since Java mandates a two's complement arithmetic).
long timeDiff = eventTime - mouseUpClickTime;
if (timeDiff < 0) {
timeDiff = -timeDiff;
}
// Did this click happen with the given time bound?
if (DOUBLE_CLICK_THRESHOLD_TIME > timeDiff) {
// did this occur with the same mouse button(s)?
if (lastMouseRelease != null && e.getModifiers() == lastMouseRelease.getModifiers()) {
// Set up the double click zone for this click
int zoneX = lastMouseRelease.getX() - DOUBLE_CLICK_OFFSET_LIMIT;
int zoneY = lastMouseRelease.getY() - DOUBLE_CLICK_OFFSET_LIMIT;
java.awt.Rectangle doubleClickZoneRect =
new java.awt.Rectangle(zoneX, zoneY, DOUBLE_CLICK_OFFSET_LIMIT * 2, DOUBLE_CLICK_OFFSET_LIMIT * 2);
// is the second click within the double click zone?
if (doubleClickZoneRect.contains(e.getPoint())) {
doubleClicked = true;
}
}
}
// update the previous mouse release event
if (doubleClicked) {
lastMouseRelease = null; // reset to avoid counting a triple click as two double clicks
} else {
lastMouseRelease = e;
}
return doubleClicked;
}
/**
* Surrogate method for mouseDragged. Called only when our definition of drag occurs.
* Creation date: (12/13/01 5:23:24 PM)
* @param e MouseEvent the relevant event
* @param where java.awt.Point the (possibly adjusted from e) coordinates of the drag
* @param wasDragging boolean True: this is a continuation of a drag. False: first call upon transition
* from pressed to drag.
*/
public abstract void mouseReallyDragged(MouseEvent e, java.awt.Point where, boolean wasDragging);
/**
* Carry out setup appropriate to enter the drag state.
* Principal effect is to change dragMode as appropriate.
* Creation date: (12/13/01 5:23:24 PM)
* @param e MouseEvent the mouse event which triggered entry into the drag state.
*/
public void enterDragState(MouseEvent e) {
dragMode = DragMode.DRAGGING;
}
/**
* Carry out setup appropriate to exit the drag state.
* Principal effect is to change dragMode as appropriate.
* Creation date: (12/06/01 6:14:42 PM)
* @param e MouseEvent the mouse event which caused an exit from the drag state
*/
public void exitDragState(MouseEvent e) {
dragMode = DragMode.NOTDRAGGING;
}
/**
* Converts the given mouse event to the new event type and returns the
* resulting event.
* @param e the original event
* @param id the new event id
* @return MouseEvent the converted mouse event
*/
private MouseEvent convertEvent(MouseEvent e, int id) {
return new MouseEvent((Component) e.getSource(),
id,
System.currentTimeMillis(),
e.getModifiers(),
e.getX(),
e.getY(),
e.getClickCount(),
e.isPopupTrigger(),
e.getButton());
}
}