// **********************************************************************
//
// <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/gui/AbstractOpenMapMenu.java,v $
// $RCSfile: AbstractOpenMapMenu.java,v $
// $Revision: 1.6.2.4 $
// $Date: 2006/02/13 17:02:03 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.gui;
import java.awt.Component;
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.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JSeparator;
import com.bbn.openmap.Environment;
import com.bbn.openmap.I18n;
import com.bbn.openmap.Layer;
import com.bbn.openmap.LightMapHandlerChild;
import com.bbn.openmap.MapHandler;
import com.bbn.openmap.PropertyConsumer;
import com.bbn.openmap.util.ComponentFactory;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
/**
* Abstract Menu Object that takes care of common bean context-related
* functionality. Components on this Menu that implement the
* LightMapHandlerChild interface, that also are created via the
* constructor, or created when the properties are evaluated, will be
* given the MapHandler through the findAndInit(Object) method. These
* components will also receive findAndInit() and findAndUndo() calls
* when other components are added to the MapHandler (BeanContext).
* This makes the LightMapHandlerChild menu components invisible to
* MapHandler components, but able to connect to them.
* <P>
*
* If you have a component that needs to be added to the menu that
* should in the MapHandler, you should *NOT* make it a
* LightMapHandlerChild, but implement the same methods as the
* MapHandlerChild and add it to the MapHandler before adding it to
* this menu. If the component is a LightMapHandlerChild, added to
* this menu, and also added to the MapHandler, it will receive double
* membership notifications which may be confusing.
* <P>
*
* AbstractOpenMapMenus can be configure via properties. You can set,
* with 'menu' as a property prefix:
* <P>
*
* <pre>
*
*
*
* # Title used in the Menu Bar.
* menu.prettyName=Menu Title
* # The letter to use as Mnemonic
* menu.mnemonic=M
* # A marker name list of items to use in the menu. 'sep' inserts a
* # separator, property classname definition not needed.
* menu.items=first second sep third
* first.class=Classname of first menu item
* # You can add properties for Menu Item, if it is a PropertyConsumer.
* #first.property1=prop1
* second.class=classname
* third.class=classname
*
*
*
* </pre>
*
* If no properties are set, the name, mnemonic and any items added in
* the constructor will be in the menu. Any items in the properties
* will be added to items created and added in the constructor - this
* really applies to any subclasses to this class.
*/
abstract public class AbstractOpenMapMenu extends JMenu implements
BeanContextChild, BeanContextMembershipListener, PropertyConsumer,
MenuBarMenu, LightMapHandlerChild {
/**
* All AbstractOpenMapMenus have access to an I18n object, which
* is provided by the Environment.
*/
protected I18n i18n = Environment.getI18n();
protected BeanContextChildSupport beanContextChildSupport = new BeanContextChildSupport(this);
/**
* Token uniquely identifying this component in the application
* properties.
*/
protected String propertyPrefix = null;
/**
* A hashtable to keep track of item prefixes and their
* components, for restructuring properties later. Only created if
* menu uses properties to create items.
*/
protected Hashtable items = null;
public final static String ItemsProperty = "items";
public final static String SeparatorProperty = "sep";
public final static String PrettyNameProperty = Layer.PrettyNameProperty;
public final static String MnemonicProperty = "mnemonic";
protected String itemsPropertyContents = null;
protected Hashtable itemsProperties = null;
public AbstractOpenMapMenu() {
super();
}
public AbstractOpenMapMenu(String title) {
super(title);
}
/** Method for BeanContextChild interface. */
public BeanContext getBeanContext() {
return beanContextChildSupport.getBeanContext();
}
/** Method for BeanContextChild interface. */
public void setBeanContext(BeanContext in_bc) throws PropertyVetoException {
beanContextChildSupport.setBeanContext(in_bc);
if (in_bc == null) {
return;
}
in_bc.addBeanContextMembershipListener(this);
// let MenuItems find it if they want to look for
// it there, or if they want to add themselves. Not sure what
// the ConcurrentModificationException ramifications will be,
// though.
findAndInit((Object) in_bc);
findAndInit(in_bc.iterator());
}
/** Method for BeanContextMembership interface. */
public void childrenAdded(BeanContextMembershipEvent bcme) {
findAndInit(bcme.iterator());
}
/** Method for BeanContextMembership interface. */
public void childrenRemoved(BeanContextMembershipEvent bcme) {
findAndUndo(bcme.iterator());
}
/**
* Subclasses should no longer implement this method. Use the
* findAndUndo(Object) instead, so subclasses and superclasses can
* be given the opportunity to use the object, too.
*/
public void findAndUndo(Iterator it) {
while (it.hasNext()) {
findAndUndo(it.next());
}
}
/**
* Method called when an object is removed from the MapHandler.
* Called to let menu objects disconnect from it.
*/
public void findAndUndo(Object someObj) {
Component menuItems[] = getMenuComponents();
for (int i = 0; i < menuItems.length; i++) {
Component item = menuItems[i];
if (item instanceof LightMapHandlerChild) {
((LightMapHandlerChild) item).findAndUndo(someObj);
}
}
}
/**
* Subclasses should no longer implement this method. Use the
* findAndInit(Object) instead, so subclasses and superclasses can
* be given the opportunity to use the object, too.
*/
public void findAndInit(Iterator it) {
while (it.hasNext()) {
findAndInit(it.next());
}
}
/**
* Method called when an object is added from the MapHandler.
* Called to let menu objects connect to it.
*/
public void findAndInit(Object someObj) {
Component menuItems[] = getMenuComponents();
for (int i = 0; i < menuItems.length; i++) {
Component item = menuItems[i];
if (item instanceof LightMapHandlerChild) {
((LightMapHandlerChild) item).findAndInit(someObj);
}
}
}
/** 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);
}
/**
* Return the MapHandler, if it's been set. May be null if the
* Menu hasn't been added to the MapHandler.
*/
public MapHandler getMapHandler() {
return (MapHandler) beanContextChildSupport.getBeanContext();
}
/**
* Sets the properties for the OMComponent.
*
* @param props the <code>Properties</code> object.
*/
public void setProperties(java.util.Properties props) {
setProperties(getPropertyPrefix(), props);
}
/**
* Sets the properties for the OMComponent.
*
* @param prefix the token to prefix the property names
* @param props the <code>Properties</code> object
*/
public void setProperties(String prefix, java.util.Properties props) {
setPropertyPrefix(prefix);
String realPrefix = PropUtils.getScopedPropertyPrefix(prefix);
String prettyName = props.getProperty(realPrefix + PrettyNameProperty);
if (prettyName != null) {
setText(prettyName);
}
String mnemonicString = props.getProperty(realPrefix + MnemonicProperty);
if (mnemonicString != null) {
setMnemonic((int) mnemonicString.charAt(0));
}
itemsPropertyContents = props.getProperty(realPrefix + ItemsProperty);
Vector menuItems = PropUtils.parseSpacedMarkers(itemsPropertyContents);
if (!menuItems.isEmpty()) {
int nMenuItems = menuItems.size();
if (Debug.debugging("menu")) {
Debug.output("Menu " + getText() + " created with "
+ nMenuItems + " item" + (nMenuItems == 1 ? "" : "s")
+ " in properties");
}
for (int i = 0; i < nMenuItems; i++) {
String itemPrefix = (String) menuItems.elementAt(i);
if (itemPrefix.equals(SeparatorProperty)) {
add(new JSeparator());
continue;
}
String classProperty = itemPrefix + ".class";
String className = props.getProperty(classProperty);
if (className == null) {
Debug.error("Menu " + getText()
+ ".setProperties(): Failed to locate property \""
+ classProperty + "\"\n Skipping menu item \""
+ itemPrefix + "\"");
continue;
}
if (itemsProperties == null) {
itemsProperties = new Properties();
}
itemsProperties.put(classProperty, className);
Object obj = ComponentFactory.create(className,
itemPrefix,
props);
if (obj instanceof Component) {
add((Component) obj);
} else if (obj instanceof JMenuItem) {
add((JMenuItem) obj);
}
}
} else {
if (Debug.debugging("menu")) {
Debug.output("Menu " + getText()
+ " created without items in properties");
}
}
}
/**
* PropertyConsumer method, to fill in a Properties object,
* reflecting the current values of the OMComponent. If the
* component has a propertyPrefix set, the property keys should
* have that prefix plus a separating '.' prepended to each
* propery key it uses for configuration.
*
* @param props a Properties object to load the PropertyConsumer
* properties into. If props equals null, then a new
* Properties object should be created.
* @return Properties object containing PropertyConsumer property
* values. If getList was not null, this should equal
* getList. Otherwise, it should be the Properties object
* created by the PropertyConsumer.
*/
public Properties getProperties(Properties props) {
if (props == null) {
props = new Properties();
}
String prefix = PropUtils.getScopedPropertyPrefix(propertyPrefix);
props.put(prefix + PrettyNameProperty, getText());
props.put(prefix + MnemonicProperty, "" + ((char) getMnemonic()));
if (itemsPropertyContents != null) {
props.put(prefix + ItemsProperty, itemsPropertyContents);
}
if (itemsProperties != null) {
props.putAll(itemsProperties);
}
return props;
}
/**
* Method to fill in a Properties object with values reflecting
* the properties able to be set on this PropertyConsumer. The key
* for each property should be the raw property name (without a
* prefix) with a value that is a String that describes what the
* property key represents, along with any other information about
* the property that would be helpful (range, default value,
* etc.). For Layer, this method should at least return the
* 'prettyName' property.
*
* @param list a Properties object to load the PropertyConsumer
* properties into. If getList equals null, then a new
* Properties object should be created.
* @return Properties object containing PropertyConsumer property
* values. If getList was not null, this should equal
* getList. Otherwise, it should be the Properties object
* created by the PropertyConsumer.
*/
public Properties getPropertyInfo(Properties list) {
if (list == null) {
list = new Properties();
}
return list;
}
/**
* Set the property key prefix that should be used by the
* PropertyConsumer. The prefix, along with a '.', should be
* prepended to the property keys known by the PropertyConsumer.
*
* @param prefix the prefix String.
*/
public void setPropertyPrefix(String prefix) {
propertyPrefix = prefix;
}
/**
* Get the property key prefix that is being used to prepend to
* the property keys for Properties lookups.
*
* @return the property prefix for the menu
*/
public String getPropertyPrefix() {
return propertyPrefix;
}
}