package com.limegroup.gnutella.gui.tables;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.MouseDragGestureRecognizer;
import java.awt.event.MouseEvent;
import java.lang.reflect.Field;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputListener;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.util.CommonUtils;
/**
* Wraps the mouse events around mouse listeners that consume
* the event & check for pointing on a selection.
*/
public class TableDragRecognitionWrapper implements MouseInputListener,
DragSourceListener {
/**
* Indicates the dnd is armed.
*/
private boolean dndArmed = false;
/**
* The table this wrapper is working on.
*
* Required for checking to see if the mouse was acting
* on a selection.
*/
private final LimeJTable table;
/**
* The delegate MouseDragGestureRecognizer.
*/
private final MouseDragGestureRecognizer delegate;
/**
* The last press. Required for reprocessing presses
* that was mistaked for an initiating d&d gesture.
*/
private MouseEvent storedPress = null;
/**
* Constructs a new drag recognizer.
*/
TableDragRecognitionWrapper(LimeJTable table, MouseDragGestureRecognizer wrappee) {
this.table = table;
this.delegate = wrappee;
}
/**
* Forwards the click to the delegate.
*
* If the prior press was consumed, then we unconsume
* it and process the event. This is necessary to allow
* presses that were mistaken for the initiating d&d gesture
* to be correctly processed as selections.
*/
public void mouseClicked(MouseEvent e) {
dndArmed = false;
delegate.mouseClicked(e);
// if it was a click & we consumed it improperly
// then reprocess it, unconsumed.
if(storedPress != null && storedPress.isConsumed()) {
storedPress = unconsume(storedPress);
table.processMouseEvent(storedPress);
}
}
/**
* Ignores the event if it was the 'stored press', allowing
* a mistaken d&d recognition to be corrected.
*
* Arms the d&d code & consumes the event, preventing
* selection from changing.
*/
public void mousePressed(MouseEvent e) {
if(storedPress == e)
return;
dndArmed = false;
storedPress = e;
if (table.isPointSelected(e.getPoint()) &&
SwingUtilities.isLeftMouseButton(e) ) {
dndArmed = true;
e.consume();
delegate.mousePressed(e);
}
}
/**
* De-arms the d&d event.
*/
public void mouseReleased(MouseEvent e) {
dndArmed = false;
delegate.mouseReleased(e);
}
public void mouseEntered(MouseEvent e) {
delegate.mouseEntered(e);
}
public void mouseExited(MouseEvent e) {
delegate.mouseExited(e);
}
/**
* DnD is broken on OSX with JVMs elder than 1.4.2_05.
*
* @return true if OSX and JVM is not 1.4.2_05 or later
*/
private boolean isOSXAndDnDIsBroken() {
return CommonUtils.isMacOSX() &&
CommonUtils.getJavaVersion().compareTo("1.4.2_05") < 0;
}
/**
* Consumes the event if the d&d action is still armed,
* preventing the selection code from changing the selection
*/
public void mouseDragged(MouseEvent e) {
if (dndArmed) {
// If it's broken, select only 1 row, 'cause
// more than that will fail.
if (isOSXAndDnDIsBroken()) {
int row = table.rowAtPoint(e.getPoint());
if (row != -1)
table.setSelectedRow(row);
}
e.consume();
}
delegate.mouseDragged(e);
}
public void mouseMoved(MouseEvent e) {
delegate.mouseDragged(e);
}
/**
* Unarms the d&d.
*/
public void dragDropEnd(DragSourceDropEvent dsde) {
dndArmed = false;
}
public void dragEnter(DragSourceDragEvent dsde) {}
public void dragExit(DragSourceEvent dse){}
public void dragOver(DragSourceDragEvent dsde) {}
public void dropActionChanged(DragSourceDragEvent dsde) {}
/**
* The 'consumed' field in a MouseEvent.
*/
private final static Field consumed;
static {
Field f = null;
try {
f = java.awt.AWTEvent.class.getDeclaredField("consumed");
} catch(NoSuchFieldException nsfe) {
ErrorService.error(nsfe);
}
if(f != null)
f.setAccessible(true);
consumed = f;
}
/**
* Uses reflection to unset the 'consumed' field in a MouseEvent.
* This is required for fixing mistaken d&d recognition.
*/
private static MouseEvent unconsume(MouseEvent e) {
try {
if(consumed != null)
consumed.setBoolean(e, false);
} catch(IllegalAccessException iae) {}
return e;
}
}