// ********************************************************************** // // <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.10 $ // $Date: 2006/02/13 16:58:32 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.gui; import java.awt.Component; import java.beans.PropertyChangeListener; 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(); /** * 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); } /** Method for BeanContextChild interface. */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener in_pcl) { beanContextChildSupport.addPropertyChangeListener(propertyName, in_pcl); } /** Method for BeanContextChild interface. */ public void removePropertyChangeListener(String propertyName, PropertyChangeListener in_pcl) { beanContextChildSupport.removePropertyChangeListener(propertyName, in_pcl); } /** * 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); } // We need to update this, as requested by the Javadocs, to use VK_XXX // ints. // 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 property 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()); // Mnemonics are handled differently now, needs to be updated to use // VK_XXX // 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; } }