org.jdesktop.swingx.rollover.RolloverProducer Maven / Gradle / Ivy
/*
* $Id$
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.rollover;
import static javax.swing.SwingUtilities.convertPointFromScreen;
import static javax.swing.SwingUtilities.isDescendingFrom;
import java.awt.KeyboardFocusManager;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.logging.Logger;
import javax.swing.JComponent;
/**
* Mouse/Motion/Listener which maps mouse coordinates to client coordinates
* and stores these as client properties in the target JComponent. The exact
* mapping process is left to subclasses. Typically, they will map to "cell"
* coordinates.
*
* Note: this class assumes that the target component is of type JComponent.
* Note: this implementation is stateful, it can't be shared across different
* instances of a target component.
*
*
* @author Jeanette Winzenburg
*/
public abstract class RolloverProducer implements MouseListener, MouseMotionListener,
ComponentListener {
@SuppressWarnings("unused")
private static final Logger LOG = Logger.getLogger(RolloverProducer.class
.getName());
/**
* Key for client property mapped from mouse-triggered action.
* Note that the actual mouse-event which results in setting the property
* depends on the implementation of the concrete RolloverProducer.
*/
public static final String CLICKED_KEY = "swingx.clicked";
/** Key for client property mapped from rollover events */
public static final String ROLLOVER_KEY = "swingx.rollover";
// public static final String PRESSED_KEY = "swingx.pressed";
private boolean isDragging;
/**
* Installs all listeners, as required.
*
* @param component target to install required listeners on, must
* not be null.
*/
public void install(JComponent component) {
component.addMouseListener(this);
component.addMouseMotionListener(this);
component.addComponentListener(this);
}
/**
* Removes all listeners.
*
* @param component target component to uninstall required listeners from,
* must not be null
*/
public void release(JComponent component) {
component.removeMouseListener(this);
component.removeMouseMotionListener(this);
component.removeComponentListener(this);
}
//----------------- mouseListener
/**
* Implemented to map to Rollover properties as needed. This implemenation calls
* updateRollover with both ROLLOVER_KEY and CLICKED_KEY properties.
*/
@Override
public void mouseReleased(MouseEvent e) {
Point oldCell = new Point(rollover);
// JW: fix for #456-swingx - rollover not updated after end of dragging
updateRollover(e, ROLLOVER_KEY, false);
// Fix Issue 1387-swingx - no click on release-after-drag
if (isClick(e, oldCell, isDragging)) {
updateRollover(e, CLICKED_KEY, true);
}
isDragging = false;
}
/**
* Returns a boolean indicating whether or not the given mouse event should
* be interpreted as a click. This method is called from mouseReleased
* after the cell coordiates were updated. While the ID of mouse event
* is not formally enforced, it is assumed to be a MOUSE_RELEASED. Calling
* for other types might or might not work as expected.
*
* This implementation returns true if the current rollover point is the same
* cell as the given oldRollover, that is ending a drag inside the same cell
* triggers the action while ending a drag somewhere does not.
*
* PENDING JW: open to more complex logic in case it clashes with existing code,
* see Issue #1387.
*
* @param e the mouseEvent which triggered calling this, assumed to be
* a mouseReleased, must not be null
* @param oldRollover the cell before the mouseEvent was mapped, must not be null
* @param wasDragging true if the release happened
* @return a boolean indicating whether or not the given mouseEvent should
* be interpreted as a click.
*/
protected boolean isClick(MouseEvent e, Point oldRollover, boolean wasDragging) {
return oldRollover.equals(rollover);
}
/**
* Implemented to map to client property rollover and fire only if client
* coordinate changed.
*/
@Override
public void mouseEntered(MouseEvent e) {
// LOG.info("" + e);
isDragging = false;
updateRollover(e, ROLLOVER_KEY, false);
}
/**
* Implemented to remove client properties rollover and clicked. if the
* source is a JComponent. Does nothing otherwise.
*/
@Override
public void mouseExited(MouseEvent e) {
isDragging = false;
// screenLocation = null;
// LOG.info("" + e);
// if (((JComponent) e.getComponent()).getMousePosition(true) != null) {
// updateRollover(e, ROLLOVER_KEY, false);
// } else {
// }
((JComponent) e.getSource()).putClientProperty(ROLLOVER_KEY, null);
((JComponent) e.getSource()).putClientProperty(CLICKED_KEY, null);
}
/**
* Implemented to do nothing.
*/
@Override
public void mouseClicked(MouseEvent e) {
}
/**
* Implemented to do nothing.
*/
@Override
public void mousePressed(MouseEvent e) {
}
// ---------------- MouseMotionListener
/**
* Implemented to set a dragging flag to true.
*/
@Override
public void mouseDragged(MouseEvent e) {
isDragging = true;
}
/**
* Implemented to map to client property rollover and fire only if client
* coordinate changed.
*/
@Override
public void mouseMoved(MouseEvent e) {
updateRollover(e, ROLLOVER_KEY, false);
}
//---------------- ComponentListener
@Override
public void componentShown(ComponentEvent e) {
}
@Override
public void componentResized(ComponentEvent e) {
updateRollover(e);
}
@Override
public void componentMoved(ComponentEvent e) {
updateRollover(e);
}
/**
* @param e
*/
private void updateRollover(ComponentEvent e) {
Point componentLocation = null;
try {
componentLocation = e.getComponent().getMousePosition();
} catch (ClassCastException ignore) {
// caused by core issue on Mac/Java 7
//try to work our way there by a different path
//*sigh* this requires permissions
//if this fails, rollover is effectively neutered
Window w = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
if (w != null && isDescendingFrom(e.getComponent(), w)) {
try {
PointerInfo pi = java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
@Override
public PointerInfo run() {
return MouseInfo.getPointerInfo();
}
}
);
componentLocation = pi.getLocation();
convertPointFromScreen(componentLocation, e.getComponent());
} catch (SecurityException se) { }
}
}
if (componentLocation == null) {
componentLocation = new Point(-1, -1);
}
// LOG.info("" + componentLocation + " / " + e);
updateRolloverPoint((JComponent) e.getComponent(), componentLocation);
updateClientProperty((JComponent) e.getComponent(), ROLLOVER_KEY, true);
}
@Override
public void componentHidden(ComponentEvent e) {
}
//---------------- mapping methods
/**
* Controls the mapping of the given mouse event to a client property. This
* implementation first calls updateRolloverPoint to convert the mouse coordinates.
* Then calls updateClientProperty to actually set the client property in the
*
* @param e the MouseEvent to map to client coordinates
* @param property the client property to map to
* @param fireAlways a flag indicating whether a client event should be fired if unchanged.
*
* @see #updateRolloverPoint(JComponent, Point)
* @see #updateClientProperty(JComponent, String, boolean)
*/
protected void updateRollover(MouseEvent e, String property,
boolean fireAlways) {
updateRolloverPoint((JComponent) e.getComponent(), e.getPoint());
updateClientProperty((JComponent) e.getComponent(), property, fireAlways);
}
/** Current mouse location in client coordinates. */
protected Point rollover = new Point(-1, -1);
/**
* Sets the given client property to the value of current mouse location in
* client coordinates. If fireAlways, the property is force to fire a change.
*
* @param component the target component
* @param property the client property to set
* @param fireAlways a flag indicating whether a client property
* should be forced to fire an event.
*/
protected void updateClientProperty(JComponent component, String property,
boolean fireAlways) {
if (fireAlways) {
// fix Issue #864-swingx: force propertyChangeEvent
component.putClientProperty(property, null);
component.putClientProperty(property, new Point(rollover));
} else {
Point p = (Point) component.getClientProperty(property);
if (p == null || (rollover.x != p.x) || (rollover.y != p.y)) {
component.putClientProperty(property, new Point(rollover));
}
}
}
/**
* Subclasses must implement to map the given mouse coordinates into
* appropriate client coordinates. The result must be stored in the
* rollover field.
*
* @param component the target component which received a mouse event
* @param mousePoint the mouse position of the event, coordinates are
* component pixels
*/
protected abstract void updateRolloverPoint(JComponent component, Point mousePoint);
}