/**
* MouseEventRouter.java
* Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.expeditee.gio.swing;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Container;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JMenuBar;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import org.expeditee.gio.gesture.StandardGestureActions;
import org.expeditee.gui.DisplayController;
import org.expeditee.gui.FrameGraphics;
import org.expeditee.gui.FrameUtils;
import org.expeditee.gui.FreeItems;
import org.expeditee.gui.PopupManager;
import org.expeditee.items.Item;
/**
* The gateway for mouse input; conditionally forwards mouse messages to swing
* components / expeditee frames for the Browser.
*
* Various mouse listeners can attach themselves here to listen to mouse events
* forwarded to expeditee .. this excludes widget-exclusive mouse messages.
*
* @author Brook Novak
*
*/
public class MouseEventRouter extends JComponent {
private static final long serialVersionUID = 1L;
private JMenuBar _menuBar;
private Container _contentPane;
private List _mouseListeners = new LinkedList();
private List _mouseMotionListeners = new LinkedList();
private List _mouseWheelListeners = new LinkedList();
private static MouseEvent _currentMouseEvent = null;
/**
* Routes events on given frame layers... the menu bar and content pane
*
* @param menuBar
* Must not be null.
*
* @param contentPane
* Must not be null.
*/
public MouseEventRouter(JMenuBar menuBar, Container contentPane)
{
if (contentPane == null) throw new NullPointerException("contentPane");
// Listen for all AWT events (ensures popups are included)
Toolkit.getDefaultToolkit().addAWTEventListener(new EventCatcher(),
AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK);
this._menuBar = menuBar;
this._contentPane = contentPane;
}
/**
* Listens only to events to frames... i.e. to expeditee, not to widgets.
*
* @param listener
* The listener to add.
*/
public void addExpediteeMouseListener(MouseListener listener)
{
if (listener == null) throw new NullPointerException("listener");
_mouseListeners.add(listener);
}
public void removeExpediteeMouseListener(MouseListener listener)
{
if (listener == null) throw new NullPointerException("listener");
_mouseListeners.remove(listener);
}
public void addExpediteeMouseMotionListener(MouseMotionListener listener)
{
if (listener == null) throw new NullPointerException("listener");
_mouseMotionListeners.add(listener);
}
public void removeExpediteeMouseMotionListener(MouseMotionListener listener)
{
if (listener == null) throw new NullPointerException("listener");
_mouseMotionListeners.remove(listener);
}
public void addExpediteeMouseWheelListener(MouseWheelListener listener)
{
if (listener == null) throw new NullPointerException("listener");
_mouseWheelListeners.add(listener);
}
public void removeExpediteeMouseWheelListener(MouseWheelListener listener)
{
if (listener == null) throw new NullPointerException("listener");
_mouseWheelListeners.remove(listener);
}
/**
* Conceal event catching from outside
*
* @author Brook Novak
*/
private class EventCatcher implements AWTEventListener
{
/**
* All events for every component in frame are fired to here
*/
public void eventDispatched(AWTEvent event) {
if (event instanceof MouseEvent) {
routeMouseEvent((MouseEvent) event);
}
}
}
/**
* Forwards the mouse event to its appropriate destination. For events that
* belong to Expeditee space the events are consumed and manually routed to
* the mouse actions handler.
*
* @param e
* The mouse event for any component in the browser frame
*/
private void routeMouseEvent(MouseEvent e)
{
_currentMouseEvent = e;
// First convert the point to expeditee space
Point containerPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), _contentPane);
// TODO: Find a reliable way of detecting when the mouse moved onto a window that isn't a child of ours
if(e.getID() == MouseEvent.MOUSE_EXITED) {
// System.out.println(e.getComponent());
if(containerPoint.x <= 0 || containerPoint.x >= _contentPane.getWidth() ||
containerPoint.y <= 0 || containerPoint.y >= _contentPane.getHeight())
{
//FrameGestureActions.mouseExitedWindow(e);
}
}
if (containerPoint.y < 0 && e.getID() != MouseEvent.MOUSE_EXITED) { // not in the content pane
if (_menuBar != null && containerPoint.y + _menuBar.getHeight() >= 0) {
// The mouse event is over the menu bar.
// Could handle specially.
} else {
// The mouse event is over non-system window
// decorations, such as the ones provided by
// the Java look and feel.
// Could handle specially.
}
} else {
// Check to see if the mouse is over an expeditee item or
// whether an expeditee item is currently picked up
boolean forwardToExpeditee = false;
boolean isOverPopup = PopupManager.getInstance().isPointOverPopup(SwingConversions.fromSwingPoint(containerPoint));
if (isOverPopup) {
// Popups have highest preference
// forwardToExpiditee = false...
// If there are items in free space - keep them moving with the
// cursor over the
// popups.
if (!FreeItems.getInstance().isEmpty()) {
StandardGestureActions.move(FreeItems.getInstance(), SwingConversions.fromSwingPoint(containerPoint));
}
// Note: all frame.content pane events belong to expeditee
} else if (e.getSource() == _contentPane || e.getSource() == SwingMiscManager.getIfUsingSwingGraphicsManager().getJFrame() || !FreeItems.getInstance().isEmpty())
{
forwardToExpeditee = true;
} else if (DisplayController.getCurrentFrame() != null) {
/* is mouse over a specific expeditee item? */
// NOTE: Do not use FrameUtils.getCurrentItem() - thats relevent
// for old mouse position only
/*
* for (Item i : DisplayIO.getCurrentFrame().getItems()) { if
* (i.getPolygon().contains(containerPoint)) {
* forwardToExpiditee = true; break; } }
*/// ABOVE: Does not consider overlays
// NOTE: Below is an expensive operation and could be re-used
// when passing mouse events!!!
forwardToExpeditee = (FrameUtils.onItem(DisplayController.getCurrentFrame(), containerPoint.x, containerPoint.y, true) != null);
} else {
forwardToExpeditee = false;
}
// Create artificial mouse event and forward it to expeditee
MouseEvent expediteeEvent = SwingUtilities.convertMouseEvent(e.getComponent(), e, _contentPane);
// NOTE: Convert util masks-out the needed extensions
MouseEvent withExtensions = null;
if (forwardToExpeditee) {
// Ensure that underlying widgets don't get the event
e.consume();
switch (e.getID()) {
case MouseEvent.MOUSE_MOVED:
for (MouseMotionListener listener : _mouseMotionListeners) {
listener.mouseMoved(expediteeEvent);
}
// Ensure that expeditee has focus only if no pop-ups exist
SwingGraphicsManager g = SwingMiscManager.getIfUsingSwingGraphicsManager();
if (g.getContentPane() != null) {
if (!g.getContentPane().isFocusOwner() && !isPopupVisible()) {
g.getContentPane().requestFocus();
}
}
break;
case MouseEvent.MOUSE_CLICKED:
withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
for (MouseListener listener : _mouseListeners) {
listener.mouseClicked(withExtensions);
}
break;
case MouseEvent.MOUSE_PRESSED:
withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
for (MouseListener listener : _mouseListeners) {
listener.mousePressed(withExtensions);
}
break;
case MouseEvent.MOUSE_RELEASED:
withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
for (MouseListener listener : _mouseListeners) {
listener.mouseReleased(withExtensions);
}
break;
case MouseEvent.MOUSE_WHEEL:
MouseWheelEvent mwe = (MouseWheelEvent) expediteeEvent;
for (MouseWheelListener listener : _mouseWheelListeners) {
listener.mouseWheelMoved(mwe);
}
break;
case MouseEvent.MOUSE_ENTERED:
withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
for (MouseListener listener : _mouseListeners) {
listener.mouseEntered(withExtensions);
}
break;
case MouseEvent.MOUSE_EXITED:
withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
for (MouseListener listener : _mouseListeners) {
listener.mouseExited(withExtensions);
}
break;
case MouseEvent.MOUSE_DRAGGED:
for (MouseMotionListener listener : _mouseMotionListeners) {
listener.mouseDragged(expediteeEvent);
}
break;
}
} else {
// Keep expeditees mouse X/Y updated
//FrameMouseActions.MouseX = expediteeEvent.getX();
//FrameMouseActions.MouseY = expediteeEvent.getY();
// If forwarding to swing ensure that widgets are not highlighted
// to give visual feedback to users such that swing has focus.
Item i = StandardGestureActions.getlastHighlightedItem();
if (i != null && i.getHighlightMode() != Item.HighlightMode.None) {
FrameGraphics.changeHighlightMode(i, Item.HighlightMode.None);
}
// Also bring expeditee behaviour to swing: Auto-focus on component
// whenever the mouse moves over it.
if (e.getID() == MouseEvent.MOUSE_MOVED) {
if (e.getSource() instanceof Component) {
Component target = (Component) e.getSource();
if (!target.isFocusOwner()) {
target.requestFocus();
}
}
// Auto-hide pop-ups when user click on something other
// than a pop-up - and is not on a pop-up invoker
} /*else if (!isOverPopup // TODO: Reinstate. cts16
&& e.getID() == MouseEvent.MOUSE_PRESSED
&& !PopupManager.getInstance().isInvoker(e.getComponent()))
{
PopupManager.getInstance().hideAutohidePopups();
}*/
}
}
}
public static boolean isPopupVisible()
{
return isPopupVisible(SwingMiscManager.getIfUsingSwingGraphicsManager().getLayeredPane());
}
private static boolean isPopupVisible(Container parent)
{
for (Component c : parent.getComponents()) {
if (c instanceof JPopupMenu && ((JPopupMenu) c).isVisible()) {
return true;
} else if (c instanceof Container && c != SwingMiscManager.getIfUsingSwingGraphicsManager().getContentPane()) {
if (isPopupVisible((Container) c))
return true;
}
}
return false;
}
private MouseEvent duplicateMouseEvent(MouseEvent e, int modifiers)
{
return new MouseEvent(e.getComponent(), e.getID(), e.getWhen(),
modifiers, e.getX(), e.getY(),
/** The below methods are not compatible with Java 1.5 */
/*
* e.getXOnScreen(), e.getYOnScreen(),
*/
e.getClickCount(), e.isPopupTrigger(), e.getButton());
}
public static MouseEvent getCurrentMouseEvent()
{
return _currentMouseEvent;
}
}