// **********************************************************************
//
// <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/ToolPanel.java,v $
// $RCSfile: ToolPanel.java,v $
// $Revision: 1.13 $
// $Date: 2006/03/06 15:41:47 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.gui;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
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.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLabel;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import com.bbn.openmap.Environment;
import com.bbn.openmap.I18n;
import com.bbn.openmap.PropertyConsumer;
import com.bbn.openmap.gui.menu.ToolPanelToggleMenuItem;
import com.bbn.openmap.util.PropUtils;
/**
* Represents the toolbar containing tools to apply to the map. Tools can be
* added in sequential order, and retrieved using the tool's keyword. NOTE:
* Every time a string is passed into a method of this class, the interned
* version of it is used as a key.
* <P>
*
* When the ToolPanel is part of the BeanContext, it looks for Tools that have
* also been added to the BeanContext. If there is more than one ToolPanel in a
* BeanContext at a time, both will show the same Tool faces. The 'components'
* property can be used to control which tools can be added to a specific
* instance of a ToolPanel. That property should contain a space separated list
* of prefixes used for Tools, which in turn should be set in the Tools as their
* keys.
*
* @see Tool
* @author john gash
*/
public class ToolPanel extends JToolBar implements BeanContextChild, BeanContextMembershipListener,
MapPanelChild, PropertyConsumer, ComponentListener {
private static Logger logger = Logger.getLogger(ToolPanel.class.getName());
private static final long serialVersionUID = 1L;
/** The set of tools contained on the toolbar. */
protected Hashtable<String, Tool> items = new Hashtable<String, Tool>();
/**
* A flag to note whether the ToolPanel inserts spaces between tools.
*/
protected boolean autoSpace = false;
/**
* BeanContextChildSupport object provides helper functions for
* BeanContextChild interface.
*/
private BeanContextChildSupport beanContextChildSupport = new BeanContextChildSupport(this);
/**
* The property prefix used for this ToolPanel.
*/
protected String propertyPrefix = null;
/**
* A list of components to use for filtering tools found in the MapHandler
* to add to this ToolPanel.
*/
public final static String ComponentsProperty = "components";
/**
* A list of components to use for filtering out tools found in the
* MapHandler. Components added to this list will NOT be added to this
* ToolPanel.
*/
public final static String AvoidComponentsProperty = "avoid";
public final static String MembershipProperty = "membership";
public final static String NameProperty = "name";
/**
* A filter list of components to look for and add.
*/
protected List<String> componentList = null;
/**
* A filter list of components to avoid.
*/
protected List<String> avoidList = null;
protected GridBagLayout gridbag = new GridBagLayout();
protected GridBagConstraints c = new GridBagConstraints();
protected String parentName = null;
/**
* Holder that expands in the GridBagLayout, keeping things pushed to the
* left side of the toolpanel.
*/
protected JLabel filler = null;
/**
* Constructor
*/
public ToolPanel() {
setLayout(gridbag);
setFloatable(false);
setVisible(false);
setName("Tool Panel");
}
/**
* Add an item to the tool bar.
*
* @param key The key associated with the item.
* @param item The Tool to add.
*/
public void add(String key, Tool item) {
add(key, item, -1);
}
/**
* A little array used to track what indexes are already used, to prevent
* the GridBagLayout from placing things on top of each other.
*/
protected boolean[] usedIndexes;
public int MAX_INDEXES = 101;
/**
* Provides the next available component index for placement, starting at 0.
*/
protected int getNextAvailableIndex() {
return getNextAvailableIndex(0);
}
/**
* Provides the next available component index for placement, given a
* starting index.
*/
protected int getNextAvailableIndex(int startAt) {
if (usedIndexes == null) {
usedIndexes = new boolean[MAX_INDEXES];
}
if (startAt < 0)
startAt = 0;
if (startAt >= MAX_INDEXES)
startAt = MAX_INDEXES - 1;
int i = startAt;
// Find the first unused
for (; i < MAX_INDEXES && usedIndexes[i] == true; i++) {
}
usedIndexes[i] = true;
return i;
}
/**
* Add an item to the tool bar.
*
* @param key The key associated with the item.
* @param item The Tool to add.
* @param index The position index for placement of the tool. -1 puts it at
* the end, and if the position is greater than the size, it is
* placed at the end. This class does not remember where items were
* asked to be placed, so later additions may mess up intended order.
*/
public void add(String key, Tool item, int index) {
int orientation = getOrientation();
boolean hOrient = orientation == SwingConstants.HORIZONTAL;
item.setOrientation(orientation);
Container face = item.getFace();
if (face != null) {
face.addComponentListener(this);
items.put(key.intern(), item);
if (autoSpace) {
index *= 2;
}
if (hOrient) {
c.weightx = 0;
c.gridx = getNextAvailableIndex(index);
c.gridy = 0;
c.anchor = GridBagConstraints.WEST;
} else {
c.weighty = 0;
c.gridx = 0;
c.gridy = getNextAvailableIndex(index);
c.anchor = GridBagConstraints.NORTH;
}
gridbag.setConstraints(face, c);
add(face);
if (filler == null) {
if (hOrient) {
c.gridx = getNextAvailableIndex(MAX_INDEXES);
c.anchor = GridBagConstraints.EAST;
c.weightx = 1;
} else {
c.gridy = getNextAvailableIndex(MAX_INDEXES);
c.anchor = GridBagConstraints.SOUTH;
c.weighty = 1;
}
filler = new JLabel("");
gridbag.setConstraints(filler, c);
add(filler);
}
if (autoSpace) {
JLabel l = new JLabel(" ");
gridbag.setConstraints(l, c);
add(l);
}
}
setVisibility();
firePropertyChange(MembershipProperty, null, items);
}
/**
* Add an item to the tool bar. Assumes that the key will be picked out of
* the Tool.
*
* @param item The Tool to add.
*/
public void add(Tool item) {
add(item, -1);
}
/**
* Add an item to the tool bar. Assumes that the key will be picked out of
* the Tool.
*
* @param item The Tool to add.
* @param index The position to add the Tool. -1 will add it to the end.
*/
public void add(Tool item, int index) {
try {
add(item.getKey(), item, index);
} catch (NullPointerException npe) {
if (item != null) {
logger.warning("ToolPanel.add(): no name for " + item.getClass().getName());
npe.printStackTrace();
} else {
logger.warning("ToolPanel.add(): no name for null tool.");
}
}
}
/**
* Get an item from the tool bar.
*
* @param key The key associated with the item.
* @return The tool associated with the key, null if not found.
*/
public Tool get(String key) {
return (Tool) items.get(key.intern());
}
/** Remove a tool with the right key */
public void remove(String key) {
Tool tool = (Tool) items.remove(key.intern());
if (tool != null) {
remove(tool.getFace());
tool.getFace().removeComponentListener(this);
firePropertyChange(MembershipProperty, null, items);
}
}
/** Add a space between tools. */
protected void addSpace() {
add(new JLabel(" "));
}
/** Set whether spaces are placed between tools. */
public void setAutoSpace(boolean set) {
autoSpace = set;
}
/**
* BorderLayout.NORTH by default for this class.
*/
protected String preferredLocation = java.awt.BorderLayout.NORTH;
/**
* MapPanelChild method.
*/
public void setPreferredLocation(String value) {
preferredLocation = value;
}
/** MapPanelChild method. */
public String getPreferredLocation() {
return preferredLocation;
}
/** Find out whether spaces are being placed between tools. */
public boolean isAutoSpace() {
return autoSpace;
}
/**
* Set the list of strings used by the ToolPanel to figure out which Tools
* should be added (in the findAndInit()) method and where they should go.
*/
public void setComponentList(List<String> list) {
componentList = list;
}
/**
* Get the list of strings used by the ToolPanel to figure out which Tools
* should be added (in the findAndInit()) method and where they should go.
*/
public List<String> getComponentList() {
return componentList;
}
/**
* Set the list of strings used by the ToolPanel to figure out which Tools
* should not be added (in the findAndInit()) method.
*/
public void setAvoidList(List<String> list) {
avoidList = list;
}
/**
* Get the list of strings used by the ToolPanel to figure out which Tools
* should not be added (in the findAndInit()) method.
*/
public List<String> getAvoidList() {
return avoidList;
}
/**
* Called when the ToolPanel is added to the BeanContext, and when new
* objects are added to the BeanContext after that. The ToolPanel looks for
* Tools that are part of the BeanContext.
*
* @param it iterator to use to go through the new objects.
*/
public void findAndInit(Iterator<Object> it) {
while (it.hasNext()) {
findAndInit(it.next());
}
}
/**
* Figure out if the string key is in the provided list, and provide the
* location index of it is.
*
* @param key the key of the component to check for.
* @param list the list of keys to check.
* @return -1 if not on the list, the index starting at 0 if it is.
*/
protected int keyOnList(String key, List<String> list) {
int ret = -1;
int index = 0;
if (list != null) {
for (String listKey : list) {
if (listKey.equalsIgnoreCase(key)) {
ret = index;
break;
}
index++;
}
}
return ret;
}
public void findAndInit(Object someObj) {
if (someObj instanceof Tool) {
String key = ((Tool) someObj).getKey();
List<String> list = getComponentList();
int index;
if (list != null) {
index = keyOnList(key, list);
if (index >= 0) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "ToolPanel: found a tool Object {0} for placement at {1}", new Object[] {
key, index });
}
add((Tool) someObj, index);
}
} else {
index = keyOnList(key, getAvoidList());
if (index < 0) {
logger.fine("ToolPanel: found a tool Object");
add((Tool) someObj);
}
}
}
}
/**
* Get a menu item that controls the visibility of this ToolPanel.
*
* @return ToolPanelToggleMenuItem
*/
public ToolPanelToggleMenuItem getToggleMenu() {
return new ToolPanelToggleMenuItem(this);
}
/**
* Checks to see if the menu item controls this ToolPanel.
*
* @param mi
* @return true if menu item refers to this tool panel.
*/
public boolean checkToolPanelToggleMenuItem(ToolPanelToggleMenuItem mi) {
return (mi != null && mi.getToolPanel().equals(this));
}
/**
* BeanContextMembershipListener method. Called when objects have been added
* to the parent BeanContext.
*
* @param bcme the event containing the iterator with new objects.
*/
public void childrenAdded(BeanContextMembershipEvent bcme) {
findAndInit(bcme.iterator());
}
/**
* BeanContextMembershipListener method. Called when objects have been
* removed from the parent BeanContext. If the ToolPanel finds a Tool in the
* list, it removes it from the ToolPanel.
*
* @param bcme the event containing the iterator with removed objects.
*/
public void childrenRemoved(BeanContextMembershipEvent bcme) {
Iterator<Object> it = bcme.iterator();
Object someObj;
while (it.hasNext()) {
someObj = it.next();
if (someObj instanceof Tool) {
// do the initializing that need to be done here
logger.fine("ToolPanel removing tool Object");
remove(((Tool) someObj).getKey());
}
}
}
/** Method for BeanContextChild interface. */
public BeanContext getBeanContext() {
return beanContextChildSupport.getBeanContext();
}
/**
* Method for BeanContextChild interface. Called when the ToolPanel is added
* to the BeanContext.
*
* @param in_bc the BeanContext.
*/
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) {
beanContextChildSupport.addPropertyChangeListener(propertyName, in_pcl);
}
/** Method for BeanContextChild interface. */
public void removePropertyChangeListener(String propertyName, PropertyChangeListener in_pcl) {
beanContextChildSupport.removePropertyChangeListener(propertyName, in_pcl);
}
/** 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);
}
public void setPropertyPrefix(String prefix) {
propertyPrefix = prefix;
}
public String getPropertyPrefix() {
return propertyPrefix;
}
public void setProperties(Properties props) {
setProperties(null, props);
}
public void setProperties(String prefix, Properties props) {
setPropertyPrefix(prefix);
prefix = PropUtils.getScopedPropertyPrefix(prefix);
String componentsString = props.getProperty(prefix + ComponentsProperty);
if (componentsString != null) {
setComponentList(PropUtils.parseSpacedMarkers(componentsString));
}
String avoidComponentsString = props.getProperty(prefix + AvoidComponentsProperty);
if (avoidComponentsString != null) {
setAvoidList(PropUtils.parseSpacedMarkers(avoidComponentsString));
}
String preferredLocationString = props.getProperty(prefix + PreferredLocationProperty);
if (preferredLocationString != null) {
try {
preferredLocationString = (String) java.awt.BorderLayout.class.getField(preferredLocationString).get(null);
} catch (NoSuchFieldException nsfe) {
preferredLocationString = null;
} catch (IllegalAccessException iae) {
preferredLocationString = null;
}
if (preferredLocationString != null) {
setPreferredLocation(preferredLocationString);
if (preferredLocationString.equalsIgnoreCase("WEST")
|| preferredLocationString.equalsIgnoreCase("EAST")) {
setOrientation(SwingConstants.VERTICAL);
}
}
}
setName(props.getProperty(prefix + NameProperty, getName()));
setParentName(props.getProperty(prefix + ParentNameProperty, getParentName()));
}
/**
* Take a List of strings, and return a space-separated version. Return null
* if the List is null.
*/
protected StringBuffer rebuildListProperty(List<String> aList) {
StringBuffer list = null;
if (aList != null) {
list = new StringBuffer();
for (String toolKey : aList) {
list.append(toolKey).append(" ");
}
}
return list;
}
public Properties getProperties(Properties props) {
if (props == null) {
props = new Properties();
}
String prefix = PropUtils.getScopedPropertyPrefix(this);
StringBuffer listProp = rebuildListProperty(getComponentList());
if (listProp != null) {
props.put(prefix + ComponentsProperty, listProp.toString());
}
listProp = rebuildListProperty(getAvoidList());
if (listProp != null) {
props.put(prefix + AvoidComponentsProperty, listProp.toString());
}
PropUtils.putIfNotDefault(props, prefix + PreferredLocationProperty, getPreferredLocation());
PropUtils.putIfNotDefault(props, prefix + NameProperty, getName());
PropUtils.putIfNotDefault(props, prefix + ParentNameProperty, getParentName());
return props;
}
public Properties getPropertyInfo(Properties props) {
if (props == null) {
props = new Properties();
}
I18n i18n = Environment.getI18n();
PropUtils.setI18NPropertyInfo(i18n, props, ToolPanel.class, ComponentsProperty, "Tool Names", "List of Names of Tools to Add", null);
PropUtils.setI18NPropertyInfo(i18n, props, ToolPanel.class, AvoidComponentsProperty, "Avoid Tool Names", "List of Names of Tools to Not Add", null);
PropUtils.setI18NPropertyInfo(i18n, props, ToolPanel.class, PreferredLocationProperty, "Location", "Preferred Location of Tool Panel", null);
PropUtils.setI18NPropertyInfo(i18n, props, ToolPanel.class, NameProperty, "Tool Name", "Name of This Tool Panel", null);
return props;
}
/**
* If any of the components are visible, set the ToolPanel to be visible. If
* all of them are invisible, make the ToolPanel invisible.
*/
protected void setVisibility() {
setVisible(areComponentsVisible());
}
public boolean areComponentsVisible() {
Enumeration<Tool> enumeration = items.elements();
while (enumeration.hasMoreElements()) {
Tool tool = enumeration.nextElement();
Container face = tool.getFace();
// make sure tool != filler - filler(JPanel) test is for object
// equivalence
if (!filler.equals(tool) && face != null && face.isVisible()) {
return true;
}
}
return false;
}
public void componentHidden(ComponentEvent ce) {
setVisibility();
}
public void componentMoved(ComponentEvent ce) {
}
public void componentResized(ComponentEvent ce) {
}
public void componentShown(ComponentEvent ce) {
setVisibility();
}
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
}