Re: [OpenMap Users] Changes in OpenMap 4.6.2

From: Piotr Kamiński <Piotr.Kaminski_at_ctm.gdynia.pl>
Date: Thu, 27 Jan 2005 12:39:40 +0100

Hi Don,

Don Dietrick wrote:

> Hi Piotr,
>
> Thanks for all the updates, I'll try to get them into 4.6.2. I might
> have to wait and do a quick 4.6.3 release, I'm juggling a few things
> right now and need to get the 4.6.2 release out.

I've attached a few additional files, which has changed:
 - 'Layer*' correction of 'removeable' to 'removable' property name.
 - 'ProjectionMenu' and 'I18N' - localization


>
> On Jan 25, 2005, at 11:31 AM, Piotr Kamiński wrote:
>
>> A few additional questions:ła
>> 1. UTMPoint was changed. Instead of Zone letter code there is N or
>> S now. Why such a change? I though that zone letters was some kind
>> of standard. I assume that you decided to use ONLY S or N letter. Am
>> I right? There is erronous comment in UTMPoint constructor - "If
>> the letter is something else instead of 'N' or 'S', 'N' is
>> assumed." Method checkZone throws exception when something different
>> than N or S is specified.
>
>
> It turns out that N or S are the only valid zones for UTM. All the
> other zone letters only pertain to MGRS. I had a reference that
> blurred this line, which is the reason for the original design.

I've just searched some resources and most of them use 'old' UTM Grid
Zone Designator, based on MGRS notation. Only one or two publication use
notation wihout any letter (eg. 16 3423432.0 mE 4234324.0 mN). There was
a note that specifying hemisphre is important, but it was not explained
how to do it. I think that changes in UTMPoint might be little
confusing. In MGRS both S and N letters are used. Now if I see 16S
2343242 234324 I can't be sure if 'S' stands for MGRS zone or south
hemishere. I think that GPS devices also use MGRS notation.

Could you point me to references which exactly defines how UTM
coordinates should be presented.

>
> Thanks for picking up on the comment, it's a result of hesitation on
> my part, I was trying to decide whether it was better to throw an
> exception or quietly interpret incorrect input. I decided to throw
> an exception, but forgot to update the comment.
>
Exception is better, but what do you think about using two schemes of
zone numbering - new one with hemisphere and old one with MGRS zone
letter. You could add second constructor to support this.

>> 2. What about changing LatLonPoint from float to double type. I had
>> asked about this some time ago but haven't found any change in
>> source code. I saw reports on OpenMap's list about successful
>> transformation to double. Maybe just such change is enough? Our
>> changes in ProjMath (insufficient precision of float type) testifies
>> against current, mostly float based, implementation of calculation
>> algorithms.
>
>
> It's not going to happen for the 4.6.2 release, but I've hammered out
> a design in a different project (which uses OpenMap, too) that
> preserves backward compatibility which providing a choice between
> floats and doubles. The API change to the projection libraries will
> be significant, (but still backward compatible) so I think a 4.7
> version change will be proper.


When we could expect 4.7 release ?

Regards,
Piotr



// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/openmap/openmap/src/openmap/com/bbn/openmap/Layer.java,v $
// $RCSfile: Layer.java,v $
// $Revision: 1.18 $
// $Date: 2004/10/14 18:18:15 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap;

import java.awt.Component;
import java.awt.Container;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
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.Properties;

import javax.swing.JComponent;

import com.bbn.openmap.I18n;
import com.bbn.openmap.ProjectionPainter;
import com.bbn.openmap.event.InfoDisplayEvent;
import com.bbn.openmap.event.InfoDisplayListener;
import com.bbn.openmap.event.LayerStatusEvent;
import com.bbn.openmap.event.LayerStatusListener;
import com.bbn.openmap.event.ListenerSupport;
import com.bbn.openmap.event.MapMouseListener;
import com.bbn.openmap.event.ProjectionEvent;
import com.bbn.openmap.event.ProjectionListener;
import com.bbn.openmap.gui.ScrollPaneWindowSupport;
import com.bbn.openmap.gui.WindowSupport;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
import com.bbn.openmap.util.propertyEditor.Inspector;

/**
 * Layer objects are components which can be added to the MapBean to
 * make a map.
 * <p>
 *
 * Layers implement the ProjectionListener interface to listen for
 * ProjectionEvents. When the projection changes, they may need to
 * refetch, regenerate their graphics, and then repaint themselves
 * into the new view.
 * <p>
 *
 * When the Layer is added to the MapBean, it will start receiving
 * ProjectionEvents via the ProjectionListener.projectionChanged()
 * method it has to implement. There is a
 * setProjection(ProjectionEvent) methods that should be called from
 * there if you want to save the projection for later use (handling
 * MouseEvents, etc). If you call getProjection() before calling
 * setProjection(), getProjection() will return null, and your
 * OMGraphics will complain and probably freak out at some point.
 *
 * <pre>
 * //// SAMPLE handling of the ProjectionListener interface.
 *
 * public void projectionChanged(com.bbn.openmap.event.ProjectionEvent pe) {
 * Projection proj = setProjection(pe);
 * if (proj != null) {
 * // Use the projection to gather OMGraphics in the layer,
 * // and prepare the layer so that in the paint() method,
 * // the OMGraphics get rendered.
 *
 * // Call any methods that kick off work to build graphics
 * // here...
 *
 * // You get the paint() methods called by calling
 * // repaint():
 * repaint();
 * }
 *
 * fireStatusUpdate(LayerStatusEvent.FINISH_WORKING);
 * }
 * </pre>
 *
 * _at_see com.bbn.openmap.event.ProjectionListener
 * _at_see com.bbn.openmap.event.ProjectionEvent
 * _at_see com.bbn.openmap.PropertyConsumer
 */
