// ********************************************************************** // // <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/MouseDelegator.java,v $ // $RCSfile: MouseDelegator.java,v $ // $Revision: 1.7 $ // $Date: 2005/12/16 14:14:02 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyVetoException; import java.beans.VetoableChangeListener; import java.beans.beancontext.BeanContext; import java.beans.beancontext.BeanContextChild; import java.beans.beancontext.BeanContextChildSupport; import java.beans.beancontext.BeanContextMembershipEvent; import java.beans.beancontext.BeanContextMembershipListener; import java.util.Iterator; import java.util.Vector; import com.bbn.openmap.event.MapMouseListener; import com.bbn.openmap.event.MapMouseMode; import com.bbn.openmap.event.NavMouseMode; import com.bbn.openmap.event.NullMouseMode; import com.bbn.openmap.event.ProjectionListener; import com.bbn.openmap.event.SelectMouseMode; import com.bbn.openmap.util.Debug; /** * The MouseDelegator manages the MapMouseModes that handle MouseEvents on the * map. There should only be one MouseDelegator within a MapHandler. * * @see com.bbn.openmap.event.MapMouseMode * @see com.bbn.openmap.event.AbstractMouseMode * @see com.bbn.openmap.event.NavMouseMode * @see com.bbn.openmap.event.SelectMouseMode */ public class MouseDelegator implements PropertyChangeListener, java.io.Serializable, BeanContextChild, BeanContextMembershipListener, SoloMapComponent { private static final long serialVersionUID = 1L; public final static transient String ActiveModeProperty = "NewActiveMouseMode"; public final static transient String MouseModesProperty = "NewListOfMouseModes"; /** * A property string used when firing PropertyChangeSupport notifications * when the mouse mode is acting as proxy for another mouse mode. */ public static final String ProxyMouseModeProperty = "MouseModeProxy"; /** * The active MapMouseMode. */ protected transient MapMouseMode activeMouseMode = null; /** * The registered MapMouseModes. */ protected transient Vector<MapMouseMode> mouseModes = new Vector<MapMouseMode>(0); /** * The MapBean. */ protected transient MapBean map; /** * Need to keep a safe copy of the current layers that are part of the * MapBean in case a MouseMode gets added before the MapBean is set in the * MouseDelegator. Without this, you can get into a situation where new * MapMouseModes don't know about layers until the MouseDelegator receives a * property change event from the MapBean. */ protected Layer[] currentLayers = null; /** * PropertyChangeSupport for handling listeners. */ protected PropertyChangeSupport pcSupport = new PropertyChangeSupport(this); /** * BeanContextChildSupport object provides helper functions for * BeanContextChild interface. */ protected BeanContextChildSupport beanContextChildSupport = new BeanContextChildSupport(); /** * Construct a MouseDelegator with an associated MapBean. * * @param map MapBean */ public MouseDelegator(MapBean map) { setMap(map); } /** * Construct a MouseDelegator without an associated MapBean. You will need to * set the MapBean via <code>setMap()</code>. * * @see #setMap */ public MouseDelegator() { this(null); } /** * Set the associated MapBean. * * @param mapbean MapBean */ public void setMap(MapBean mapbean) { if (map != null) { map.removePropertyChangeListener(this); setInactive(activeMouseMode); } map = mapbean; if (map != null) { map.addPropertyChangeListener(this); setActive(activeMouseMode); } } /** * Get the associated MapBean. * * @return MapBean */ public MapBean getMap() { return map; } // ---------------------------------------------------------------------- // // Mouse Event handling support // // ---------------------------------------------------------------------- /** * Returns the ID string for the active Mouse Mode. * * @return String ID of the active mouse mode. */ public String getActiveMouseModeID() { if (activeMouseMode != null) return activeMouseMode.getID(); else return null; } /** * Sets the mouse mode to the mode with the same ID string. If none of the * MouseEventDelagates have a matching ID string, the mode is not changed. <br> * The map mouse cursor is set to the recommended cursor retrieved from the * active mouseMode. * * @param MouseModeID the string ID of the mode to set active. */ public void setActiveMouseModeWithID(String MouseModeID) { if (MouseModeID == null) { Debug.error("MouseDelegator:setActiveMouseModeWithID() - null value"); return; } MapMouseMode oldActive = activeMouseMode; setInactive(activeMouseMode); for (MapMouseMode med : mouseModes) { if (MouseModeID.equals(med.getID())) { setActive(med); if (Debug.debugging("mousemode")) { Debug.output("MouseDelegator.setActiveMouseModeWithID() setting new mode to mode " + med.getID()); } break; } } firePropertyChange(ActiveModeProperty, oldActive, activeMouseMode); } /** * Returns the mouse mode delegate that is active at the moment. * * @return MapMouseMode the active mouse mode */ public MapMouseMode getActiveMouseMode() { return activeMouseMode; } /** * Sets the active mouse mode. If the MapMouseMode is not a member of the * current mouse modes, it is added to the list. <br> * The map mouse cursor is set to the recommended cursor retrieved from the * active mouseMode. * * @param aMed a MapMouseMode to make active. */ public void setActiveMouseMode(MapMouseMode aMed) { if (aMed == null) { Debug.error("MouseDelegator:setActiveMouseMode() - null value"); return; } MapMouseMode oldActive = activeMouseMode; boolean isAlreadyAMode = false; for (MapMouseMode med : mouseModes) { // Need to go through the modes, turn off the other active // mode, and turn on this one. if (aMed.getID().equals(med.getID())) { isAlreadyAMode = true; } } if (!isAlreadyAMode) { addMouseMode(aMed); } setActive(aMed); firePropertyChange(ActiveModeProperty, oldActive, activeMouseMode); } /** * Returns an array of MapMouseModes that are available to the MapBean. * * @return an array of MapMouseModes. */ public MapMouseMode[] getMouseModes() { int nMouseModes = mouseModes.size(); if (nMouseModes == 0) return (new MapMouseMode[0]); MapMouseMode[] result = new MapMouseMode[nMouseModes]; for (int i = 0; i < nMouseModes; i++) { result[i] = (MapMouseMode) mouseModes.elementAt(i); } return result; } /** * Used to set the mouseModes available to the MapBean. The Delegator drops * all references to any mouseModes it knew about previously. It also sets * the index of the array to be the active mouse mode. <br> * The map mouse cursor is set to the recommended cursor retrieved from the * active mouseMode. * * @param meds an array of MapMouseModes * @param activeIndex which mouse mode to make active */ public void setMouseModes(MapMouseMode[] meds, int activeIndex) { mouseModes.clear(); MapMouseMode oldActive = activeMouseMode; for (int i = 0; i < meds.length; i++) { mouseModes.add(meds[i]); if (i == activeIndex) { // activate the right mode setActive(meds[i]); } } firePropertyChange(MouseModesProperty, null, mouseModes); firePropertyChange(ActiveModeProperty, oldActive, activeMouseMode); } /** * Used to set the mouseModes available to the MapBean. The MapBean drops all * references to any mouseModes it knew about previously. The meds[0] mode is * made active, by default. * * @param meds an array of MapMouseModes */ public void setMouseModes(MapMouseMode[] meds) { setMouseModes(meds, 0); } /** * Adds a MapMouseMode to the MouseMode list. Does not make it the active * mode. * * @param med the MouseEvent Delegate to add. */ public void addMouseMode(MapMouseMode med) { if (med != null) { mouseModes.addElement(med); // All of the MouseModes will think they are active, but // the Delegator will only pass events to the one it // thinks is... if (mouseModes.size() == 1) { setActive(med); } if (currentLayers != null) { setupMouseModeWithLayers(med, currentLayers); } firePropertyChange(MouseModesProperty, null, mouseModes); } } /** * Removes a particular MapMouseMode from the MouseMode list. * * @param med the MapMouseMode that should be removed. */ public void removeMouseMode(MapMouseMode med) { boolean needToAdjustActiveMode = false; if (med == null) { return; } if (med.equals(activeMouseMode)) { needToAdjustActiveMode = true; setInactive(med); } for (MapMouseMode checkMM : mouseModes) { if (med.equals(checkMM)) { med.removeAllMapMouseListeners(); } // Set the first mode to the active one, if deleting the // active one. else if (needToAdjustActiveMode) { setActive(checkMM); needToAdjustActiveMode = false; } } mouseModes.remove(med); firePropertyChange(MouseModesProperty, null, mouseModes); } /** * Removes a particular MapMouseMode from the MouseMode list, with the ID * given. * * @param id the ID of the MapMouseMode that should be removed */ public void removeMouseMode(String id) { for (MapMouseMode med : mouseModes) { if (id.equals(med.getID())) { removeMouseMode(med); break; } } } /** * Sets the three default OpenMap mouse modes. These modes are: NavMouseMode * (Map Navigation), the SelectMouseMode (MouseEvents go to Layers), and * NullMouseMode (MouseEvents are ignored). */ public void setDefaultMouseModes() { MapMouseMode[] modes = new MapMouseMode[3]; modes[0] = new NavMouseMode(true); modes[1] = new SelectMouseMode(true); modes[2] = new NullMouseMode(); setMouseModes(modes); } /** * PropertyChangeListenter Interface method. * * @param evt PropertyChangeEvent */ public void propertyChange(PropertyChangeEvent evt) { String property = evt.getPropertyName(); if (property == MapBean.LayersProperty) { // make a safe copy of the layers that are part of the // MapBean Layer[] layers = (Layer[]) evt.getNewValue(); currentLayers = new Layer[layers.length]; System.arraycopy(layers, 0, currentLayers, 0, layers.length); setupMouseModesWithLayers(currentLayers); } if (property.equals(ProxyMouseModeProperty)) { Object newObj = evt.getNewValue(); if (newObj instanceof MapMouseMode) { map.setCursor(((MapMouseMode) newObj).getModeCursor()); } else { map.setCursor(getActiveMouseMode().getModeCursor()); } firePropertyChange(ProxyMouseModeProperty, evt.getOldValue(), newObj); } } /** * Does the work putting the layers given on each mouse mode's list of layers * to notify if it becomes active. */ public void setupMouseModesWithLayers(Layer[] layers) { for (int j = 0; j < mouseModes.size(); j++) { // Clear out the old listeners first MapMouseMode mmm = (MapMouseMode) mouseModes.elementAt(j); setupMouseModeWithLayers(mmm, layers); } } /** * Gives a MapMouseMode access to a Layer[], and it will find the layers that * want to listen to it and will forward events to them if it is added to the * MapBean as a MouseListener or a MouseMotionListener. * * @param mmm MapMouseMode * @param layers Layer[] */ public void setupMouseModeWithLayers(MapMouseMode mmm, Layer[] layers) { mmm.removeAllMapMouseListeners(); for (int i = 0; i < layers.length; i++) { // Add the listeners from each layer to the mouse mode. MapMouseListener tempmml = null; if (layers[i] != null) { tempmml = layers[i].getMapMouseListener(); } if (tempmml == null) { continue; } String[] services = tempmml.getMouseModeServiceList(); if (services != null) { for (int k = 0; k < services.length; k++) { if (mmm.getID().equals(services[k])) { mmm.addMapMouseListener(tempmml); if (Debug.debugging("mousemode")) { Debug.output("MouseDelegator.setupMouseModeWithLayers():" + " layer = " + layers[i].getName() + " service = " + mmm.getID()); } break; } } } } } /** * Set the active MapMouseMode. This sets the MapMouseMode of the associated * MapBean. * * @param mm MapMouseMode */ public void setActive(MapMouseMode mm) { if (activeMouseMode != null) { setInactive(activeMouseMode); } activeMouseMode = mm; if (map != null && activeMouseMode != null) { if (Debug.debugging("mousemode")) { Debug.output("MouseDelegator.setActive(): " + mm.getID()); } map.addMouseListener(mm); map.addMouseMotionListener(mm); map.addMouseWheelListener(mm); map.addPaintListener(mm); map.setCursor(activeMouseMode.getModeCursor()); if (mm instanceof ProjectionListener) { map.addProjectionListener((ProjectionListener) mm); } activeMouseMode.setActive(true); activeMouseMode.addPropertyChangeListener(this); } } /** * Deactivate the MapMouseMode. * * @param mm MapMouseMode. */ public void setInactive(MapMouseMode mm) { if (map != null) { map.removeMouseListener(mm); map.removeMouseMotionListener(mm); map.removeMouseWheelListener(mm); map.removePaintListener(mm); // should set map's cursor to some default value?? if (mm instanceof ProjectionListener) { map.removeProjectionListener((ProjectionListener) mm); } } if (activeMouseMode == mm) { activeMouseMode = null; } if (mm != null) { mm.setActive(false); mm.removePropertyChangeListener(this); } } /** * Eventually gets called when the MouseDelegator is added to the * BeanContext, and when other objects are added to the BeanContext anytime * after that. The MouseDelegator looks for a MapBean to manage MouseEvents * for, and MouseModes to use to manage those events. If a MapBean is added * to the BeanContext while another already is in use, the second MapBean * will take the place of the first. * * @param it iterator to use to go through the new objects in the * BeanContext. */ public void findAndInit(Iterator<?> it) { while (it.hasNext()) { findAndInit(it.next()); } } /** * Called when an object should be evaluated by the MouseDelegator to see if * it is needed. */ public void findAndInit(Object someObj) { if (someObj instanceof MapBean) { Debug.message("mousedelegator", "MouseDelegator found a map."); setMap((MapBean) someObj); } if (someObj instanceof MapMouseMode) { Debug.message("mousedelegator", "MouseDelegator found a MapMouseMode."); addMouseMode((MapMouseMode) someObj); } } /** * BeanContextMembershipListener method. Called when new objects are added to * the parent BeanContext. * * @param bcme event that contains an iterator that can be used to go through * the new objects. */ public void childrenAdded(BeanContextMembershipEvent bcme) { findAndInit(bcme.iterator()); } /** * BeanContextMembershipListener method. Called when objects have been * removed from the parent BeanContext. The MouseDelegator looks for the * MapBean it is managing MouseEvents for, and any MouseModes that may be * removed. * * @param bcme event that contains an iterator that can be used to go through * the removed objects. */ public void childrenRemoved(BeanContextMembershipEvent bcme) { Iterator<?> it = bcme.iterator(); while (it.hasNext()) { findAndUndo(it.next()); } } /** * Called by childrenRemoved. */ public void findAndUndo(Object someObj) { if (someObj instanceof MapBean) { if (getMap() == (MapBean) someObj) { Debug.message("mousedelegator", "MouseDelegator: removing the map."); setMap(null); } } if (someObj instanceof MapMouseMode) { Debug.message("mousedelegator", "MouseDelegator: removing a MapMouseMode."); removeMouseMode((MapMouseMode) someObj); } if (someObj == this) { dispose(); } } public void dispose() { if (mouseModes != null) { mouseModes.clear(); } currentLayers = null; } /** Method for BeanContextChild interface. */ public BeanContext getBeanContext() { return beanContextChildSupport.getBeanContext(); } /** Method for BeanContextChild interface. */ public void setBeanContext(BeanContext in_bc) throws PropertyVetoException { if (in_bc != null) { in_bc.addBeanContextMembershipListener(this); beanContextChildSupport.setBeanContext(in_bc); findAndInit(in_bc.iterator()); } } /** Method for BeanContextChild interface. */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener in_pcl) { pcSupport.addPropertyChangeListener(propertyName, in_pcl); } /** Method for BeanContextChild interface. */ public void removePropertyChangeListener(String propertyName, PropertyChangeListener in_pcl) { pcSupport.removePropertyChangeListener(propertyName, in_pcl); } public void addPropertyChangeListener(PropertyChangeListener listener) { // This function is why we don't use the // BeanContextChildSupport PropertyChangeSupport object. pcSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { // This function is why we don't use the // BeanContextChildSupport PropertyChangeSupport object. pcSupport.removePropertyChangeListener(listener); } public void firePropertyChange(String property, Object oldObj, Object newObj) { pcSupport.firePropertyChange(property, oldObj, newObj); } /** Method for BeanContextChild interface. */ public void addVetoableChangeListener(String propertyName, VetoableChangeListener in_vcl) { beanContextChildSupport.addVetoableChangeListener(propertyName, in_vcl); } /** Method for BeanContextChild interface. */ public void removeVetoableChangeListener(String propertyName, VetoableChangeListener in_vcl) { beanContextChildSupport.removeVetoableChangeListener(propertyName, in_vcl); } /** * Report a vetoable property update to any registered listeners. If anyone * vetos the change, then fire a new event reverting everyone to the old * value and then rethrow the PropertyVetoException. * <P> * * No event is fired if old and new are equal and non-null. * <P> * * @param name The programmatic name of the property that is about to change * * @param oldValue The old value of the property * @param newValue - The new value of the property * * @throws PropertyVetoException if the recipient wishes the property change * to be rolled back. */ public void fireVetoableChange(String name, Object oldValue, Object newValue) throws PropertyVetoException { beanContextChildSupport.fireVetoableChange(name, oldValue, newValue); } }