/* * $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. <p> * * Note: this class assumes that the target component is of type JComponent.<p> * Note: this implementation is stateful, it can't be shared across different * instances of a target component.<p> * * * @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. <p> * * 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. <p> * * 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<PointerInfo>() { @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); }