public abstract class Layer extends JComponent implements ProjectionListener,
        ProjectionPainter, BeanContextChild, BeanContextMembershipListener,
        PropertyConsumer, ActionListener {

    /**
     * Precaches the swing package. Computed based on the package of
     * <code>JComponent</code>.
     */
    protected static final String SWING_PACKAGE = getPackage(JComponent.class);

    /**
     * The String to use for a key lookup in a Properties object to
     * find the name to use in a GUI relating to this layer.
     */
    public static final String PrettyNameProperty = "prettyName";

    /**
     * The property to set to add the layer to the BeanContext
     * "addToBeanContext". This probably needs be set by the layer
     * itself, because it knows whether it needs other components or
     * not. However, this property is defined in case an option can be
     * given to the user. If a Layer doesn't want this option given,
     * it should reset the addToBeanContext variable after
     * setProperties() is called. The Layer.setProperties() methods
     * maintain the current state of the variable if undefined, which
     * is true by default.
     */
    public static final String AddToBeanContextProperty = "addToBeanContext";

    /**
     * Property 'background' to designate this layer as a background
     * layer, which will cause extra buffering to occur if the
     * application can handle it. False by default.
     */
    public static final String AddAsBackgroundProperty = "background";

    /**
     * Property 'removable' to designate this layer as removable
     * from the application, or able to be deleted. True by default.
     */
    public static final String RemovableProperty = "removable";

    /**
     * The property to show the palette when the layer is created -
     * or, more accurately, when the properties are set.
     */
    public static final String AutoPaletteProperty = "autoPalette";
    /** Layer-defined action event command to display the palette. */
    public static final String DisplayPaletteCmd = "displayPaletteCmd";
    /** Layer-defined action event command to hide the palette. */
    public static final String HidePaletteCmd = "hidePaletteCmd";
    /**
     * Layer-defined action event command to display the properties
     * using an Inspector.
     */
    public static final String DisplayPropertiesCmd = "displayPropertiesCmd";
    /**
     * Layer-defined action event command to force a redraw on the
     * layer. The Layer class does not respond to this command, it's
     * provided as a convenience.
     */
    public static final String RedrawCmd = "redrawCmd";

    /**
     * The listeners to the Layer that respond to requests for
     * information displays, like messages, requests for URL displays,
     * etc.
     */
    protected ListenerSupport IDListeners = null;

    /**
     * List of LayerStatusListeners.
     */
    protected ListenerSupport lsListeners = null;

    /**
     * Token uniquely identifying this layer in the application
     * properties.
     */
    protected String propertyPrefix = null;

    /**
     * Used by the LayerHandler to check if the layer should be added
     * to the MapHandler BeanContext. See the comments under the
     * AddToBeanContextProperty. True by default.
     */
    protected boolean addToBeanContext = true;

    /**
     * Flag used by the layer to indicate that it should be treated as
     * a background layer, indicating that any cache mechanism
     * available can enable extra buffering. This may prevent mouse
     * events from being received by the layer.
     */
    protected boolean addAsBackground = false;

    /**
     * Flag to designate the layer as removable or not.
     */
    protected boolean removable = true;

    /**
     * A flag to have the layer display it's palette when the
     * properties are set. If you are creating a layer manually, just
     * call showPalette() instead.
     */
    protected boolean autoPalette = false;

    /**
     * This is a convenience copy of the latest projection received
     * from the MapBean, when the Layer is added to the map. If you
     * need it, use the accessor!.
     */
    private Projection projection = null;

    /**
     * Support class that now handles palette windows.
     */
    protected transient WindowSupport windowSupport;

    /**
     * A helper component listener that is paying attention to the
     * visibility of the palette.
     */
    protected transient ComponentListener paletteListener;

    /**
     * A pointer to the JDialog or JInternalFrame. May be used by the
     * layer's ComponentListeners to figure out if a component event
     * is for the layer or for the palette.
     */
    protected transient Container palette;

    /**
     * The BeanContext allows Layers to find other components, and
     * other components to find the layer, if the layer is added to
     * it.
     */
    protected final BeanContextChildSupport beanContextChildSupport = new BeanContextChildSupport(this);
    /**
     * All layers have access to an I18n object, which is provided by
     * the Environment.
     */
    protected I18n i18n = Environment.getI18n();

    /**
     * Returns the package of the given class as a string.
     *
     * _at_param c a class
     */
    protected static String getPackage(Class c) {
        String className = c.getName();
        int lastDot = className.lastIndexOf('.');
        return className.substring(0, lastDot);
    }

    /**
     * Override to only allow swing package listeners. If Listeners
     * get added to the Layers, the mouse events don't make it to the
     * map. Ever.
     * <p>
     * Swing popup menus, like <code>JPopupMenu</code> grab the
     * JComponent by adding themselves as <code>MouseListener</code>
     * s. So this method allows instances of classes in the xxx.swing
     * package to be added as <code>MouseListener</code>s, and no
     * one else.
     *
     * _at_param l a mouse listener.
     */
    public final void addMouseListener(MouseListener l) {
        String pkg = getPackage(l.getClass());
        if (java.beans.Beans.isDesignTime() || pkg.equals(SWING_PACKAGE)
                || pkg.startsWith(SWING_PACKAGE)) {

            // Used to do nothing for the equals and startsWith
            // comparison, but that breaks the menus from being
            // recinded when something else is clicked on. Thanks to
            // Tom Peel for pointing this out, 11/29/00.
            super.addMouseListener(l);

        } else {
            throw new IllegalArgumentException("This operation is disallowed because the package \""
                    + pkg
                    + "\" is not in the swing package (\""
                    + SWING_PACKAGE + "\").");
        }
    }

    /**
     * Sets the properties for the <code>Layer</code>. This
     * particular method assumes that the marker name is not needed,
     * because all of the contents of this Properties object are to be
     * used for this layer, and scoping the properties with a prefix
     * is unnecessary.
     *
     * _at_param props the <code>Properties</code> object.
     */
    public void setProperties(Properties props) {
        setProperties(getPropertyPrefix(), props);
    }

    /**
     * Sets the properties for the <code>Layer</code>. Part of the
     * PropertyConsumer interface. Layers which override this method
     * should do something like:
     *
     * <code><pre>
     * public void setProperties(String prefix, Properties props) {
     * super.setProperties(prefix, props);
     * // do local stuff
     * }
     * </pre></code>
     *
     * If the addToBeanContext property is not defined, it maintains
     * the same state.
     *
     * _at_param prefix the token to prefix the property names
     * _at_param props the <code>Properties</code> object
     */
    public void setProperties(String prefix, Properties props) {
        String prettyName = PrettyNameProperty;
        setPropertyPrefix(prefix);

        String realPrefix = PropUtils.getScopedPropertyPrefix(prefix);

        prettyName = realPrefix + PrettyNameProperty;

        String defaultName = getName();
        if (defaultName == null) {
            defaultName = "Anonymous";
        }

        setName(props.getProperty(prettyName, defaultName));

        setAddToBeanContext(PropUtils.booleanFromProperties(props, realPrefix
                + AddToBeanContextProperty, addToBeanContext));

        setAddAsBackground(PropUtils.booleanFromProperties(props, realPrefix
                + AddAsBackgroundProperty, addAsBackground));

        setRemovable(PropUtils.booleanFromProperties(props, realPrefix
                + RemovableProperty, removable));

        autoPalette = PropUtils.booleanFromProperties(props, realPrefix
                + AutoPaletteProperty, autoPalette);
    }

    public void setName(String name) {
        super.setName(name);

        BeanContext bc = getBeanContext();
        if (bc != null && bc instanceof MapHandler) {
            LayerHandler lh = (LayerHandler) ((MapHandler) bc).get("com.bbn.openmap.LayerHandler");

            if (lh != null) {
                lh.setLayers();
            }
        }
    }

    /**
     * PropertyConsumer method, to fill in a Properties object,
     * reflecting the current values of the layer. If the layer has a
     * propertyPrefix set, the property keys should have that prefix
     * plus a separating '.' prepended to each propery key it uses for
     * configuration.
     *
     * _at_param props a Properties object to load the PropertyConsumer
     * properties into. If props equals null, then a new
     * Properties object should be created.
     * _at_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 + "class", this.getClass().getName());

        String prettyName = getName();
        if (prettyName != null) {
            props.put(prefix + PrettyNameProperty, prettyName);
        }

        props.put(prefix + AutoPaletteProperty,
                new Boolean(autoPalette).toString());
        props.put(prefix + AddAsBackgroundProperty,
                new Boolean(addAsBackground).toString());
        props.put(prefix + RemovableProperty,
                new Boolean(removable).toString());
        props.put(prefix + AddToBeanContextProperty,
                new Boolean(addToBeanContext).toString());

        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.
     *
     * _at_param list a Properties object to load the PropertyConsumer
     * properties into. If getList equals null, then a new
     * Properties object should be created.
     * _at_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();
        }

        list.put("class", "Class Name used for Layer.");
        list.put("class.editor",
                "com.bbn.openmap.util.propertyEditor.NonEditablePropertyEditor");

        String internString = i18n.get(Layer.class,
                PrettyNameProperty,
                I18n.TOOLTIP,
                "Presentable name for Layer");
        list.put(PrettyNameProperty, internString);
        internString = i18n.get(Layer.class, PrettyNameProperty, "Layer Name");
        list.put(PrettyNameProperty + LabelEditorProperty, internString);
        list.put(PrettyNameProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.NonEditablePropertyEditor");

        internString = i18n.get(Layer.class,
                AutoPaletteProperty,
                I18n.TOOLTIP,
                "Flag to automatically display palette when properties are set");
        list.put(AutoPaletteProperty, internString);
        internString = i18n.get(Layer.class,
                AutoPaletteProperty,
                "Open Palette At Start");
        list.put(AutoPaletteProperty + LabelEditorProperty, internString);
        list.put(AutoPaletteProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");

        internString = i18n.get(Layer.class,
                AddAsBackgroundProperty,
                I18n.TOOLTIP,
                "Flag to use the layer as a background layer");
        list.put(AddAsBackgroundProperty, internString);
        internString = i18n.get(Layer.class,
                AddAsBackgroundProperty,
                "Background");
        list.put(AddAsBackgroundProperty + LabelEditorProperty, internString);
        list.put(AddAsBackgroundProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");

        internString = i18n.get(Layer.class,
                RemovableProperty,
                I18n.TOOLTIP,
                "Flag to allow layer to be deleted.");
        list.put(RemovableProperty, internString);
        internString = i18n.get(Layer.class, RemovableProperty, "Removeable");
        list.put(RemovableProperty + LabelEditorProperty, internString);
        list.put(RemovableProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");

        internString = i18n.get(Layer.class,
                AddToBeanContextProperty,
                I18n.TOOLTIP,
                "Flag to give the layer access to all of the other application components.");
        list.put(AddToBeanContextProperty, internString);
        internString = i18n.get(Layer.class,
                AddToBeanContextProperty,
                "Add to MapHandler");
        list.put(AddToBeanContextProperty + LabelEditorProperty, internString);
        list.put(AddToBeanContextProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");

        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 layer
     */
    public String getPropertyPrefix() {
        return propertyPrefix;
    }

    /**
     * Set the projection the layer should use for calculations. You
     * probably don't need this if you are wondering if you do. Call
     * setProjection(projEvent) instead.
     */
    public void setProjection(Projection proj) {
        projection = proj;
    }

    /**
     * This method lets you take the ProjectionEvent received from the
     * MapBean, and lets you know if you should do something with it.
     * MUST to be called in the projectionChanged() method of your
     * layer, if you want to refer to the projection later. If this
     * methods returns null, you probably just want to call repaint()
     * if your layer.paint() method is ready to paint what it should.
     *
     * _at_param projEvent the ProjectionEvent from the
     * ProjectionListener method.
     * _at_return The new Projection if it is different from the one we
     * already have, null if is the same as the current one.
     */
    public Projection setProjection(ProjectionEvent projEvent) {
        Projection newProjection = projEvent.getProjection();

        if (!newProjection.equals(getProjection())) {
            Projection clone = newProjection.makeClone();
            setProjection(clone);
            return clone;
        } else {
            return null;
        }
    }

    /**
     * Get the latest projection.
     */
    public Projection getProjection() {
        return projection;
    }

    /**
     * Returns the MapMouseListener object that handles the mouse
     * events. This method is IGNORED in this class: it returns null.
     * Derived Layers should return the appropriate object if they
     * desire to receive MouseEvents. The easiest thing for a Layer to
     * do in order to receive MouseEvents is to implement the
     * MapMouseListener interface and return itself. A code snippet:
     * <code><pre>
     * public MapMouseListener getMapMouseListener() {
     * return this;
     * }
     *
     * public String[] getMouseModeServiceList() {
     * return new String[] { SelectMouseMode.modeID };
     * }
     * </pre></code>
     *
     * _at_return null
     */
    public MapMouseListener getMapMouseListener() {
        return null;
    }

    /**
     * Gets the gui controls associated with the layer. This default
     * implementation returns null indicating that the layer has no
     * gui controls.
     *
     * _at_return java.awt.Component or null
     */
    public Component getGUI() {
        return null;
    }

    ///////////////////////////////////////////////////
    // InfoDisplay Handling Setup and Firing

    /**
     * Adds a listener for <code>InfoDisplayEvent</code>s.
     *
     * _at_param aInfoDisplayListener the listener to add
     */
    public synchronized void addInfoDisplayListener(
                                                    InfoDisplayListener aInfoDisplayListener) {
        if (IDListeners == null) {
            IDListeners = new ListenerSupport(this);
        }
        IDListeners.addListener(aInfoDisplayListener);
    }

    /**
     * Removes an InfoDisplayListener from this Layer.
     *
     * _at_param aInfoDisplayListener the listener to remove
     */
    public synchronized void removeInfoDisplayListener(
                                                       InfoDisplayListener aInfoDisplayListener) {

        if (IDListeners != null) {
            IDListeners.removeListener(aInfoDisplayListener);
        }
    }

    /**
     * Sends a request to the InfoDisplayListener to show the
     * information in the InfoDisplay event on an single line display
     * facility.
     *
     * _at_param evt the InfoDisplay event carrying the string.
     */
    public void fireRequestInfoLine(InfoDisplayEvent evt) {
        if (IDListeners != null) {
            for (Iterator it = IDListeners.iterator(); it.hasNext();) {
                ((InfoDisplayListener) it.next()).requestInfoLine(evt);
            }
        } else if (Debug.debugging("layer")) {
            Debug.output(getName()
                    + "|Layer.fireRequestInfoLine(): no info request listener!");
        }
    }

    /**
     * Sends a request to the InfoDisplay listener to display the
     * information on an single line display facility. The
     * InfoDisplayEvent is created inside this function.
     *
     * _at_param infoLine the string to put in the InfoDisplayEvent.
     */
    public void fireRequestInfoLine(String infoLine) {
        fireRequestInfoLine(new InfoDisplayEvent(this, infoLine));
    }

    /**
     * Sends a request to the InfoDisplay listener to display the
     * information on an single line display facility at preferred
     * location. The InfoDisplayEvent is created inside this function.
     *
     * _at_param infoLine the string to put in the InfoDisplayEvent.
     * _at_param loc the index of a preferred location, starting at 0.
     */
    public void fireRequestInfoLine(String infoLine, int loc) {
        fireRequestInfoLine(new InfoDisplayEvent(this, infoLine, loc));
    }

    /**
     * Sends a request to the InfoDisplay listener to display the
     * information in the InfoDisplay event in a Browser.
     *
     * _at_param evt the InfoDisplayEvent holding the contents to put in
     * the Browser.
     */
    public void fireRequestBrowserContent(InfoDisplayEvent evt) {
        if (IDListeners != null) {
            for (Iterator it = IDListeners.iterator(); it.hasNext();) {
                ((InfoDisplayListener) it.next()).requestBrowserContent(evt);
            }
        } else if (Debug.debugging("layer")) {
            Debug.output(getName()
                    + "|Layer.fireRequestBrowserContent(): no info request listener!");
        }
    }

    /**
     * Sends a request to the InfoDisplayListener to display the
     * information in a Browser. The InfoDisplayEvent is created here
     * holding the browserContent
     *
     * _at_param browserContent the contents to put in the Browser.
     */
    public void fireRequestBrowserContent(String browserContent) {
        fireRequestBrowserContent(new InfoDisplayEvent(this, browserContent));
    }

    /**
     * Sends a request to the InfoDisplayListener to display a URL
     * given in the InfoDisplay event in a Browser.
     *
     * _at_param evt the InfoDisplayEvent holding the url location to
     * give to the Browser.
     */
    public void fireRequestURL(InfoDisplayEvent evt) {
        if (IDListeners != null) {
            for (Iterator it = IDListeners.iterator(); it.hasNext();) {
                ((InfoDisplayListener) it.next()).requestURL(evt);
            }
        } else if (Debug.debugging("layer")) {
            Debug.output(getName()
                    + "|Layer.fireRequestURL(): no info request listener!");
        }
    }

    /**
     * Sends a request to the InfoDisplayListener to display a URL in
     * a browser. The InfoDisplayEvent is created here, and the URL
     * location is put inside it.
     *
     * @param url the url location to give to the Browser.
     */
    public void fireRequestURL(String url) {
        fireRequestURL(new InfoDisplayEvent(this, url));
    }

    /**
     * Sends a request to the InfoDisplayListener to show a specific
     * cursor over its component area.
     *
     * _at_param cursor the cursor to use.
     */
    public void fireRequestCursor(java.awt.Cursor cursor) {
        if (IDListeners != null) {
            for (Iterator it = IDListeners.iterator(); it.hasNext();) {
                ((InfoDisplayListener) it.next()).requestCursor(cursor);
            }
        } else if (Debug.debugging("layer")) {
            Debug.output(getName()
                    + "|Layer.fireRequestCursor(): no info request listener!");
        }
    }

    /**
     * Sends a request to the InfoDisplayListener to put the
     * information in the InfoDisplay event in a dialog window.
     *
     * _at_param evt the InfoDisplayEvent holding the message to put into
     * the dialog window.
     */
    public void fireRequestMessage(InfoDisplayEvent evt) {
        if (IDListeners != null) {
            for (Iterator it = IDListeners.iterator(); it.hasNext();) {
                ((InfoDisplayListener) it.next()).requestMessage(evt);
            }
        } else if (Debug.debugging("layer")) {
            Debug.output(getName()
                    + "|Layer.fireRequestMessage(): no info request listener!");
        }
    }

    /**
     * Sends a request to the InfoDisplayListener to display the
     * information in a dialog window. The InfoDisplayEvent is created
     * here, and the URL location is put inside it.
     *
     * @param message the message to put in the dialog window.
     */
    public void fireRequestMessage(String message) {
        fireRequestMessage(new InfoDisplayEvent(this, message));
    }

    /**
     * Request to show the tool tips on the map.
     *
     * _at_param me MouseEvent location for the tool tip.
     * _at_param tip string to display.
     * _at_deprecated use fireRequestToolTip(String tip) instead.
     */
    public void fireRequestToolTip(MouseEvent me, String tip) {
        fireRequestToolTip(new InfoDisplayEvent(this, tip));
    }

    /**
     * Request to show the tool tips on the map.
     *
     * _at_param tip string to display.
     */
    public void fireRequestToolTip(String tip) {
        fireRequestToolTip(new InfoDisplayEvent(this, tip));
    }

    /**
     * Request to hide the tool tips on the map.
     *
     * _at_param me MouseEvent location.
     * _at_deprecated use fireHideToolTip() instead.
     */
    public void fireHideToolTip(MouseEvent me) {
        fireRequestToolTip((InfoDisplayEvent) null);
    }

    /**
     * Request to hide the tool tips on the map.
     */
    public void fireHideToolTip() {
        fireRequestToolTip((InfoDisplayEvent) null);
    }

    /**
     * Fire off a Tool Tip request to the InfoDisplayListeners. If the
     * InfoDisplayEvent is null, then a requestHideToolTip will be
     * fired.
     *
     * _at_deprecated use fireHideToolTip(InfoDisplayEvent) instead.
     */
    public void fireRequestToolTip(MouseEvent me, InfoDisplayEvent event) {
        fireRequestToolTip(event);
    }

    /**
     * Fire off a Tool Tip request to the InfoDisplayListeners. If the
     * InfoDisplayEvent is null, then a requestHideToolTip will be
     * fired.
     */
    public void fireRequestToolTip(InfoDisplayEvent event) {
        if (IDListeners != null) {
            for (Iterator it = IDListeners.iterator(); it.hasNext();) {
                if (event != null) {
                    ((InfoDisplayListener) it.next()).requestShowToolTip(event);
                } else {
                    ((InfoDisplayListener) it.next()).requestHideToolTip();
                }
            }
        } else if (Debug.debugging("layer")) {
            Debug.output(getName()
                    + "|Layer.fireRequestShowToolTip(): no info request listener!");
        }
    }

    ///////////////////////////////////////////////////
    // LayerStatus Handling Setup and Firing

    /**
     * Adds a listener for <code>LayerStatusEvent</code>s.
     *
     * _at_param aLayerStatusListener LayerStatusListener
     */
    public synchronized void addLayerStatusListener(
                                                    LayerStatusListener aLayerStatusListener) {

        if (lsListeners == null) {
            lsListeners = new ListenerSupport(this);
        }
        lsListeners.addListener(aLayerStatusListener);
    }

    /**
     * Removes a LayerStatusListene from this Layer.
     *
     * _at_param aLayerStatusListener the listener to remove
     */
    public synchronized void removeLayerStatusListener(
                                                       LayerStatusListener aLayerStatusListener) {

        if (lsListeners != null) {
            lsListeners.removeListener(aLayerStatusListener);
        }
    }

    /**
     * Sends a status update to the LayerStatusListener.
     *
     * _at_param evt LayerStatusEvent
     */
    public void fireStatusUpdate(LayerStatusEvent evt) {
        // AWTAvailable conditional removed, not used, not useful.
        if (lsListeners != null) {
            for (Iterator it = lsListeners.iterator(); it.hasNext();) {
                ((LayerStatusListener) it.next()).updateLayerStatus(evt);
            }
        } else if (Debug.debugging("layer")) {
            Debug.output(getName()
                    + "|Layer.fireStatusUpdate(): no LayerStatusListeners!");
        }
    }

    /**
     * Sends a status update to the LayerStatusListener.
     *
     * _at_param status the new status
     */
    public void fireStatusUpdate(int status) {
        fireStatusUpdate(new LayerStatusEvent(this, status));
    }

    /**
     * Repaint the layer. If you are using BufferedMapBean for your
     * application, WE STRONGLY RECOMMEND THAT YOU DO NOT OVERRIDE
     * THIS METHOD. This method marks the layer buffer so that it will
     * be refreshed. If you override this method, and don't call
     * super.repaint(), the layers will not be repainted.
     */
    public void repaint(long tm, int x, int y, int width, int height) {
        Component p = getParent();
        if (p instanceof MapBean) {
            ((MapBean) p).setBufferDirty(true);
            if (Debug.debugging("basic")) {
                Debug.output(getName() + "|Layer: repaint(tm=" + tm + ", x="
                        + x + ", y=" + y + ", width=" + width + ", height="
                        + height + ")");
            }

            // How dangerous is this? Let the MapBean manage the
            // repaint call? Seems to work OK, and lets the buffered
            // MapBeans work better when they are embedded in other
            // components. It's this call here that makes the
            // BufferedLayer work right.

            // This repaint request has been changed to call a
            // specific
            // method on the MapBean, which includes the layer making
            // the request. This is a hook for a policy object in the
            // MapBean to make a decision on whether to honor the
            // request, or to handle it in a different way if the
            // environment dictates that should happen.

            // ((MapBean)p).repaint(); to ->
            ((MapBean) p).repaint(this);
        } else if (p != null) {
            p.repaint(tm, x, y, width, height);
        } else {
            super.repaint(tm, x, y, width, height);
        }
    }

    /**
     * This method is here to provide a default action for Layers as
     * they act as a ProjectionPainter. Normally, ProjectionPainters
     * are expected to receive the projection, gather/create
     * OMGraphics that apply to the projection, and render them into
     * the Graphics provided. This is supposed to be done in the same
     * thread that calls this function, so the caller knows that when
     * this method returns, everything that the ProjectionPainter
     * needed to do is complete.
     * <P>
     * If the layer doesn't override this method, then the
     * paint(Graphics) method will be called.
     *
     * _at_param proj Projection of the map.
     * @param g java.awt.Graphics to draw into.
     */
    public void renderDataForProjection(Projection proj, Graphics g) {
        paint(g);
    }

    /**
     * This method is called when the layer is added to the MapBean
     *
     * _at_param cont Container
     */
    public void added(Container cont) {}

    /**
     * This method is called after the layer is removed from the
     * MapBean and when the projection changes. We recommend that
     * Layers override this method and nullify memory-intensive
     * variables.
     *
     * @param cont Container
     */
    public void removed(Container cont) {}

    /**
     * Part of a layer hack to notify the component listener when the
     * component is hidden. These components don't receive the
     * ComponentHidden notification. Remove when it works.
     */
    protected ListenerSupport localHackList;

    /**
     * Part of a layer hack to notify the component listener when the
     * component is hidden. These components don't receive the
     * ComponentHidden notification. Remove when it works. Set to
     * false to test.
     */
    protected boolean doHack = true;

    /**
     * Part of a layer hack to notify the component listener when the
     * component is hidden. These components don't receive the
     * ComponentHidden notification. Remove when it works.
     */
    public void setVisible(boolean show) {
        super.setVisible(show);
        if (doHack && !show) {
            notifyHideHack();
        }
    }

    /**
     * Part of a layer hack to notify the component listener when the
     * component is hidden. These components don't receive the
     * ComponentHidden notification. Remove when it works.
     */
    public void addComponentListener(ComponentListener cl) {
        super.addComponentListener(cl);
        if (localHackList == null) {
            localHackList = new ListenerSupport(this);
        }
        localHackList.addListener(cl);
    }

    /**
     * Part of a layer hack to notify the component listener when the
     * component is hidden. These components don't receive the
     * ComponentHidden notification. Remove when it works.
     */
    public void removeComponentListener(ComponentListener cl) {
        super.removeComponentListener(cl);
        if (localHackList != null) {
            localHackList.removeListener(cl);
        }
    }

    /**
     * Part of a layer hack to notify the component listener when the
     * component is hidden. These components don't receive the
     * ComponentHidden notification. Remove when it works.
     */
    public void notifyHideHack() {
        if (localHackList == null) {
            return;
        }

        ComponentEvent ce = new ComponentEvent(this, ComponentEvent.COMPONENT_HIDDEN);

        for (Iterator it = localHackList.iterator(); it.hasNext();) {
            ((ComponentListener) it.next()).componentHidden(ce);
        }
    }

    /**
     * Set whether the Layer should be added to the BeanContext.
     */
    public void setAddToBeanContext(boolean set) {
        addToBeanContext = set;
    }

    /**
     * Set whether the Layer should be added to the BeanContext.
     */
    public boolean getAddToBeanContext() {
        return addToBeanContext;
    }

    /**
     * Mark the layer as one that should be considered a background
     * layer. What that means is up to the MapBean or application.
     */
    public void setAddAsBackground(boolean set) {
        addAsBackground = set;
    }

    /**
     * Check to see if the layer is marked as one that should be
     * considered a background layer. What that means is up to the
     * MapBean or application.
     *
     * _at_return true if layer is a background layer.
     */
    public boolean getAddAsBackground() {
        return addAsBackground;
    }

    /**
     * Mark the layer as removable, or one that can be deleted from
     * the application. What that means is up to the LayerHandler or
     * other application components.
     */
    public void setRemovable(boolean set) {
        removable = set;
    }

    /**
     * Check to see if the layer is marked as one that can be removed
     * from an application.
     *
     * @return true if layer should be allowed to be deleted.
     */
    public boolean isRemovable() {
        return removable;
    }

    /**
     * Check to see if the removable layer can be removed now.
     * _at_return true if layer should be allowed to be deleted.
     */
    public boolean removeConfirmed() {
        return true;
    }
    
    /**
     * This is the method that your layer can use to find other
     * objects within the MapHandler (BeanContext). This method gets
     * called when the Layer gets added to the MapHandler, or when
     * another object gets added to the MapHandler after the Layer is
     * a member. If the LayerHandler creates the Layer from
     * properties, the LayerHandler will add the Layer to the
     * BeanContext if Layer.addToBeanContext is true. It is false by
     * default.
     *
     * For Layers, this method doesn't do anything by default. If you
     * need your layer to get ahold of another object, then you can
     * use the Iterator to go through the objects to look for the one
     * you need.
     */
    public void findAndInit(Iterator it) {
        while (it.hasNext()) {
            findAndInit(it.next());
        }
    }

    /**
     * This method is called by the findAndInit(Iterator) method, once
     * for every object inside the iterator. It's here to allow
     * subclasses a way to receive objects and still let the super
     * classes have a shot at the object. So, you can override this
     * method can call super.findAndInit(obj), or override the
     * findAndInit(Iterator) method and call super.findAndInit(obj).
     * Whatever.
     */
    public void findAndInit(Object obj) {}

    /**
     * BeanContextMembershipListener method. Called when a new object
     * is added to the BeanContext of this object.
     */
    public void childrenAdded(BeanContextMembershipEvent bcme) {
        findAndInit(bcme.iterator());
    }

    /**
     * BeanContextMembershipListener method. Called when a new object
     * is removed from the BeanContext of this object. For the Layer,
     * this method doesn't do anything. If your layer does something
     * with the childrenAdded method, or findAndInit, you should take
     * steps in this method to unhook the layer from the object used
     * in those methods.
     */
    public void childrenRemoved(BeanContextMembershipEvent bcme) {
        Iterator it = bcme.iterator();
        while (it.hasNext()) {
            findAndUndo(it.next());
        }
    }

    /**
     * This is the method that does the opposite as the
     * findAndInit(Object). Lets you call super classes with objects
     * that need to be removed.
     */
    public void findAndUndo(Object obj) {}

    /** Method for BeanContextChild interface. */
    public BeanContext getBeanContext() {
        return beanContextChildSupport.getBeanContext();
    }

    /**
     * Method for BeanContextChild interface. Gets an iterator from
     * the BeanContext to call findAndInit() over.
     */
    public void setBeanContext(BeanContext in_bc) throws PropertyVetoException {

        if (in_bc != null) {
            connectToBeanContext(in_bc);
            findAndInit(in_bc.iterator());
        }
    }

    /**
     * Layer method to just connect to the BeanContext, without
     * grabbing the interator as in setBeanContext(). Good for
     * protected sub-layers where you want to optimize the calling of
     * the findAndInit() method over them.
     */
    public void connectToBeanContext(BeanContext in_bc)
            throws PropertyVetoException {

        if (in_bc != null) {
            in_bc.addBeanContextMembershipListener(this);
            beanContextChildSupport.setBeanContext(in_bc);
        }
    }

    /**
     * Method for BeanContextChild interface. Uses the
     * BeanContextChildSupport to add a listener to this object's
     * property. This listener wants to have the right to veto a
     * property change.
     */
    public void addVetoableChangeListener(String propertyName,
                                          VetoableChangeListener in_vcl) {
        beanContextChildSupport.addVetoableChangeListener(propertyName, in_vcl);
    }

    /**
     * Method for BeanContextChild interface. Uses the
     * BeanContextChildSupport to remove a listener to this object's
     * property. The listener has the power to veto property changes.
     */
    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>
     *
     * _at_param name The programmatic name of the property that is about
     * to change
     *
     * _at_param oldValue The old value of the property
     * _at_param newValue - The new value of the property
     *
     * _at_throws PropertyVetoException if the recipient wishes the
     * property change to be rolled back.
     */
    public void fireVetoableChange(String name, Object oldValue, Object newValue)
            throws PropertyVetoException {
        super.fireVetoableChange(name, oldValue, newValue);
        beanContextChildSupport.fireVetoableChange(name, oldValue, newValue);
    }

    public void clearListeners() {
        if (localHackList != null) {
            localHackList.removeAll();
        }
        if (IDListeners != null) {
            IDListeners.removeAll();
        }
        if (lsListeners != null) {
            lsListeners.removeAll();
        }

        BeanContext bc = getBeanContext();
        if (bc != null) {
            bc.removeBeanContextMembershipListener(this);
        }
    }

    public void finalize() {
        if (Debug.debugging("gc")) {
            Debug.output("Layer |" + getName() + " |: getting GC'd");
        }
    }

    /**
     * Fire a component event to the Layer component listeners, with
     * the palette as the component, letting them know if it's visible
     * or not.
     */
    public void firePaletteEvent(ComponentEvent event) {
        if (localHackList == null) {
            return;
        }

        palette = (Container) event.getSource();
        int eventType = event.getID();
        for (Iterator it = localHackList.iterator(); it.hasNext();) {
            ComponentListener target = (ComponentListener) it.next();
            if (eventType == ComponentEvent.COMPONENT_HIDDEN) {
                target.componentHidden(event);
            } else if (eventType == ComponentEvent.COMPONENT_SHOWN) {
                target.componentShown(event);
            }
        }

        if (eventType == ComponentEvent.COMPONENT_HIDDEN) {
            palette = null;
        }
    }

    /**
     * Return the JDialog, or JInternalFrame, that serves as the
     * palette for the layer. May be null.
     */
    public Container getPalette() {
        return palette;
    }

    /**
     * Called when something about the layer has changed that would
     * require the palette to be reconfigured. Will cause getGUI() to
     * be called again. You should take steps before calling this
     * method to make sure that the getGUI() method is ready to
     * recreate the palette components from scratch if needed.
     */
    protected void resetPalette() {
        java.awt.Container pal = getPalette();
        boolean putUp = false;
        if (pal != null && pal.isVisible()) {
            putUp = true;
            setPaletteVisible(false);
        }

        if (putUp) {
            setPaletteVisible(true);
        }
    }

    /**
     * Make the palette visible or not, destroy if invisible.
     */
    public void setPaletteVisible(boolean visible) {
        if (visible) {
            showPalette();
        } else {
            hidePalette();
        }
    }

    /**
     * Set the WindowSupport object handling the palette.
     */
    public void setWindowSupport(WindowSupport ws) {
        windowSupport = ws;
    }

    /**
     * Get the WindowSupport object handling the palette.
     */
    public WindowSupport getWindowSupport() {
        return windowSupport;
    }

    protected WindowSupport createWindowSupport() {
        return new ScrollPaneWindowSupport(getGUI(), getName());
    }

    /**
     * Make the palette visible. Will automatically determine if we're
     * running in an applet environment and will use a JInternalFrame
     * over a JFrame if necessary.
     */
    public void showPalette() {

        WindowSupport ws = getWindowSupport();
        if (ws == null) {
            ws = createWindowSupport();
            paletteListener = new ComponentAdapter() {
                public void componentShown(ComponentEvent e) {
                    firePaletteEvent(e);
                }

                public void componentHidden(ComponentEvent e) {
                    firePaletteEvent(e);
                }
            };
            setWindowSupport(ws);
        } else {
            ws.setTitle(getName());
            ws.setContent(getGUI());
        }

        if (ws != null) {
            MapHandler mh = (MapHandler) getBeanContext();
            Frame frame = null;
            if (mh != null) {
                frame = (Frame) mh.get(java.awt.Frame.class);

                if (frame == null) {
                    MapBean mapBean = (MapBean) mh.get("com.bbn.openmap.MapBean");
                    if (mapBean == null) {
                        Debug.message("layer",
                                "Layer.showPalette: Warning...mapBean = null");
                    } else {
                        try {
                            java.awt.Component parent = mapBean.getParent();
                            while (parent.getParent() != null
                                    && !(parent instanceof java.awt.Frame)) {
                                parent = parent.getParent();
                            }

                            if (parent instanceof java.awt.Frame) {
                                frame = (java.awt.Frame) parent;
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                        } // ignore any problems here
                    }
                }
            }

            if (paletteListener != null) {
                ws.addComponentListener(paletteListener);
            }
            ws.displayInWindow(frame);
        }
    }

    /**
     * Hide the layer's palette.
     */
    public void hidePalette() {
        WindowSupport ws = getWindowSupport();
        if (ws != null) {
            ws.killWindow();
        }
    }

    /**
     * The default actionPerformed method for Layer. Make sure you
     * call super.actionPerformed if you care about receiving palette
     * show/hide commands. This method is also set up to receive the
     * DisplayPropertiesCmd, and will bring up the Inspector for the
     * layer.
     */
    public void actionPerformed(ActionEvent ae) {
        String command = ae.getActionCommand();
        if (command == DisplayPaletteCmd) {
            if (Debug.debugging("layer")) {
                Debug.output(getName() + " displaying palette");
            }
            showPalette();
        } else if (command == HidePaletteCmd) {
            if (Debug.debugging("layer")) {
                Debug.output(getName() + " hiding palette");
            }
            hidePalette();
        } else if (command == DisplayPropertiesCmd) {
            Inspector inspector = new Inspector();
            inspector.inspectPropertyConsumer(this);
        }
    }
}

// **********************************************************************
//
// <copyright>
//
// BBN Technologies, a Verizon Company
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/openmap/openmap/src/openmap/com/bbn/openmap/plugin/AbstractPlugIn.java,v $
// $RCSfile: AbstractPlugIn.java,v $
// $Revision: 1.7 $
// $Date: 2004/02/04 22:51:38 $
// $Author: dietrick $
//
// **********************************************************************


package com.bbn.openmap.plugin;

import java.awt.Component;
import java.awt.event.MouseEvent;
import java.util.Properties;

import com.bbn.openmap.Environment;
import com.bbn.openmap.I18n;
import com.bbn.openmap.Layer;
import com.bbn.openmap.PropertyConsumer;
import com.bbn.openmap.event.MapMouseListener;
import com.bbn.openmap.event.SelectMouseMode;
import com.bbn.openmap.layer.util.LayerUtils;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;

/**
 * This class is an abstract implementation of the PlugIn. It takes
 * care of setting up the layer, setting properties, etc.
 * _at_see com.bbn.openmap.plugin.PlugInLayer
 * _at_see com.bbn.openmap.plugin.PlugIn
 */
public abstract class AbstractPlugIn
implements PlugIn, PropertyConsumer, MapMouseListener {
    
    /**
     * Property 'removeable' to designate this layer as removeable
     * from the application, or able to be deleted. True by default.
     */
    public static final String RemovableProperty = "removable";
    
    /**
     * Flag to designate the layer as removeable or not.
     */
    protected boolean removable = true;
    
    /** The parent component, usually the PlugInLayer. */
    protected Component component = null;
    /** The prefix for the plugin's properties. */
    protected String prefix = null;
    /**
     * The pretty name for a plugin, if it was set in the properties.
     */
    protected String name = this.getClass().getName();
    
    /**
     * The object handling mouse events for the plugin. By default,
     * the Plugin is it, but it doesn't have to be.
     */
    protected MapMouseListener mml = this;
    
    /**
     * Flag to denote whether the plugin should be added to the bean
     * context (MapHandler). True by default.
     */
    protected boolean addToBeanContext = true;
    
    /**
     * Internationalization
     */
    public I18n i18n = Environment.getI18n();
    
    public AbstractPlugIn() {}
    
    public AbstractPlugIn(Component comp) {
        setComponent(comp);
    }
    
    /**
     * Set the name of the plugin. If the parent component is a
     * layer, set its pretty name as well.
     */
    public void setName(String name) {
        this.name = name;
        Component comp = getComponent();
        if (comp != null) {
            comp.setName(name);
        }
    }
    
    /**
     * Get the pretty name of the plugin, which is really the pretty
     * name of the parent component if it's set.
     */
    public String getName() {
        Component comp = getComponent();
        if (comp != null) {
            name = comp.getName();
        }
        return name;
    }
    
    /**
     * Set the component that this PlugIn uses as a grip to the map.
     */
    public void setComponent(Component comp) {
        this.component = comp;
    }
    
    /**
     * Get the component that this plugin uses as a grip to the map.
     */
    public Component getComponent() {
        return component;
    }
    
    /**
     * Call repaint on the parent component.
     */
    public void repaint() {
        component.repaint();
    }
    
    /**
     * Checks to see if the parent component is a PlugInLayer, and
     * calls doPrepare() on it if it is.
     */
    public void doPrepare() {
        if (component instanceof PlugInLayer) {
            ((PlugInLayer)component).doPrepare();
        }
    }
    
    /**
     * Set the MapMouseListener for this PlugIn. The MapMouseListener
     * is responsible for handling the MouseEvents that are occuring
     * over the layer using the PlugIn, as well as being able to let
     * others know which MouseModes are of interest to receive
     * MouseEvents from.
     *
     * _at_param mml MapMouseListener.
     */
    public void setMapMouseListener(MapMouseListener mml) {
        this.mml = mml;
    }
    
    /**
     * Returns the MapMouseListener that the plugin thinks should be used.
     */
    public MapMouseListener getMapMouseListener() {
        return mml;
    }
    
    /**
     * The getRectangle call is the main call into the PlugIn module.
     * The module is expected to fill a graphics list with objects
     * that are within the screen parameters passed. It's assumed that
     * the PlugIn will call generate(projection) on the OMGraphics
     * returned! If you don't call generate on the OMGraphics, they
     * will not be displayed on the map.
     *
     * _at_param p projection of the screen, holding scale, center
     * coords, height, width. May be null if the parent component
     * hasn't been given a projection.
     */
    public abstract OMGraphicList getRectangle(Projection p);
    
    /**
     */
    public Component getGUI() {
        return null;
    }
    
    public void setAddToBeanContext(boolean value) {
        addToBeanContext = value;
    }
    
    public boolean getAddToBeanContext() {
        return addToBeanContext;
    }
    
    /**
     * Mark the plugin (and layer) as removeable, or one that can be deleted from
     * the application. What that means is up to the LayerHandler or
     * other application components.
     */
    public void setRemovable(boolean set) {
        this.removable = set;
        Component comp = getComponent();
        if ((comp != null) && (comp instanceof Layer)) {
            ((Layer)comp).setRemovable(set);
        }
    }
    
    /**
     * Check to see if the plugin (and layer) is marked as one that can be removed
     * from an application.
     * _at_return true if plugin should be allowed to be deleted.
     */
    public boolean isRemovable() {
        Component comp = getComponent();
        if ((comp != null) && (comp instanceof Layer)) {
            this.removable = ((Layer)comp).isRemovable();
        }
        return removable;
    }
    
    ////// PropertyConsumer Interface Methods
    
    /**
     * Method to set the properties in the PropertyConsumer. It is
     * assumed that the properties do not have a prefix associated
     * with them, or that the prefix has already been set.
     *
     * _at_param setList a properties object that the PropertyConsumer
     * can use to retrieve expected properties it can use for
     * configuration.
     */
    public void setProperties(Properties setList) {
        setProperties(null, setList);
    }
    
    /**
     * Method to set the properties in the PropertyConsumer. The
     * prefix is a string that should be prepended to each property
     * key (in addition to a separating '.') in order for the
     * PropertyConsumer to uniquely identify properies meant for it, in
     * the midst of of Properties meant for several objects.
     *
     * _at_param prefix a String used by the PropertyConsumer to prepend
     * to each property value it wants to look up -
     * setList.getProperty(prefix.propertyKey). If the prefix had
     * already been set, then the prefix passed in should replace that
     * previous value.
     * _at_param setList a Properties object that the PropertyConsumer
     * can use to retrieve expected properties it can use for
     * configuration.
     */
    public void setProperties(String prefix, Properties setList) {
        setPropertyPrefix(prefix);
        
        String realPrefix = PropUtils.getScopedPropertyPrefix(prefix);
        
        name = setList.getProperty(realPrefix + Layer.PrettyNameProperty);
        setAddToBeanContext(LayerUtils.booleanFromProperties(setList, realPrefix + Layer.AddToBeanContextProperty, addToBeanContext));
        setRemovable(PropUtils.booleanFromProperties(setList, realPrefix + RemovableProperty, removable));
    }
    
    /**
     * Method to fill in a Properties object, reflecting the current
     * values of the PropertyConsumer. If the PropertyConsumer has a
     * prefix set, the property keys should have that prefix plus a
     * separating '.' prepended to each propery key it uses for
     * configuration.
     *
     * _at_param getList a Properties object to load the PropertyConsumer
     * properties into. If getList equals null, then a new Properties
     * object should be created.
     * _at_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 getList) {
        if (getList == null) {
            getList = new Properties();
        }
        
        String realPrefix = PropUtils.getScopedPropertyPrefix(this);
        getList.put(realPrefix + Layer.AddToBeanContextProperty,
        new Boolean(addToBeanContext).toString());
        getList.put(prefix + RemovableProperty, new Boolean(removable).toString());
        return getList;
    }
    
    /**
     * 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.).
     *
     * _at_param list a Properties object to load the PropertyConsumer
     * properties into. If getList equals null, then a new Properties
     * object should be created.
     * _at_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();
        }
        list.put(Layer.AddToBeanContextProperty, "Flag to give the PlugIn access to all of the other application components.");
        list.put(Layer.AddToBeanContextProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
        
        String internString = i18n.get(AbstractPlugIn.class, RemovableProperty, I18n.TOOLTIP, "Flag to allow layer to be deleted.");
        list.put(RemovableProperty, internString);
        internString = i18n.get(Layer.class, RemovableProperty, "Removeable");
        list.put(RemovableProperty + LabelEditorProperty, internString);
        list.put(RemovableProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
        
        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) {
        this.prefix = prefix;
    }
    
    /**
     * Get the property key prefix that is being used to prepend to
     * the property keys for Properties lookups.
     *
     * _at_return the property prefix for the plugin.
     */
    public String getPropertyPrefix() {
        return prefix;
    }
    
    ///////// MapMouseListener interface methods
    
    /**
     * Return a list of the modes that are interesting to the
     * MapMouseListener. The source MouseEvents will only get sent to
     * the MapMouseListener if the mode is set to one that the
     * listener is interested in.
     * Layers interested in receiving events should register for
     * receiving events in "select" mode:
     * <code>
     * <pre>
     * return new String[] {
     * SelectMouseMode.modeID
     * };
     * </pre>
     * <code>
     * _at_return String[] of modeID's
     * _at_see com.bbn.openmap.event.NavMouseMode#modeID
     * _at_see com.bbn.openmap.event.SelectMouseMode#modeID
     * _at_see com.bbn.openmap.event.NullMouseMode#modeID
     */
    public String[] getMouseModeServiceList() {
        return new String[] {SelectMouseMode.modeID};
    }
    
    // Mouse Listener events
    ////////////////////////
    
    /**
     * Invoked when a mouse button has been pressed on a component.
     * _at_param e MouseEvent
     * _at_return true if the listener was able to process the event.
     */
    public boolean mousePressed(MouseEvent e) {
        return false;
    }
    
    /**
     * Invoked when a mouse button has been released on a component.
     * _at_param e MouseEvent
     * _at_return true if the listener was able to process the event.
     */
    public boolean mouseReleased(MouseEvent e) {
        return false;
    }
    
    /**
     * Invoked when the mouse has been clicked on a component.
     * The listener will receive this event if it successfully
     * processed <code>mousePressed()</code>, or if no other listener
     * processes the event. If the listener successfully processes
     * <code>mouseClicked()</code>, then it will receive the next
     * <code>mouseClicked()</code> notifications that have a click
     * count greater than one.
     * <p>
     * NOTE: We have noticed that this method can sometimes be
     * erroneously invoked. It seems to occur when a light-weight AWT
     * component (like an internal window or menu) closes (removes
     * itself from the window hierarchy). A specific OpenMap example
     * is when you make a menu selection when the MenuItem you select
     * is above the MapBean canvas. After making the selection, the
     * mouseClicked() gets invoked on the MouseDelegator, which passes
     * it to the appropriate listeners depending on the MouseMode.
     * The best way to avoid this problem is to not implement anything
     * crucial in this method. Use a combination of
     * <code>mousePressed()</code> and <code>mouseReleased()</code>
     * instead.
     * _at_param e MouseEvent
     * _at_return true if the listener was able to process the event.
     */
    public boolean mouseClicked(MouseEvent e) {
        return false;
    }
    
    /**
     * Invoked when the mouse enters a component.
     * _at_param e MouseEvent
     */
    public void mouseEntered(MouseEvent e) {}
    
    /**
     * Invoked when the mouse exits a component.
     * _at_param e MouseEvent
     */
    public void mouseExited(MouseEvent e) {}
    
    // Mouse Motion Listener events
    ///////////////////////////////
    
    /**
     * Invoked when a mouse button is pressed on a component and then
     * dragged. The listener will receive these events if it
     * successfully processes mousePressed(), or if no other listener
     * processes the event.
     * @param e MouseEvent
     * _at_return true if the listener was able to process the event.
     */
    public boolean mouseDragged(MouseEvent e) {
        return false;
    }
    
    /**
     * Invoked when the mouse button has been moved on a component
     * (with no buttons down).
     * _at_param e MouseEvent
     * @return true if the listener was able to process the event.
     */
    public boolean mouseMoved(MouseEvent e) {
        return false;
    }
    
    /**
     * Handle a mouse cursor moving without the button being pressed.
     * This event is intended to tell the listener that there was a
     * mouse movement, but that the event was consumed by another
     * layer. This will allow a mouse listener to clean up actions
     * that might have happened because of another motion event
     * response.
     */
    public void mouseMoved() {}
    
    /**
     * Method that gets called when the PlugInLayer has been removed
     * from the map, so the PlugIn can free up resources.
     */
    public void removed() {}
    
}

// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/openmap/openmap/src/openmap/com/bbn/openmap/layer/rpf/RpfLayer.java,v $
// $RCSfile: RpfLayer.java,v $
// $Revision: 1.13 $
// $Date: 2004/10/14 18:18:20 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.layer.rpf;

/* Java Core */
import java.awt.Point;
import java.awt.event.*;
import java.io.*;
import java.util.Properties;
import java.util.Vector;
import javax.swing.*;
import javax.swing.event.*;

/* OpenMap */
import com.bbn.openmap.event.*;
import com.bbn.openmap.layer.OMGraphicHandlerLayer;
import com.bbn.openmap.layer.util.cacheHandler.CacheHandler;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.proj.*;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PaletteHelper;
import com.bbn.openmap.util.PropUtils;

/**
 * The RpfLayer fills the screen with RPF data. There is also a tool
 * available that allows you to see the coverage of the available
 * data. To view theimages, the projection of the map has to be set in
 * the ARC projection, which OpenMap calls the CADRG projection. The
 * RpfLayer can use several RPF directories at the same time, and
 * doesn't require that the data actually be there at runtime. That
 * way, you can give a location where the data may be mouted during
 * runtime(i.e. CDROM) and the layer will still use the data. The
 * scale of the projection does not necessarily have to match the
 * scale of a map series for that series to be displayed. There are
 * options, set in the RpfViewAttributes, that allow scaling of the
 * RPF images to match the map scale.
 * <P>
 *
 * The RpfLayer uses the RpfCacheManager to get the images it needs to
 * display. Whenever the projection changes, the cache manager takes
 * the new projection and creates a OMGraphicList with the new image
 * frames and attribute text.
 * <P>
 *
 * The RpfLayer gets its intial settings from properties. This should
 * be done right after the RpfLayer is created. The properties list
 * contains the location of the RPF directories, the opaqueness of the
 * images, the number of colors to use, and whether to show the images
 * and/or attributes by default. An example of the RpfLayer
 * properties:
 * <P>
 *
 * <pre>
 *
 * #-----------------------------
 * # Properties for RpfLayer
 * #-----------------------------
 * # Mandatory properties
 * # This property should reflect the paths to the RPF directories
 * rpf.paths=/usr/local/matt/data/RPF /usr/local/matt/data/CIB/RPF
 *
 * # Optional Properties - the default will be set if these are not
 * # included in the properties file:
 * # Number between 0-255: 0 is transparent, 255 is opaque. 255 is default.
 * rpf.opaque=128
 *
 * # Number of colors to use on the maps - 16, 32, 216. 216 is default.
 * rpf.numberColors=216
 *
 * # Display maps on startup. Default is true.
 * rpf.showMaps=true
 *
 * # Display attribute information on startup. Default is false.
 * rpf.showInfo=false
 *
 * # Scale charts to match display scale. Default is true.
 * rpf.scaleImages=true
 *
 * # The scale factor to allow when scaling images (2x, 4x, also mean 1/2, 1/4). Default is 4.
 * rpf.imageScaleFactor=4
 *
 * # Delete the cache if the layer is removed from the map. Default is false.
 * rpf.killCache=true
 * # Limit the display to the chart code specified. (GN, JN, ON, TP, etc.).
 * # Default is ANY
 * rpf.chartSeries=ANY
 * # Get the subframe attribute data from the Frame provider.
 * rpf.autofetchAttributes=false
 * # Set to true if you want the coverage tool available.
 * rpf.coverage=true
 * # Set the subframe cache size. (Number of subframes to hold on to, 256x256 pixels)
 * rpf.subframeCacheSize=128
 * # Then also include coverage properties, which are available in the RpfConstants.
 * #------------------------------------
 * # End of properties for RpfLayer
 * #------------------------------------
 *
 * </pre>
 *
 */
public class RpfLayer extends OMGraphicHandlerLayer implements ActionListener,
        RpfConstants, Serializable {

    /**
     * The main source for the images and attribute information. All
     * requests for graphic objects should go through this cache, and
     * it will automatically handle getting the frame files, decoding
     * them, and returning an object list.
     */
    protected transient RpfCacheManager cache = null;
    /** The paths to the RPF directories, telling where the data is. */
    protected String[] paths;
    /**
     * The display attributes for the maps. This object should not be
     * replaced, because the caches all look at it, too. Just adjust
     * the parameters within it.
     *
     * @see RpfViewAttributes
     */
    protected RpfViewAttributes viewAttributes;
    /** Flag to delete the cache if the layer is removed from the map. */
    protected boolean killCache = true;
    /** The supplier of frame data. */
    protected RpfFrameProvider frameProvider;
    /** The coverage tool for the layer. */
    protected RpfCoverage coverage;
    /** Subframe cache size. Default is 40. */
    protected int subframeCacheSize;
    /** Auxillary subframe cache size. Default is 10. */
    protected int auxSubframeCacheSize;

    /**
     * The default constructor for the Layer. All of the attributes
     * are set to their default values. Use this construct if you are
     * going to use a standard properties file, which will set the
     * paths.
     */
    public RpfLayer() {
        setName("RPF");
        viewAttributes = new RpfViewAttributes();
        setProjectionChangePolicy(new com.bbn.openmap.layer.policy.ListResetPCPolicy(this));
    }

    /**
     * The default constructor for the Layer. All of the attributes
     * are set to their default values.
     *
     * @param pathsToRPFDirs paths to the RPF directories that hold
     * A.TOC files.
     */
    public RpfLayer(String[] pathsToRPFDirs) {
        this();
        setPaths(pathsToRPFDirs);
    }

    /**
     * Set the paths to the RPF directories, which are by default the
     * parents of the A.TOC table of contents files. Creates the
     * RpfFrameProvider.
     *
     * _at_param pathsToRPFDirs Array of strings that list the paths to
     * RPF directories.
     */
    public void setPaths(String[] pathsToRPFDirs) {
        if (pathsToRPFDirs != null) {
            setFrameProvider(new RpfFrameCacheHandler(pathsToRPFDirs));
        } else {
            Debug.output("RpfLayer: Need RPF directory paths.");
            frameProvider = null;
        }
        paths = pathsToRPFDirs;
        this.cache = null;
    }

    /**
     * Get the paths to the RPF directories.
     *
     * _at_return String[]
     */
    public String[] getPaths() {
        return paths;
    }

    /**
     * Called when the layer is no longer part of the map. In this
     * case, we should disconnect from the server if we have a link.
     */
    public void removed(java.awt.Container cont) {
        if (killCache) {
            Debug.message("rpf", "RpfLayer: emptying cache!");
            clearCache();
        }

        // need to reset this for when it gets added again, if it was
        // removed without the projection actually changing. This
        // helps when the cache needs to be rebuilt.
        setProjection((Projection) null);
    }

    protected void setDefaultValues() {
        // defaults
        paths = null;
    }

    /**
     * Set all the RPF properties from a properties object.
     */
    public void setProperties(String prefix, java.util.Properties properties) {

        super.setProperties(prefix, properties);

        prefix = PropUtils.getScopedPropertyPrefix(prefix);

        paths = PropUtils.initPathsFromProperties(properties, prefix
                + RpfPathsProperty, paths);

        viewAttributes.setProperties(prefix, properties);

        subframeCacheSize = PropUtils.intFromProperties(properties, prefix
                + CacheSizeProperty, RpfCacheHandler.SUBFRAME_CACHE_SIZE);

        auxSubframeCacheSize = PropUtils.intFromProperties(properties, prefix
                + CacheSizeProperty, RpfCacheManager.SMALL_CACHE_SIZE);

        if (viewAttributes.chartSeries == null)
            viewAttributes.chartSeries = RpfViewAttributes.ANY;

        killCache = PropUtils.booleanFromProperties(properties, prefix
                + KillCacheProperty, true);

        if (PropUtils.booleanFromProperties(properties, prefix
                + CoverageProperty, false)) {
            setCoverage(new RpfCoverage(this));
            coverage.setProperties(prefix, properties);
        }

    }

    /**
     * PropertyConsumer method, to fill in a Properties object,
     * reflecting the current values of the layer. If the layer has a
     * propertyPrefix set, the property keys should have that prefix
     * plus a separating '.' prepended to each propery key it uses for
     * configuration.
     *
     * _at_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) {
        props = super.getProperties(props);
        String prefix = PropUtils.getScopedPropertyPrefix(this);

        // find out paths...
        String[] p = getPaths();
        StringBuffer pathString = new StringBuffer();
        if (p != null) {
            for (int i = 0; i < p.length; i++) {
                if (p[i] != null) {
                    pathString.append(p[i]);
                    if (i < p.length - 1) {
                        pathString.append(";"); // separate paths with
                                                // ;
                    }
                }
            }
            props.put(prefix + RpfPathsProperty, pathString.toString());
        } else {
            props.put(prefix + RpfPathsProperty, "");
        }

        props.put(prefix + KillCacheProperty, new Boolean(killCache).toString());
        props.put(prefix + CoverageProperty,
                new Boolean(coverage == null).toString());
        props.put(prefix + CacheSizeProperty,
                Integer.toString(subframeCacheSize));
        props.put(prefix + AuxCacheSizeProperty,
                Integer.toString(auxSubframeCacheSize));

        viewAttributes.setPropertyPrefix(prefix);
        viewAttributes.getProperties(props);

        if (coverage == null) {
            RpfCoverage cov = new RpfCoverage(this);
            cov.setProperties(prefix, new Properties());
            cov.getProperties(props);
        } else {
            coverage.getProperties(props);
        }

        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.
     *
     * _at_param list a Properties object to load the PropertyConsumer
     * properties into. If getList equals null, then a new
     * Properties object should be created.
     * _at_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) {
        list = super.getPropertyInfo(list);

        list.put(RpfPathsProperty,
                "Paths to RPF directories. Semi-colon separated paths");
        list.put(RpfPathsProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.MultiDirectoryPropertyEditor");

        list.put(KillCacheProperty,
                "Flag to trigger the cache to be cleared when layer is removed from the map.");
        list.put(KillCacheProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.OnOffPropertyEditor");

        list.put(CoverageProperty,
                "Flag that adds the coverage tool to the layer.");
        list.put(CoverageProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.OnOffPropertyEditor");

        list.put(CacheSizeProperty,
                "Number of frames to hold in the frame cache.");
        list.put(AuxCacheSizeProperty,
                "Number of subframes to hold in the subframe cache.");

        viewAttributes.getPropertyInfo(list);

        RpfCoverage tmpCov = coverage;
        if (tmpCov == null) {
            tmpCov = new RpfCoverage(this);
        }

        tmpCov.getPropertyInfo(list);

        list.put(initPropertiesProperty, RpfPathsProperty + " "
                + KillCacheProperty + " " + CacheSizeProperty + " "
                + AuxCacheSizeProperty + " "
                + viewAttributes.getInitPropertiesOrder() + " "
                + AddToBeanContextProperty + " " + AddAsBackgroundProperty
                + " " + RemovableProperty + " " + CoverageProperty + " "
                + tmpCov.getInitPropertiesOrder());

        return list;
    }

    /**
     * Sets the current graphics list to the given list.
     *
     * @param aList a list of OMGraphics.
     */
    public synchronized void setGraphicList(OMGraphicList aList) {
        setList(aList);
    }

    /** Retrieves the current graphics list. */
    public synchronized OMGraphicList getGraphicList() {
        return getList();
    }

    /**
     * Clear the frame cache.
     */
    public void clearCache() {

        if (frameProvider instanceof CacheHandler) {
            ((CacheHandler) frameProvider).resetCache();
        }

        if (this.cache != null) {
            this.cache.setViewAttributes(null);
            this.cache.setFrameProvider(null);
            this.cache.clearCaches();
        }

        frameProvider = null;

        setGraphicList(null);
        this.cache = null;
    }

    /**
     * Set the view attributes for the layer. The frame provider view
     * attributes are updated, and the cache is cleared.
     *
     * _at_param rva the RpfViewAttributes used for the layer.
     */
    public void setViewAttributes(RpfViewAttributes rva) {
        viewAttributes = rva;
        if (this.cache != null) {
            this.cache.setViewAttributes(rva);
        }
    }

    /**
     * Get the view attributes or the layer.
     *
     * _at_return RpfViewAttributes.
     */
    public RpfViewAttributes getViewAttributes() {
        return viewAttributes;
    }

    /**
     * Set the RpfCoverage tool used by the layer. If the view
     * attributes chart series setting is not equal to
     * RpfViewAttributes.ANY, then the palette of the tool is not
     * shown.
     *
     * @param cov the RpfCoverage tool.
     */
    public void setCoverage(RpfCoverage cov) {
        coverage = cov;
        if (viewAttributes != null
                && coverage != null
                && !viewAttributes.chartSeries.equalsIgnoreCase(RpfViewAttributes.ANY)) {
            coverage.setShowPalette(false);
        }
    }

    /**
     * Return the coverage tool used by the layer.
     *
     * _at_return RpfCoverage tool.
     */
    public RpfCoverage getCoverage() {
        return coverage;
    }

    /**
     * Set the RpfFrameProvider for the layer. Clears out the cache,
     * and the frame provider gets the RpfViewAttributes held by the
     * layer.
     *
     * _at_param fp the frame provider.
     */
    public void setFrameProvider(RpfFrameProvider fp) {
        frameProvider = fp;
        if (this.cache != null) {
            this.cache.setFrameProvider(frameProvider);
        }
    }

    /**
     * Return RpfFrameProvider used by the layer.
     */
    public RpfFrameProvider getFrameProvider() {
        return frameProvider;
    }

    /**
     * Returns the Vector containing RpfCoverageBoxes that was
     * returned from the RpfFrameProvider as a result of the last
     * setCache call. These provide rudimentary knowledge about what
     * is being displayed. This vector is from the primary cache
     * handler.
     *
     * _at_return Vector of RpfCoverageBoxes.
     */
    public Vector getCoverageBoxes() {
        return this.cache.getCoverageBoxes();
    }

    /**
     * The projectionListener interface method that lets the Layer
     * know when the projection has changes, and therefore new
     * graphics have to created /supplied for the screen.
     *
     * _at_param e The projection event, most likely fired from a map
     * bean.
     */
    public void projectionChanged(ProjectionEvent e) {
        projectionChanged(e, false);
    }

    /**
     * Called from projectionListener interface method that lets the
     * Layer know when the projection has changes, and therefore new
     * graphics have to created /supplied for the screen.
     *
     * _at_param e The projection event, most likely fired from a map
     * bean.
     * _at_param saveGraphicsForRedraw flag to test for whether the scale
     * and zone has changed for the projection. If true, and
     * the scale and zone is the same, we'll just reproject and
     * redraw the current frames before getting new ones, to
     * fake something happening quickly.
     */
    public void projectionChanged(ProjectionEvent e,
                                  boolean saveGraphicsForRedraw) {
        Debug.message("basic", getName() + "|RpfLayer.projectionChanged()");

        // Need to grab a copy of the old projection in case
        // saveGraphicsForRedraw is true and the projection changes,
        // so we can test to see if the zone and scale have changed,
        // testing for reuse of current frames.
        Projection oldProj = getProjection();
        Projection newProj = setProjection(e);

        if (newProj == null) {
            // Projection didn't change, nothing to do, already have
            // good graphics and just need to paint...
            repaint();
            return;
        }

        if (saveGraphicsForRedraw && oldProj instanceof CADRG
                && newProj instanceof CADRG) {

            CADRG cadrg1 = (CADRG) oldProj;
            CADRG cadrg2 = (CADRG) newProj;
            if (cadrg1.getScale() != cadrg2.getScale()
                    || cadrg1.getZone() != cadrg2.getZone()) {
                setGraphicList(null);
            }
            // else set graphic list escapes deletion...
        } else {
            setGraphicList(null);
        }

        doPrepare();
    }

    /**
     * Prepares the graphics for the layer. This is where the
     * getRectangle() method call is made on the rpf.
     * <p>
     * Occasionally it is necessary to abort a prepare call. When this
     * happens, the map will set the cancel bit in the LayerThread,
     * (the thread that is running the prepare). If this Layer needs
     * to do any cleanups during the abort, it should do so, but
     * return out of the prepare asap.
     *
     * _at_return graphics list of images and attributes.
     */
    public synchronized OMGraphicList prepare() {

        if (isCancelled()) {
            Debug.message("rpf", getName() + "|RpfLayer.prepare(): aborted.");
            return null;
        }

        if (frameProvider == null) {
            // Assuming running locally - otherwise the
            // frameProvider should be set before we get here,
            // like in setProperties or in the constructor.
            setPaths(paths);
            if (frameProvider == null) {
                // Doh! no paths were set!
                Debug.error(getName()
                        + "|RpfLayer.prepare(): null frame provider - either no RPF paths were set, or no frame provider was assigned. The RpfLayer has no way to get RPF data.");
                return new OMGraphicList();
            }
        }

        if (this.cache == null) {
            Debug.message("rpf", getName() + "|RpfLayer: Creating cache!");
            this.cache = new RpfCacheManager(frameProvider, viewAttributes, subframeCacheSize, auxSubframeCacheSize);
        }

        Projection projection = getProjection();

        if (coverage != null && coverage.isInUse()) {
            coverage.prepare(frameProvider,
                    projection,
                    viewAttributes.chartSeries);
        }

        // Check to make sure the projection is CADRG
        if (!(projection instanceof EqualArc)
                && (viewAttributes.showMaps || viewAttributes.showInfo)) {
            fireRequestInfoLine("RpfLayer requires an Equal Arc projection (CADRG/LLXY) for images or attributes!");
            return null;
        }

        Debug.message("basic", getName() + "|RpfLayer.prepare(): doing it");

        // Setting the OMGraphicsList for this layer. Remember, the
        // OMGraphicList is made up of OMGraphics, which are generated
        // (projected) when the graphics are added to the list. So,
        // after this call, the list is ready for painting.

        // call getRectangle();
        if (Debug.debugging("rpf")) {
            Debug.output(getName() + "|RpfLayer.prepare(): "
                    + "calling getRectangle " + " with projection: "
                    + projection + " ul = " + projection.getUpperLeft()
                    + " lr = " + projection.getLowerRight());
        }

        if (frameProvider.needViewAttributeUpdates()) {
            frameProvider.setViewAttributes(viewAttributes);
        }

        Projection cadrgProj = projection;
        if (!(projection instanceof CADRG)) {
            cadrgProj = new CADRG(projection.getCenter(), projection.getScale(), projection.getWidth(), projection.getHeight());

            Point ulp = cadrgProj.forward(projection.getUpperLeft());
            Point lrp = cadrgProj.forward(projection.getLowerRight());

            int w = (int) Math.abs(lrp.getX() - ulp.getX());
            int h = (int) Math.abs(lrp.getY() - ulp.getY());

            // float cadrgScale =
            // ProjMath.getScale(projection.getUpperLeft(),
            // projection.getLowerRight(),
            // cadrgProj);

            cadrgProj = new CADRG(projection.getCenter(), projection.getScale(), w, h);
        }

        // Fetch the list with a CADRG projection, generate it with
        // the real projection.

        OMGraphicList omGraphicList;
        try {
            omGraphicList = this.cache.getRectangle(cadrgProj);
        } catch (java.lang.NullPointerException npe) {
            Debug.error(getName()
                    + "|RpfLayer.prepare(): Something really bad happened - \n "
                    + npe);
            npe.printStackTrace();
            omGraphicList = new OMGraphicList();
            this.cache = null;
        }

        /////////////////////
        // safe quit
        int size = 0;
        if (omGraphicList != null) {
            size = omGraphicList.size();
            if (Debug.debugging("basic")) {
                Debug.output("RpfLayer.prepare(): finished with " + size
                        + " graphics");
            }

            // Don't forget to project them. Since they are only
            // being recalled if the projection hase changed, then we
            // need to force a reprojection of all of them because the
            // screen position has changed.
            omGraphicList.project(projection, true);

        } else {
            Debug.message("basic",
                    "RpfLayer.prepare(): finished with null graphics list");
        }

        return omGraphicList;
    }

    /**
     * Paints the layer.
     *
     * _at_param g the Graphics context for painting
     *
     */
    public void paint(java.awt.Graphics g) {
        Debug.message("rpf", "RpfLayer.paint()");
        super.paint(g);

        if (coverage != null && coverage.isInUse()) {
            coverage.paint(g);
        }
    }

    //----------------------------------------------------------------------
    // GUI
    //----------------------------------------------------------------------
    private transient Box box = null;

    /**
     * Provides the palette widgets to control the options of showing
     * maps, or attribute text.
     *
     * _at_return Component object representing the palette widgets.
     */
    public java.awt.Component getGUI() {
        if (box == null) {
            JCheckBox showMapsCheck, showInfoCheck, lockSeriesCheck;

            box = Box.createVerticalBox();
            Box box1 = Box.createVerticalBox();
            Box box2 = Box.createVerticalBox();
            JPanel topbox = new JPanel();
            JPanel subbox2 = new JPanel();

            showMapsCheck = new JCheckBox("Show Images", viewAttributes.showMaps);
            showMapsCheck.setActionCommand(showMapsCommand);
            showMapsCheck.addActionListener(this);

            showInfoCheck = new JCheckBox("Show Attributes", viewAttributes.showInfo);
            showInfoCheck.setActionCommand(showInfoCommand);
            showInfoCheck.addActionListener(this);

            String tmpCS = viewAttributes.chartSeries;
            if (tmpCS == null) {
                tmpCS = RpfViewAttributes.ANY;
            }

            boolean locked = !tmpCS.equalsIgnoreCase(RpfViewAttributes.ANY);
            String lockedTitle = locked ? (lockedButtonTitle + " - " + tmpCS)
                    : unlockedButtonTitle;

            lockSeriesCheck = new JCheckBox(lockedTitle, locked);
            lockSeriesCheck.setActionCommand(lockSeriesCommand);
            lockSeriesCheck.addActionListener(this);

            box1.add(showMapsCheck);
            box1.add(showInfoCheck);
            box1.add(lockSeriesCheck);

            if (coverage != null) {
                JCheckBox showCoverageCheck;
                if (coverage.isShowPalette()) {
                    showCoverageCheck = new JCheckBox("Show Coverage Tool", false);
                } else {
                    showCoverageCheck = new JCheckBox("Show Coverage", false);
                }
                showCoverageCheck.setActionCommand(showCoverageCommand);
                showCoverageCheck.addActionListener(this);
                box1.add(showCoverageCheck);
            }

            topbox.add(box1);
            topbox.add(box2);
            box.add(topbox);

            JPanel opaquePanel = PaletteHelper.createPaletteJPanel("Map Opaqueness");
            JSlider opaqueSlide = new JSlider(JSlider.HORIZONTAL, 0/* min */, 255/* max */, viewAttributes.opaqueness/* inital */);
            java.util.Hashtable dict = new java.util.Hashtable();
            dict.put(new Integer(0), new JLabel("clear"));
            dict.put(new Integer(255), new JLabel("opaque"));
            opaqueSlide.setLabelTable(dict);
            opaqueSlide.setPaintLabels(true);
            opaqueSlide.setMajorTickSpacing(50);
            opaqueSlide.setPaintTicks(true);
            opaqueSlide.addChangeListener(new ChangeListener() {
                public void stateChanged(ChangeEvent ce) {
                    JSlider slider = (JSlider) ce.getSource();
                    if (slider.getValueIsAdjusting()) {
                        viewAttributes.opaqueness = slider.getValue();
                        fireRequestInfoLine("RPF Opaqueness set to "
                                + viewAttributes.opaqueness
                                + " for future requests.");
                    }
                }
            });
            opaquePanel.add(opaqueSlide);
            box.add(opaquePanel);

            JButton redraw = new JButton("Redraw RPF Layer");
            redraw.addActionListener(this);
            subbox2.add(redraw);
            box.add(subbox2);
        }
        return box;
    }

    //----------------------------------------------------------------------
    // ActionListener interface implementation
    //----------------------------------------------------------------------

    /**
     * The Action Listener method, that reacts to the palette widgets
     * actions.
     */
    public void actionPerformed(ActionEvent e) {
        super.actionPerformed(e);
        String cmd = e.getActionCommand();
        if (cmd == showMapsCommand) {
            JCheckBox mapCheck = (JCheckBox) e.getSource();
            viewAttributes.showMaps = mapCheck.isSelected();
            repaint();
        } else if (cmd == showInfoCommand) {
            JCheckBox infoCheck = (JCheckBox) e.getSource();
            viewAttributes.showInfo = infoCheck.isSelected();
            repaint();
        } else if (cmd == lockSeriesCommand) {
            JCheckBox lockCheck = (JCheckBox) e.getSource();
            boolean locked = lockCheck.isSelected();
            if (locked) {
                Vector vector = getCoverageBoxes();
                String seriesName;

                if (vector == null || vector.size() == 0) {
                    seriesName = RpfViewAttributes.ANY;
                } else {
                    seriesName = ((RpfCoverageBox) vector.elementAt(0)).chartCode;
                }

                if (seriesName == null) {
                    seriesName = RpfViewAttributes.ANY;
                    fireRequestMessage("The "
                            + getName()
                            + " Layer is having trouble determining what kind\nof charts are being displayed. Can't establish lock for charts\ncurrently being viewed.");
                }

                lockCheck.setText(lockedButtonTitle + " - " + seriesName);
                viewAttributes.chartSeries = seriesName;

            } else {
                lockCheck.setText(unlockedButtonTitle);
                viewAttributes.chartSeries = RpfViewAttributes.ANY;
            }

        } else if (cmd == showCoverageCommand) {
            if (coverage != null) {
                JCheckBox coverageCheck = (JCheckBox) e.getSource();
                coverage.setInUse(coverageCheck.isSelected());
                if (coverage.isInUse()) {
                    coverage.prepare(frameProvider,
                            getProjection(),
                            viewAttributes.chartSeries);
                }
                repaint();
            }
        } else {
            // Debug.error("RpfLayer: Unknown action command \"" + cmd
            // +
            // "\" in RpfLayer.actionPerformed().");

            // OK, not really sure what happened, just act like a
            // reset.
            doPrepare();
        }
    }

    /** Print out the contents of a properties file. */
    public static void main(String[] argv) {
        System.out.println("#########################################");
        System.out.println("# Properties for the JAVA RpfLayer");
        System.out.println("# Mandatory properties:");
        System.out.println("layer.class=com.bbn.openmap.layer.rpf.RpfLayer");
        System.out.println("layer.prettyName=CADRG");
        System.out.println("# This property should reflect the paths to the RPF directories");
        System.out.println("layer.paths=<Path to RPF dir>;/cdrom/cdrom0/RPF");
        System.out.println("# Optional properties - Defaults will be set for properties not included (defaults are listed):");
        System.out.println("# Number between 0-255: 0 is transparent, 255 is opaque");
        System.out.println("layer.opaque=255");
        System.out.println("# Number of colors to use on the maps - 16, 32, 216");
        System.out.println("layer.numberColors=216");
        System.out.println("# Display maps on startup");
        System.out.println("layer.showMaps=true");
        System.out.println("# Display attribute information on startup");
        System.out.println("layer.showInfo=false");
        System.out.println("# Scale images to match map scale");
        System.out.println("layer.scaleImages=true");
        System.out.println("# The scale factor to allow when scaling images (2x, 4x, also mean 1/2, 1/4). Default is 4.");
        System.out.println("rpf.imageScaleFactor=4");
        System.out.println("# Reset the cache if layer is removed from map");
        System.out.println("layer.killCache=false");
        System.out.println("# Limit the display to the chart code specified. (GN, JN, ON, TP, etc.)");
        System.out.println("layer.chartSeries=ANY");
        System.out.println("# Set the subframe cache size. (Number of subframes to hold on to, 256x256 pixels");
        System.out.println("layer.subframeCacheSize=128");
        System.out.println("# Get the subframe attribute data from the frame provider.");
        System.out.println("rpf.autofetchAttributes=false");
        System.out.println("#If you want the coverage tool to be available");
        System.out.println("layer.coverage=true");
        System.out.println("#Then add coverage constants as needed.");
    }
}

// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/openmap/openmap/src/openmap/com/bbn/openmap/layer/daynight/DayNightLayer.java,v $
// $RCSfile: DayNightLayer.java,v $
// $Revision: 1.7 $
// $Date: 2004/10/14 18:18:17 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.layer.daynight;

/* Java Core */
import java.awt.Color;
import java.awt.event.ActionListener;
import java.util.Properties;

import javax.swing.Timer;

/* OpenMap */
import com.bbn.openmap.I18n;
import com.bbn.openmap.LatLonPoint;
import com.bbn.openmap.MoreMath;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
import com.bbn.openmap.event.ProjectionListener;
import com.bbn.openmap.layer.OMGraphicHandlerLayer;
import com.bbn.openmap.omGraphics.OMCircle;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMRaster;
import com.bbn.openmap.proj.Cylindrical;
import com.bbn.openmap.proj.GreatCircle;
import com.bbn.openmap.proj.Length;
import com.bbn.openmap.proj.Projection;

/**
 * The DayNightLayer is a layer that draws the day/Night terminator on
 * the map. When the layer is re-projected, it figures out the
 * brightest point on the earth (closest to the sun), and creates an
 * image that has daytime pixels clear and the nighttime pixels
 * shaded. There are a couple of options available for the layer. The
 * terminator can be faded from light to dark, and the width of the
 * fading can be adjusted. The color of the shading can be changed.
 * The shading can reflect the current time, or be set to display the
 * shading of a specified time. A time interval can be set to have the
 * layer automatically update at regular intervals.
 *
 * <P>
 * The openmap.properties file can control the layer with the
 * following settings: <code><pre>
 *
 * # These are all optional, and can be omitted if you want to use the defaults.
 * # draw terminator as poly (faster calculation than image,
 * # defaults to true).
 * daynight.doPolyTerminator=true
 * # number of vertices for polygon terminator line. this is only valid
 * # if doPolyTerminator is true...
 * daynight.terminatorVerts=360
 * # termFade - the distance of the transition of fade, as a percentage of PI.
 * daynight.termFade=.1
 * # currentTime - true to display the shading at the computer's current time.
 * daynight.currentTime=true
 * # overlayTime - time, in milliseconds from java/unix epoch, to set the layer
 * # time being displayed. currentTime has to be false for this to be used.
 * daynight.overlayTime=919453689000
 * # updateInterval - time in milliseconds between updates. currentTime has to be
 * # true for this to be used.
 * daynight.updateInterval=300000
 * # Color of the shading (32bit Hex ARGB)
 * daynight.nighttimeColor=64000000
 *
 * </pre></code> In addition, you can get this layer to work with the
 * OpenMap viewer by editing your openmap.properties file: <code><pre>
 *
 * # layers
 * openmap.layers=daynight ...
 * # class
 * daynight.class=com.bbn.openmap.layer.daynight.DayNightLayer
 * # name
 * daynight.prettyName=Day/Night Shading
 *
 * </pre></code>
 *
 */
public class DayNightLayer extends OMGraphicHandlerLayer implements
        ProjectionListener, ActionListener {
    /**
     * Default value of fade to the terminator line, set to .10f. This
     * means that the last 10% of the horizon will be faded out.
     */
    public static final float DEFAULT_TERM_FADE = .10f;
    /**
     * Default update interval, which is never - updates occur on
     * re-projections.
     */
    public static final int DO_NOT_UPDATE = -1;

    /** The color of daytime - default is white and clear. */
    protected Color daytimeColor = new Color(0x00FFFFFF);
    /** the color of darkness - default is black. */
    protected Color nighttimeColor = new Color(0x7F000000);

    /**
     * Percentage of the distance from the horizon to the brightest
     * point to start fading to darkness. Expected to be between 0.0
     * and 0.5.
     */
    protected float termFade = DEFAULT_TERM_FADE;
    /**
     * If true, the layer will set the darkness according to the
     * current time.
     */
    protected boolean currentTime = true;
    /**
     * The time used to create the layer, in milliseconds from
     * java/unix epoch.
     */
    protected long overlayTime = 0;
    /**
     * Update interval to automatically update the layer, in
     * milli-seconds
     */
    protected int updateInterval = 300000;
    /** Update timer. */
    protected Timer timer;

    /**
     * Create the terminator line as a polygon.
     */
    protected boolean doPolyTerminator = true;

    /**
     * The number of vertices of the polygon terminator line.
     */
    protected int terminatorVerts = 360;

    /////// Properties
    public static final String DaytimeColorProperty = "daytimeColor";
    public static final String NighttimeColorProperty = "nighttimeColor";
    public static final String TermFadeProperty = "termFade";
    public static final String CurrentTimeProperty = "useCurrentTime";
    public static final String OverlayTimeProperty = "overlayTime";
    public static final String UpdateIntervalProperty = "updateInterval";
    public static final String DoPolyTerminatorProperty = "doPolyTerminator";
    public static final String TerminatorVertsProperty = "terminatorVerts";

    /**
     * The default constructor for the Layer. All of the attributes
     * are set to their default values.
     */
    public DayNightLayer() {
        setName("Day-Night");
    }

    /**
     * The properties and prefix are managed and decoded here, for the
     * standard uses of the DayNightLayer.
     *
     * _at_param prefix string prefix used in the properties file for
     * this layer.
     * _at_param properties the properties set in the properties file.
     */
    public void setProperties(String prefix, java.util.Properties properties) {
        super.setProperties(prefix, properties);

        prefix = PropUtils.getScopedPropertyPrefix(prefix);

        overlayTime = PropUtils.longFromProperties(properties, prefix
                + OverlayTimeProperty, overlayTime);
        if (overlayTime <= 0) {
            currentTime = true;
        }

        currentTime = PropUtils.booleanFromProperties(properties, prefix
                + CurrentTimeProperty, currentTime);

        updateInterval = PropUtils.intFromProperties(properties, prefix
                + UpdateIntervalProperty, updateInterval);

        if (updateInterval > 0) {
            timer = new Timer(updateInterval, this);
        }

        termFade = PropUtils.floatFromProperties(properties, prefix
                + TermFadeProperty, termFade);

        if (termFade < 0 || termFade >= .5) {
            Debug.output("DayNightLayer: termFade funky value ignored.");
            termFade = DEFAULT_TERM_FADE;
        }

        daytimeColor = (Color) PropUtils.parseColorFromProperties(properties,
                prefix + DaytimeColorProperty,
                daytimeColor);
        nighttimeColor = (Color) PropUtils.parseColorFromProperties(properties,
                prefix + NighttimeColorProperty,
                nighttimeColor);

        doPolyTerminator = PropUtils.booleanFromProperties(properties, prefix
                + DoPolyTerminatorProperty, doPolyTerminator);
        terminatorVerts = PropUtils.intFromProperties(properties, prefix
                + TerminatorVertsProperty, terminatorVerts);
    }

    public Properties getProperties(Properties props) {
        props = super.getProperties(props);
        String prefix = PropUtils.getScopedPropertyPrefix(this);

        props.put(prefix + OverlayTimeProperty, Long.toString(overlayTime));
        props.put(prefix + CurrentTimeProperty,
                new Boolean(currentTime).toString());
        props.put(prefix + UpdateIntervalProperty,
                Integer.toString(updateInterval));
        props.put(prefix + TermFadeProperty, Float.toString(termFade));
        props.put(prefix + DaytimeColorProperty,
                Integer.toHexString(daytimeColor.getRGB()));
        props.put(prefix + NighttimeColorProperty,
                Integer.toHexString(nighttimeColor.getRGB()));
        props.put(prefix + DoPolyTerminatorProperty,
                new Boolean(doPolyTerminator).toString());
        props.put(prefix + TerminatorVertsProperty,
                Integer.toString(terminatorVerts));

        return props;
    }

    public Properties getPropertyInfo(Properties props) {
        props = super.getPropertyInfo(props);
        String interString;

        interString = i18n.get(DayNightLayer.class,
                OverlayTimeProperty,
                I18n.TOOLTIP,
                "The time used to create the layer, in milliseconds from java/unix epoch (leave empty for current time).");
        props.put(OverlayTimeProperty, interString);
        interString = i18n.get(DayNightLayer.class,
                OverlayTimeProperty,
                OverlayTimeProperty);
        props.put(OverlayTimeProperty + LabelEditorProperty, interString);

        interString = i18n.get(DayNightLayer.class,
                CurrentTimeProperty,
                I18n.TOOLTIP,
                "If true, the layer will set the darkness according to the current time.");
        props.put(CurrentTimeProperty, interString);
        interString = i18n.get(DayNightLayer.class,
                CurrentTimeProperty,
                CurrentTimeProperty);
        props.put(CurrentTimeProperty + LabelEditorProperty, interString);
        props.put(CurrentTimeProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");

        interString = i18n.get(DayNightLayer.class,
                UpdateIntervalProperty,
                I18n.TOOLTIP,
                "Update interval to automatically update the layer, in milli-seconds.");
        props.put(UpdateIntervalProperty, interString);
        interString = i18n.get(DayNightLayer.class,
                UpdateIntervalProperty,
                UpdateIntervalProperty);
        props.put(UpdateIntervalProperty + LabelEditorProperty, interString);

        interString = i18n.get(DayNightLayer.class,
                TermFadeProperty,
                I18n.TOOLTIP,
                "Percentage of the distance from the horizon to the brightest point to start fading to darkness, 0.0 to 0.5.");
        props.put(TermFadeProperty, interString);
        interString = i18n.get(DayNightLayer.class,
                TermFadeProperty,
                TermFadeProperty);
        props.put(TermFadeProperty + LabelEditorProperty, interString);

        interString = i18n.get(DayNightLayer.class,
                DaytimeColorProperty,
                I18n.TOOLTIP,
                "Color for the daytime area, if polygon terminator isn't used.");
        props.put(DaytimeColorProperty, interString);
        interString = i18n.get(DayNightLayer.class,
                DaytimeColorProperty,
                DaytimeColorProperty);
        props.put(DaytimeColorProperty + LabelEditorProperty, interString);
        props.put(DaytimeColorProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor");

        interString = i18n.get(DayNightLayer.class,
                NighttimeColorProperty,
                I18n.TOOLTIP,
                "Color for the nighttime area.");
        props.put(NighttimeColorProperty, interString);
        interString = i18n.get(DayNightLayer.class,
                NighttimeColorProperty,
                NighttimeColorProperty);
        props.put(NighttimeColorProperty + LabelEditorProperty, interString);
        props.put(NighttimeColorProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor");

        interString = i18n.get(DayNightLayer.class,
                DoPolyTerminatorProperty,
                I18n.TOOLTIP,
                "Render with polygon instead of image (it's faster).");
        props.put(DoPolyTerminatorProperty, interString);
        interString = i18n.get(DayNightLayer.class,
                DoPolyTerminatorProperty,
                DoPolyTerminatorProperty);
        props.put(DoPolyTerminatorProperty + LabelEditorProperty, interString);
        props.put(DoPolyTerminatorProperty + ScopedEditorProperty,
                "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");

        interString = i18n.get(DayNightLayer.class,
                TerminatorVertsProperty,
                I18n.TOOLTIP,
                "Number of vertices of the polygon terminator (more is smoother).");
        props.put(TerminatorVertsProperty, interString);
        interString = i18n.get(DayNightLayer.class,
                TerminatorVertsProperty,
                TerminatorVertsProperty);
        props.put(TerminatorVertsProperty + LabelEditorProperty, interString);

        props.put(initPropertiesProperty, CurrentTimeProperty + " "
                + OverlayTimeProperty + " " + UpdateIntervalProperty + " "
                + NighttimeColorProperty + " " + DoPolyTerminatorProperty + " "
                + TerminatorVertsProperty + " " + DaytimeColorProperty + " "
                + TermFadeProperty + " " + RemovableProperty + " "
                + AddAsBackgroundProperty);

        return props;
    }

    /**
     * Handle an ActionEvent from the Timer.
     *
     * _at_param ae action event from the timer.
     */
    public void actionPerformed(java.awt.event.ActionEvent ae) {
        super.actionPerformed(ae);
        if (Debug.debugging("daynight")) {
            Debug.output(getName() + "| updating image via timer...");
        }
        doPrepare();
    }

    /**
     * Create the OMGraphic that acts as an overlay showing the
     * day/night terminator. The brightest spot on the earth is
     * calculated, and then each pixel is inverse projected to find
     * out its coordinates. Then the great circle distance is
     * calculated. The terminator is assumed to be the great circle
     * where all the points are PI/2 away from the bright point. If
     * the termFade variable is set, then the difference in color over
     * the terminator is feathered, on equal amount of the terminator.
     *
     * _at_param projection the projection of the screen,
     * _at_return OMGraphic containing image to use for the layer. The
     * image has been projected.
     */
    protected OMGraphic createImage(Projection projection) {

        if (currentTime)
            overlayTime = System.currentTimeMillis();

        if (Debug.debugging("daynight")) {
            Debug.output("DayNightLayer: Calculating sun position at time "
                    + Long.toString(overlayTime));
        }

        LatLonPoint brightPoint = SunPosition.sunPosition(overlayTime);

        Debug.message("daynight", "DayNightLayer: Calculated sun position");

        // Do a fast and relatively inexpensive calculation of the
        // terminator. NOTE: for non-cylindrical projections we don't
        // create a full-hemisphere circle so that we don't get
        // flip-rendering problem...
        if (doPolyTerminator) {
            Debug.message("daynight",
                    "DayNightLayer: Creating polygon terminator");
            LatLonPoint darkPoint = GreatCircle.spherical_between(brightPoint.radlat_,
                    brightPoint.radlon_,
                    (float) Math.PI,
                    (float) Math.PI / 4f);
            OMCircle circle = new OMCircle(darkPoint, (projection instanceof Cylindrical) ? 90f
                    : 89.0f,//HACK
                    Length.DECIMAL_DEGREE, terminatorVerts);
            circle.setPolarCorrection(true);
            circle.setFillPaint(nighttimeColor);
            circle.setLinePaint(nighttimeColor);
            circle.generate(projection);
            Debug.message("daynight",
                    "DayNightLayer: Done creating polygon terminator");
            return circle;
        }

        int width = projection.getWidth();
        int height = projection.getHeight();
        int[] pixels = new int[width * height];

        OMRaster ret = new OMRaster((int) 0, (int) 0, width, height, pixels);

        float lat, lon;

        Debug.message("daynight", getName()
                + "|createImage: Center of bright spot lat= "
                + brightPoint.getLatitude() + ", lon= "
                + brightPoint.getLongitude());

        // Light is clear and/or white
        int light = daytimeColor.getRGB();

        // Allocate the memory here for the testPoint
        LatLonPoint testPoint = new LatLonPoint(0f, 0f);
        // great circle distance between the bright point and each
        // pixel.
        float distance;

        // Set the darkeness value
        int dark = nighttimeColor.getRGB();// ARGB
        int darkness = dark >>> 24;// darkness alpha
        int value;

        // Calculate the fae limits around the terminator
        float upperFadeLimit = (float) (MoreMath.HALF_PI * (1.0 + termFade));
        float lowerFadeLimit = (float) (MoreMath.HALF_PI * (1.0 - termFade));
        int fadeColorValue = 0x00FFFFFF & (dark); // RGB

        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {

                testPoint = projection.inverse(i, j, testPoint);
                distance = GreatCircle.spherical_distance(brightPoint.radlat_,
                        brightPoint.radlon_,
                        testPoint.radlat_,
                        testPoint.radlon_);

                if (distance > upperFadeLimit) {
                    pixels[j * width + i] = dark;
                } else if (distance > lowerFadeLimit) {
                    value = (int) (darkness * (1 - ((upperFadeLimit - distance) / (upperFadeLimit - lowerFadeLimit))));
                    value <<= 24;
                    pixels[j * width + i] = fadeColorValue | value;
                } else {
                    pixels[j * width + i] = light;
                }
            }
        }

        ret.generate(projection);
        return ret;
    }

    /**
     * Prepares the graphics for the layer. This is where the
     * getRectangle() method call is made on the location.
     * <p>
     * Occasionally it is necessary to abort a prepare call. When this
     * happens, the map will set the cancel bit in the LayerThread,
     * (the thread that is running the prepare). If this Layer needs
     * to do any cleanups during the abort, it should do so, but
     * return out of the prepare asap.
     *
     */
    public synchronized OMGraphicList prepare() {

        OMGraphicList list = getList();
        if (list == null) {
            list = new OMGraphicList();
        } else {
            list.clear();
        }

        if (isCancelled()) {
            Debug.message("daynight", getName()
                    + "|DayNightLayer.prepare(): aborted.");
            return null;
        }

        Debug.message("basic", getName() + "|DayNightLayer.prepare(): doing it");

        OMGraphic ras = createImage(getProjection());
        if (timer != null)
            timer.restart();
        list.add(ras);

        return list;
    }

    /**
     * Get the time of the overlay.
     */
    public long getOverlayTime() {
        return overlayTime;
    }

    /**
     * Set the time for the overlay.
     */
    public void setOverlayTime(long ot) {
        overlayTime = ot;
        currentTime = false;
        doPrepare();
    }

    /**
     * Returns whether the layer will set the overlayTime to the time
     * the image is created.
     */
    public boolean getCurrentTime() {
        return currentTime;
    }

    /**
     * Set whether the layer should set the overlayTime to the time
     * the image is created. If the time is being set to reflect a
     * time other than the current time, this needs to be set to
     * false. It actually is, if you manually set the overlay time.
     */
    public void setCurrentTime(boolean ct) {
        currentTime = ct;
    }

    /**
     * Get the timer being used for automatic updates. May be null if
     * a timer is not set.
     */
    public Timer getTimer() {
        return timer;
    }

    /**
     * If you want the layer to update itself at certain intervals,
     * you can set the timer to do that. Set it to null to disable it.
     */
    public void setTimer(Timer t) {
        timer = t;
    }

}

// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/openmap/openmap/src/openmap/com/bbn/openmap/gui/menu/ProjectionMenu.java,v $
// $RCSfile: ProjectionMenu.java,v $
// $Revision: 1.6 $
// $Date: 2004/10/14 18:18:16 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap.gui.menu;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.*;

import com.bbn.openmap.Environment;
import com.bbn.openmap.I18n;
import com.bbn.openmap.MapBean;
import com.bbn.openmap.event.ProjectionEvent;
import com.bbn.openmap.event.ProjectionListener;
import com.bbn.openmap.event.ProjectionSupport;
import com.bbn.openmap.gui.AbstractOpenMapMenu;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.ProjectionException;
import com.bbn.openmap.proj.ProjectionFactory;
import com.bbn.openmap.proj.ProjectionLoader;
import com.bbn.openmap.util.Debug;


/**
 * Provides ProjectionMenu items for selecting Projection type.
 */
public class ProjectionMenu extends AbstractOpenMapMenu implements
        ActionListener, ProjectionListener, PropertyChangeListener {

    public static final String defaultText = "Projection";
    
    protected transient ProjectionSupport projectionSupport = new ProjectionSupport(this);
    protected transient Projection projection;
    protected transient Component projComponent;
    public final static transient String projCmd = "setProj";
    protected I18n i18n = Environment.getI18n();

    /**
     * Create the projection submenu.
     */
    public ProjectionMenu() {
        super();
        setText(i18n.get(this, "projectionMenu", defaultText));
        
    }

    public void configure(Vector loaders) {
        removeAll();
        JRadioButtonMenuItem rb;
        ButtonGroup group = new ButtonGroup();

        for (Iterator it = loaders.iterator(); it.hasNext();) {
            Object obj = it.next();
            if (obj instanceof ProjectionLoader) {
                ProjectionLoader pl = (ProjectionLoader) obj;

                rb = new JRadioButtonMenuItem(pl.getPrettyName());
                rb.setActionCommand(projCmd);
                String plclassname = pl.getProjectionClass().getName();
                rb.setName(plclassname);
                rb.setToolTipText(pl.getDescription());
                rb.addActionListener(this);
                group.add(rb);
                add(rb);
            }
        }

        setProjection(projection);
    }

    public void actionPerformed(ActionEvent ae) {
        String command = ae.getActionCommand();

        Debug.message("projectionmenu", "ProjectionMenu.actionPerformed(): "
                + command);

        if (command == projCmd) {
            JRadioButtonMenuItem rb = (JRadioButtonMenuItem) (ae.getSource());
            String projclassname = rb.getName();
            Debug.message("projectionmenu", "ProjectionMenu new proj name: "
                    + projclassname);
            try {
                Projection newProj = ProjectionFactory.makeProjection(projclassname,
                        projection);
                fireProjectionChanged(newProj);
            } catch (ProjectionException pe) {
                rb.setEnabled(false);
            }
        }
    }

    public void propertyChange(PropertyChangeEvent pce) {
        if (pce.getPropertyName() == ProjectionFactory.AvailableProjectionProperty) {
            configure((Vector) pce.getNewValue());
        }
    }

    //------------------------------------------------------------
    // ProjectionListener interface
    //------------------------------------------------------------

    /**
     * The Map projection has changed, in order to baseline new
     * changes as a result of menu options being selected.
     *
     * @param e ProjectionEvent
     */
    public void projectionChanged(ProjectionEvent e) {
        if (Debug.debugging("projectionmenu")) {
            System.out.println("ProjectionMenu.projectionChanged()");
        }

        Projection newProj = e.getProjection();
        if (projection == null || (!projection.equals(newProj))) {
            setProjection((Projection) newProj.makeClone());
            Object source = e.getSource();
            if (source instanceof Component) {
                projComponent = (Component) source;
            }
        }
    }

    /**
     * Set the projection. This changes the setting of the projection
     * radio button menu.
     *
     * _at_param aProjection Projection
     */
    protected synchronized void setProjection(Projection aProjection) {
        projection = aProjection;

        if (projection == null) {
            return;
        }

        String newProjClassName = projection.getClass().getName();

        // Change the selected projection type menu item
        for (int i = 0; i < getItemCount(); i++) {
            JMenuItem item = getItem(i);
            if (newProjClassName.equals(item.getName())) {
                if (Debug.debugging("projectionmenu")) {
                    Debug.output("ProjectionMenu | setting " + item.getName()
                            + " as active");
                }
                item.setSelected(true);
                return;
            }
        }
    }

    /**
     * Convenience function for setting up listeners
     */
    public void setupListeners(MapBean map) {
        Debug.message("projectionmenu", "ProjectionMenu | setupListeners");
        addProjectionListener(map);
        map.addProjectionListener(this);
    }

    /**
     * Convenience function for undoing set up listeners
     */
    public void undoListeners(MapBean map) {
        removeProjectionListener(map);
        map.removeProjectionListener(this);
    }

    /*----------------------------------------------------------------------
     * Projection Support - for broadcasting projection changed events
     *----------------------------------------------------------------------*/
    /**
     * Add a ProjectionListener to this menu and its components.
     */
    protected synchronized void addProjectionListener(ProjectionListener l) {
        projectionSupport.addProjectionListener(l);
    }

    /**
     * Remove a ProjectionListener from this menu and its components.
     */
    protected synchronized void removeProjectionListener(ProjectionListener l) {
        projectionSupport.removeProjectionListener(l);
    }

    /**
     * Fire the changed projection from the support.
     */
    public void fireProjectionChanged(Projection p) {
        projectionSupport.fireProjectionChanged(p);
    }

    public void findAndInit(Object someObj) {
        if (someObj instanceof MapBean) {
            setupListeners((MapBean) someObj);
        }
        if (someObj instanceof ProjectionFactory) {
            ((ProjectionFactory) someObj).addPropertyChangeListener(this);
        }
    }

    public void findAndUndo(Object someObj) {
        if (someObj instanceof MapBean) {
            undoListeners((MapBean) someObj);
        }

        if (someObj instanceof ProjectionFactory) {
            ((ProjectionFactory) someObj).removePropertyChangeListener(this);
        }
    }
}

# Sample ResourceBundle properties file

MouseModeMenu.mouseModeMenu=Mouse Mode

ProjectionMenu.projectionMenu=Projection

# Sample ResourceBundle properties file

MouseModeMenu.mouseModeMenu=Tryb pracy myszy

ProjectionMenu.projectionMenu=Odwzorowanie

--
[To unsubscribe to this list send an email to "majdart_at_bbn.com"
with the following text in the BODY of the message "unsubscribe openmap-users"]
Received on Thu Jan 27 2005 - 06:42:07 EST

This archive was generated by hypermail 2.3.0 : Tue Mar 28 2017 - 23:25:06 EDT