// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/event/AbstractMouseMode.java,v $
// $RCSfile: AbstractMouseMode.java,v $
// $Revision: 1.15 $
// $Date: 2008/10/16 19:33:09 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.event;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.util.Properties;
import java.util.logging.Logger;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.Timer;
import com.bbn.openmap.I18n;
import com.bbn.openmap.Layer;
import com.bbn.openmap.MapBean;
import com.bbn.openmap.MouseDelegator;
import com.bbn.openmap.OMComponent;
import com.bbn.openmap.util.PropUtils;
import com.bbn.openmap.util.propertyEditor.OptionPropertyEditor;
/**
* Base class of the MouseModes. It takes care of the administrative aspects of
* being a mouse mode, but does not respond to MouseEvents.
* <p>
* The ID and pretty name can be set in the properties file.
*
* <pre>
*
*
* # Name that layers use to get events from this mode
* mousemode.id=ID
* # Tooltip and Menu name for mode
* mousemode.prettyName=Display Name
*
*
*
*
* </pre>
*
* This class delegates much of the work of managing its listeners to a
* MapMouseSupport object.
*
* @see MapMouseSupport
*/
public class AbstractMouseMode
extends OMComponent
implements MapMouseMode, Serializable {
private static final long serialVersionUID = 1L;
protected static Logger logger = Logger.getLogger("com.bbn.openmap.event.MapMouseMode");
/**
* The identifier for the mode, which is also the name that will be used in a
* used interface describing the mode to a user.
*/
protected String ID = null;
/**
* The object used to handle the listeners and to pass out the event to the
* layers interested in it.
*/
protected MapMouseSupport mouseSupport;
/**
* The cursor that appears on the map when this Mouse Mode is active.
*/
protected Cursor cursor = Cursor.getDefaultCursor();
/**
* The Icon that can be used in a GUI. Can be null. The class will look for a
* resource gif file that has the same ID string - Navigation.gif for the
* NavMouseMode, for instance.
*/
protected transient Icon guiIcon = null;
protected transient boolean visible = true;
protected boolean mouseWheelListener = true;
protected boolean noMouseWheelListenerTimer = false;
protected String prettyName;
protected String iconName;
protected boolean zoomWhenMouseWheelUp = ZOOM_IN;
protected PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
/**
* Zoom direction in when mouse wheel rotated up.
*/
public static final boolean ZOOM_IN = true;
/**
* Zoom direction out when mouse wheel rotated up.
*/
public static final boolean ZOOM_OUT = false;
/**
* The MouseModeID to use for a particular instance of a MapMouseMode. If not
* set, the default mouse mode ID of the MapMouseMode will be used.
*/
public static final String IDProperty = "id";
/**
* The String to use for a key lookup in a Properties object to find the name
* to use in a GUI relating to this Mouse Mode.
*/
public static final String PrettyNameProperty = "prettyName";
/**
* The java.awt.Cursor id that should be used for the mouse mode.
*
* @see java.awt.Cursor
*/
public static final String CursorIDProperty = "cursorID";
/**
* A property that lets you specify the resource to use for the icon for the
* MouseMode.
*/
public static final String IconProperty = "icon";
/**
* A property that lets you specify if the mode zooms in or out when the
* mouse wheel is rotated up. Appropriate values are ZOOM_IN or ZOOM_OUT.
*/
public static final String MouseWheelZoomProperty = "mouseWheelUp";
/**
* A property that lets you turn off the mouse wheel listening functionality.
* If enabled, the mouse wheel changes the scale of the map.
*/
public static final String MouseWheelListenerProperty = "mouseWheelListener";
/**
* A property that lets you turn off the mouse wheel timer.
* If disabled, a timer is used for dealing with the mouse wheel changes.
*/
public static final String NoMouseWheelListenerTimerProperty = "noMouseWheelListenerTimer";
/**
* A property that lets you set the wait interval before a mouse wheel event
* gets triggered.
*/
public static final String MouseWheelTimerIntervalProperty = "mouseWheelTimerInterval";
/**
* Construct an AbstractMouseMode. Default constructor, allocates the mouse
* support object.
*/
public AbstractMouseMode() {
this("Unnamed Mode", true);
}
/**
* Construct an AbstractMouseMode.
*
* @param name the ID of the mode.
* @param shouldConsumeEvents if true, events are propagated to the first
* MapMouseListener that successfully processes the event, if false,
* events are propagated to all MapMouseListeners
*/
public AbstractMouseMode(String name, boolean shouldConsumeEvents) {
mouseSupport = new MapMouseSupport(this, shouldConsumeEvents);
ID = name;
setIconName(name + ".gif");
}
/**
* Internal callback method that lets subclasses override a class to use as a
* resource point for icon image retrieval.
*
* @return Class that has icon image file next to it in classpath.
*/
protected Class<?> getClassToUseForIconRetrieval() {
return getClass();
}
/**
* Sets the GUI icon based on the name of the resource provided. The resource
* will be checked against the classpath, and if it isn't found, the mouse
* mode will be asked for the class to use for icon retrieval.
*
* @param iName
*/
public void setIconName(String iName) {
iconName = iName;
java.net.URL url = null;
try {
url = PropUtils.getResourceOrFileOrURL(iName);
} catch (MalformedURLException murle) {
}
if (url == null) {
url = getClassToUseForIconRetrieval().getResource(iconName);
}
if (url != null) {
guiIcon = new ImageIcon(url);
}
}
public String getIconName() {
return iconName;
}
/**
* Returns the id (mode name).
*
* @return String ID
*/
public String getID() {
return ID;
}
/**
* Set the id (mode name).
*
* @param id string that identifies the delegate.
*/
public void setID(String id) {
ID = id;
}
public void setPrettyName(String pn) {
prettyName = pn;
}
/**
* Return a pretty name, suitable for the GUI. If set, is independent of the
* mode ID. If not set, is the same as the mode ID.
*/
public String getPrettyName() {
if (prettyName == null) {
return i18n.get(this.getClass(), PrettyNameProperty, ID);
} else {
return prettyName;
}
}
/**
* Gets the mouse cursor recommended for use when this mouse mode is active.
*
* @return Cursor the mouse cursor recommended for use when this mouse mode
* is active.
*/
public Cursor getModeCursor() {
return cursor;
}
/**
* Sets the cursor that is recommended for use on the map when this mouse
* mode is active.
*
* @param curs the cursor that is recommended for use on the map when this
* mouse mode is active.
*/
public void setModeCursor(Cursor curs) {
cursor = curs;
}
/**
* Sets the cursor that is recommended for use on the map when this mouse
* mode is active.
*
* @param cursorID the cursor ID member variable string, i.e. DEFAULT_CURSOR
* @see java.awt.Cursor
*/
public void setModeCursor(String cursorID) {
if (cursorID != null) {
try {
int cid = java.awt.Cursor.class.getField(cursorID).getInt(null);
setModeCursor(Cursor.getPredefinedCursor(cid));
} catch (NoSuchFieldException nsfe) {
} catch (IllegalAccessException iae) {
}
}
}
/**
* Gets the Icon to represent the Mouse Mode in a GUI. May be null.
*/
public Icon getGUIIcon() {
return guiIcon;
}
/**
* Set the icon that should be used for this Mouse Mode in a GUI.
*/
public void setGUIIcon(Icon icon) {
guiIcon = icon;
}
/**
* Sets how the delegate passes out events. If the value passed in is true,
* the delegate will only pass the event to the first listener that can
* respond to the event. If false, the delegate will pass the event on to all
* its listeners.
*
* @param value true for limited distribution.
*/
public void setConsumeEvents(boolean value) {
mouseSupport.setConsumeEvents(value);
}
/**
* Returns how the delegate (and it's mouse support) is set up to distribute
* events.
*
* @return true if only one listener gets to act on an event.
*/
public boolean isConsumeEvents() {
return mouseSupport.isConsumeEvents();
}
public boolean isZoomWhenMouseWheelUp() {
return zoomWhenMouseWheelUp;
}
public void setZoomWhenMouseWheelUp(boolean zoomWhenMouseWheelUp) {
this.zoomWhenMouseWheelUp = zoomWhenMouseWheelUp;
}
/**
* Add a MapMouseListener to the MouseMode. The listener will then get events
* from the delegator if the delegator is active.
*
* @param l the MapMouseListener to add.
*/
public void addMapMouseListener(MapMouseListener l) {
mouseSupport.add(l);
}
/**
* Remove a MapMouseListener from the MouseMode.
*
* @param l the MapMouseListener to remove.
*/
public void removeMapMouseListener(MapMouseListener l) {
mouseSupport.remove(l);
}
/**
* Remove all MapMouseListeners from the mode.
*/
public void removeAllMapMouseListeners() {
mouseSupport.clear();
}
/**
* Invoked when the mouse has been clicked on a component. Calls
* fireMapMouseClicked on MouseSupport.
*
* @param e MouseEvent
*/
public void mouseClicked(MouseEvent e) {
mouseSupport.fireMapMouseClicked(e);
}
/**
* Invoked when a mouse button has been pressed on a component. Calls
* fiewMapMousePressed on the MouseSupport. Also requests focus on the source
* of the MouseEvent, so that key events can be processed.
*
* @param e MouseEvent
*/
public void mousePressed(MouseEvent e) {
e.getComponent().requestFocus();
mouseSupport.fireMapMousePressed(e);
}
/**
* Invoked when a mouse button has been released on a component. Calls
* fireMapMouseReleased on the MouseSupport.
*
* @param e MouseEvent
*/
public void mouseReleased(MouseEvent e) {
mouseSupport.fireMapMouseReleased(e);
}
/**
* Invoked when the mouse enters a component. Calls fireMapMouseEntered on
* the MouseSupport.
*
* @param e MouseEvent
*/
public void mouseEntered(MouseEvent e) {
mouseSupport.fireMapMouseEntered(e);
}
/**
* Invoked when the mouse exits a component. This does nothing. Extend this
* class to add functionality.
*
* @param e MouseEvent
*/
public void mouseExited(MouseEvent e) {
mouseSupport.fireMapMouseExited(e);
}
/**
* Invoked when a mouse button is pressed on a component and then dragged.
* Calls fireMapMouseDragged on the MouseSupport.
*
* @param e MouseEvent
*/
public void mouseDragged(MouseEvent e) {
mouseSupport.fireMapMouseDragged(e);
}
/**
* Invoked when the mouse button has been moved on a component (with no
* buttons no down). Calls fireMapMouseMoved on the MouseSupport.
*
* @param e MouseEvent
*/
public void mouseMoved(MouseEvent e) {
mouseSupport.fireMapMouseMoved(e);
}
/**
* Invoked from the MouseWheelListener interface.
*/
public void mouseWheelMoved(MouseWheelEvent e) {
if (mouseWheelListener) {
int rot = e.getWheelRotation();
if (e.getSource() instanceof MapBean) {
MapBean mb = (MapBean) e.getSource();
boolean direction = isZoomWhenMouseWheelUp();
float zoomIn = 1.1f;
float zoomOut = .9f;
float amount = zoomIn;
if ((direction && rot < 0) || (!direction && rot > 0)) {
amount = zoomOut;
}
if (noMouseWheelListenerTimer) {
updateMouseWheelMoved(mb, mb.getScale() * amount);
} else {
if (mouseTimer == null) {
mouseTimer = new Timer(mouseWheelTimerInterval, mouseWheelTimerListener);
mouseTimer.setRepeats(false);
}
mouseWheelTimerListener.addAmount(mb, amount);
mouseTimer.restart();
}
}
}
}
/**
* Invoked from the MouseWheelListener interface.
*/
public void updateMouseWheelMoved(MapBean mb, float value) {
if (mb != null) {
mb.zoom(new ZoomEvent(mb, ZoomEvent.ABSOLUTE, value));
}
}
/**
* Check setting for whether MouseMode responds to mouse wheel events.
*
* @return true if mouse mode is interested in mouse wheel events.
*/
public boolean isMouseWheelListener() {
return mouseWheelListener;
}
/**
* Set whether MouseMode responds to mouse wheel events.
*
* @param mouseWheelListener
*/
public void setMouseWheelListener(boolean mouseWheelListener) {
this.mouseWheelListener = mouseWheelListener;
}
/**
* Part of the MapMouseMode interface. Called when the MouseMode is made
* active or inactive.
*
* @param active true if the mode has been made active, false if it has been
* made inactive.
*/
public void setActive(boolean active) {
}
/**
* Set a MouseSupport explicitly.
*
* @param support The new MapMouseSupport instance
*/
public void setMouseSupport(MapMouseSupport support) {
mouseSupport = support;
}
/**
* Get the MouseSupport.
*
* @return the MapMouseSupport used by the MouseMode.
*/
public MapMouseSupport getMouseSupport() {
return mouseSupport;
}
/**
* Method to let the MouseDelegator know if the MapMouseMode should be
* visible, as opposed to a MapMouseMode that is being provided and
* controlled by another tool. True by default.
*/
public boolean isVisible() {
return visible;
}
/**
* Method to set if the MapMouseMode should be visible, as opposed to a
* MapMouseMode that is being provided and controlled by another tool.
*/
public void setVisible(boolean value) {
visible = value;
}
/**
* Request to have the parent MapMouseMode act as a proxy for a MapMouseMode
* that wants to remain hidden. Can be useful for directing events to one
* object. This version sets the proxy distribution mask to zero, which means
* that none of this support objects targets will be notified of events.
*
* @param mmm the hidden MapMouseMode for this MapMouseMode to send events
* to.
* @return true if the proxy setup (essentially a lock) is successful, false
* if the proxy is already set up for another listener.
*/
public boolean actAsProxyFor(MapMouseMode mmm) {
return actAsProxyFor(mmm, 0);
}
/**
* Request to have the MapMouseMode act as a proxy for a MapMouseMode that
* wants to remain hidden. Can be useful for directing events to one object.
*
* @param mmm the hidden MapMouseMode for this MapMouseMode to send events
* to.
* @param pdm the proxy distribution mask to use, which lets this support
* object notify its targets of events if the parent is acting as a
* proxy.
* @return true if the proxy setup (essentially a lock) is successful, false
* if the proxy is already set up for another listener.
*/
public boolean actAsProxyFor(MapMouseMode mmm, int pdm) {
MapMouseMode omm = mouseSupport.getProxied();
boolean ret = false;
if (mmm != null && !mmm.equals(omm)) {
ret = mouseSupport.setProxyFor(mmm, pdm);
propertyChangeSupport.firePropertyChange(MouseDelegator.ProxyMouseModeProperty, omm, mmm);
}
return ret;
}
/**
* Can check if the MapMouseMode is acting as a proxy for another
* MapMouseMode.
*/
public boolean isProxyFor(MapMouseMode mmm) {
return mouseSupport.isProxyFor(mmm);
}
/**
* Release the proxy lock on the MapMouseMode.
*/
public void releaseProxy() {
MapMouseMode mmm = mouseSupport.getProxied();
if (mmm != null) {
mouseSupport.releaseProxy();
propertyChangeSupport.firePropertyChange(MouseDelegator.ProxyMouseModeProperty, mmm, null);
}
}
/**
* Set the mask that dictates which events get sent to this support object's
* targets even if the parent mouse mode is acting as a proxy.
*/
public void setProxyDistributionMask(int mask) {
mouseSupport.setProxyDistributionMask(mask);
}
/**
* Returns the MapMouseMode being held inside this mouse mode.
*/
public MapMouseMode getProxied() {
return mouseSupport.getProxied();
}
/**
* Get the mask that dictates which events get sent to this support object's
* targets even if the parent mouse mode is acting as a proxy.
*/
public int getProxyDistributionMask() {
return mouseSupport.getProxyDistributionMask();
}
public void setProperties(String prefix, Properties props) {
super.setProperties(prefix, props);
prefix = PropUtils.getScopedPropertyPrefix(prefix);
String prettyNameString = props.getProperty(prefix + PrettyNameProperty);
if (prettyNameString != null) {
setPrettyName(prettyNameString);
}
String idString = props.getProperty(prefix + IDProperty);
if (idString != null) {
setID(idString);
}
setModeCursor(props.getProperty(prefix + CursorIDProperty));
String iconString = props.getProperty(prefix + IconProperty);
if (iconString != null) {
setIconName(iconString);
}
mouseWheelListener = PropUtils.booleanFromProperties(props, prefix + MouseWheelListenerProperty, mouseWheelListener);
zoomWhenMouseWheelUp = PropUtils.booleanFromProperties(props, prefix + MouseWheelZoomProperty, zoomWhenMouseWheelUp);
String zwmwu = props.getProperty(prefix + MouseWheelZoomProperty);
if (zwmwu != null) {
try {
boolean zSetting = getClass().getField(zwmwu).getBoolean(null);
setZoomWhenMouseWheelUp(zSetting);
} catch (NoSuchFieldException nsfe) {
} catch (IllegalAccessException iae) {
}
}
noMouseWheelListenerTimer = PropUtils.booleanFromProperties(props, prefix + NoMouseWheelListenerTimerProperty, noMouseWheelListenerTimer);
mouseWheelTimerInterval = PropUtils.intFromProperties(props, prefix + MouseWheelTimerIntervalProperty, mouseWheelTimerInterval);
}
public Properties getProperties(Properties props) {
props = super.getProperties(props);
String prefix = PropUtils.getScopedPropertyPrefix(this);
if (prettyName != null) {
props.put(prefix + PrettyNameProperty, prettyName);
}
props.put(prefix + IDProperty, getID());
int cursorType = getModeCursor().getType();
Field[] cFields = Cursor.class.getFields();
for (int i = 0; i < cFields.length; i++) {
Field f = cFields[i];
String name = f.getName();
if (name.endsWith("_CURSOR")) {
try {
int testType = f.getInt(null);
if (testType == cursorType) {
props.put(prefix + CursorIDProperty, name);
break;
}
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
}
}
if (zoomWhenMouseWheelUp) {
props.put(prefix + MouseWheelZoomProperty, "ZOOM_IN");
} else {
props.put(prefix + MouseWheelZoomProperty, "ZOOM_OUT");
}
props.put(prefix + MouseWheelListenerProperty, Boolean.toString(mouseWheelListener));
props.put(prefix + IconProperty, PropUtils.unnull(getIconName()));
props.put(prefix + NoMouseWheelListenerTimerProperty, Boolean.toString(noMouseWheelListenerTimer));
props.put(prefix + MouseWheelTimerIntervalProperty, Integer.toString(mouseWheelTimerInterval));
return props;
}
public Properties getPropertyInfo(Properties props) {
props = super.getPropertyInfo(props);
Class<?> thisClass = getClass();
String internString = i18n.get(thisClass, PrettyNameProperty, I18n.TOOLTIP, "Presentable name for Mouse Mode.");
props.put(Layer.AddToBeanContextProperty, internString);
internString = i18n.get(thisClass, PrettyNameProperty, "Name");
props.put(PrettyNameProperty + LabelEditorProperty, internString);
internString = i18n.get(thisClass, IDProperty, I18n.TOOLTIP, "Internal ID for Mouse Mode, used by Layers.");
props.put(Layer.AddToBeanContextProperty, internString);
internString = i18n.get(thisClass, IDProperty, "ID");
props.put(IDProperty + LabelEditorProperty, internString);
PropUtils.setI18NPropertyInfo(i18n, props, thisClass, IconProperty, "Icon", "Icon to use for mouse mode.", null);
PropUtils.setI18NPropertyInfo(i18n, props, thisClass, MouseWheelZoomProperty, "Mouse Wheel Zoom Direction",
"Action to take when the mouse wheel is rolled up.",
"com.bbn.openmap.util.propertyEditor.ComboBoxPropertyEditor");
props.put(MouseWheelZoomProperty + OptionPropertyEditor.ScopedOptionsProperty, "zoomin zoomout");
props.put(MouseWheelZoomProperty + ".zoomin", "ZOOM_IN");
props.put(MouseWheelZoomProperty + ".zoomout", "ZOOM_OUT");
PropUtils.setI18NPropertyInfo(i18n, props, thisClass, MouseWheelListenerProperty, "Mouse Wheel Zoom",
"Setting for whether mouse wheel controls map zoom",
"com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
PropUtils.setI18NPropertyInfo(i18n, props, thisClass, CursorIDProperty, "Cursor", "Cursor to use for this mouse mode.",
"com.bbn.openmap.util.propertyEditor.ComboBoxPropertyEditor");
PropUtils.setI18NPropertyInfo(i18n, props, thisClass, NoMouseWheelListenerTimerProperty, "No Mouse Wheel Listener Timer",
"Setting for whether a timer is used with the mouse wheel controller",
"com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
PropUtils.setI18NPropertyInfo(i18n, props, thisClass, MouseWheelTimerIntervalProperty, "Mouse Wheel Timer Interval",
"Setting for the wait interval for the mouse wheel timer",
null);
StringBuffer cOptions = new StringBuffer();
Field[] cFields = Cursor.class.getFields();
for (int i = 0; i < cFields.length; i++) {
Field f = cFields[i];
String name = f.getName();
if (name.endsWith("_CURSOR")) {
String cName = f.getName();
props.put(CursorIDProperty + "." + cName, cName);
cOptions.append(" ").append(cName);
}
}
props.put(CursorIDProperty + OptionPropertyEditor.ScopedOptionsProperty, cOptions.toString().trim());
return props;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
/**
* PaintListener interface, notifying the MouseMode that the MapBean has
* repainted itself. Useful if the MouseMode is drawing stuff.
*/
public void listenerPaint(Object source, Graphics g) {
}
public void setNoMouseWheelListener(boolean val) {
noMouseWheelListenerTimer = val;
}
/**
*
*/
public boolean getNoMouseWheelListener() {
return noMouseWheelListenerTimer;
}
/**
* The wait interval before a mouse wheel event gets triggered.
*/
protected int mouseWheelTimerInterval = 60;
/**
* Set the time interval that the mouse timer waits before calling
* upateMouseMoved. A negative number or zero will disable the timer.
*/
public void setMouseWheelTimerInterval(int interval) {
mouseWheelTimerInterval = interval;
if (mouseTimer != null) {
mouseTimer.setInitialDelay(mouseWheelTimerInterval);
}
}
public int getMouseWheelTimerInterval() {
return mouseWheelTimerInterval;
}
/**
* The timer used to track the wait interval.
*/
protected Timer mouseTimer = null;
/**
* The timer listener that calls updateMouseMoved.
*/
protected MouseWheelTimerListener mouseWheelTimerListener = new MouseWheelTimerListener();
/**
* The definition of the listener that calls updateMouseMoved when the timer
* goes off.
*/
protected class MouseWheelTimerListener
implements ActionListener {
float newScale = 0f;
MapBean mapBean;
public synchronized void addAmount(MapBean map, float amount) {
mapBean = map;
if (newScale == 0f) {
newScale = map.getScale() * amount;
} else {
newScale *= amount;
}
}
public synchronized void actionPerformed(ActionEvent ae) {
if (newScale != 0f) {
updateMouseWheelMoved(mapBean, newScale);
newScale = 0f;
}
}
}
public boolean isNoMouseWheelListenerTimer() {
return noMouseWheelListenerTimer;
}
public void setNoMouseWheelListenerTimer(boolean noMouseWheelListenerTimer) {
this.noMouseWheelListenerTimer = noMouseWheelListenerTimer;
}
}